目录
2.@Configuration的proxyBeanMethod属性
7.导入实现了ImportBeanDefinitionRegistrar接口的类
8.导入实现了BeanDefinitionRegistryPostProcessor接口的类
如果以前使用xml配置bean,现在想改用注解方式的配置类,如果将xml内的改写太麻烦,而且可能出错,想加载配置类的同时也加载xml配置文件,使用@ImpotResource注解实现。
- @ImportResouce("applicationContext.xml") //将applicationContext.xml注入进来,使用一个容器
- public class MyConfig {
-
- }
spring中有两种生成代理对象的方式:jdk动态代理和cglib动态代理
在配置类上注解@Configuration中有proxyBeanMethod属性,默认值为true,即:
@Configuration=@Configuration(proxyBeanMethod=true)
这样配置类里@Bean生成的对象是使用cglib代理生成的代理对象,这样在其他地方不管调用几次代理对象都是同一个。
将proxyBeanMethod属性改为false,就不是代理对象了,每次调用生成一个新的对象
@Configuration(proxyBeanMethod=true)
参考Spring 加载Bean的方式(8种) - 郝志锋 - 博客园
使用扫描的方式加载bean是企业级开发中常见的bean的加载方式,但是由于扫描的时候不仅可以加载到你要的东西,还有可能加载到各种各样的乱七八糟的东西,万一没有控制好得不偿失了。
所以我们需要一种精准制导的加载方式,使用@Import注解就可以解决你的问题。它可以加载所有的一切,只需要在注解的参数中写上加载的类对应的.class即可。有人就会觉得,还要自己手写,多麻烦,不如扫描好用。对呀,但是他可以指定加载啊,好的命名规范配合@ComponentScan可以解决很多问题,但是@Import注解拥有其重要的应用场景。有没有想过假如你要加载的bean没有使用@Component修饰呢?这下就无解了,而@Import就无需考虑这个问题。
被@Import进的bean的名字,是全路径类名。
- public class Dog {
- }
- @Import({Dog.class})
- // 被导入的为普通的Class就行,无需使用注解声明为bean
- public class SpringConfig4 {
-
- }
测试:
- public class App4 {
- public static void main(String[] args) {
- ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);
- String[] names = ctx.getBeanDefinitionNames();
- for (String name : names) {
- System.out.println(name);
- }
- System.out.println("----------------------");
- }
- }
这样在User类和Book类不需要使用@Component等注解就配置了对应bean,而且创建迅速,当导入的时候就容器中有了对应bean,可直接使用了,也体现了spring的无侵入式编程,降低与spring技术的耦合度。
也可以导入一个配置类,且配置其中的所有bean。
除了加载bean,还可以使用@Import注解加载配置类。其实本质上是一样的。 但是注意,这种@Import方式加载进spring的配置Bean的名字(全路径类名)和前面扫描加载进spring的配置bean的名字(类名小写)不同,具体可见方式二中和本方式的测试代码的运行效果。
- @Configuration
- // 实际山,当被使用@Import 方式导入时, 无论 @Configuration 注解是否有,都可以将 DbConfig和dataSource 加载到Spring容器中
- public class DbConfig {
- @Bean
- public DruidDataSource dataSource(){
- DruidDataSource ds = new DruidDataSource();
- return ds;
- }
-
- }
- @Import({Dog.class,DbConfig.class})
- public class SpringConfig4 {
-
- }
测试:
- public class App3 {
- public static void main(String[] args) {
- ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);
- String[] names = ctx.getBeanDefinitionNames();
- for (String name : names) {
- System.out.println(name);
- }
-
- }
- }
前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载,下面这种方式就比较特殊了,可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。这种方式平时应用开发中不常用,但是在框架开发中会使用。
- public class Cat {
- public Cat(){
- }
-
- int age;
- public Cat(int age){
- this.age = age;
- }
-
- @Override
- public String toString() {
- return "Cat{" +
- "age=" + age +
- '}';
- }
- }
- public class Mouse {
- }
测试:
- public class App5 {
- public static void main(String[] args) {
- // ApplicationContext对象做不了,只能用AnnotationConfigApplicationContext对象
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);
- //上下文容器对象已经初始化完毕后,手工加载bean
- ctx.registerBean("tom", Cat.class,0);
- ctx.registerBean("tom", Cat.class,1);
- ctx.registerBean("tom", Cat.class,2);
- ctx.register(Mouse.class);
- String[] names = ctx.getBeanDefinitionNames();
- for (String name : names) {
- System.out.println(name);
- }
- System.out.println("----------------------");
- System.out.println(ctx.getBean(Cat.class));
- }
- }

bean的加载可以进行编程化的控制,添加if语句就可以实现bean的加载控制了。但是毕竟是在容器初始化后实现bean的加载控制,那是否可以在容器初始化过程中进行控制呢?答案是必须的。实现ImportSelector接口的类可以设置加载的bean的全路径类名,记得一点,只要能编程就能判定,能判定意味着可以控制程序的运行走向,进而控制一切。
现在又多了一种控制bean加载的方式,或者说是选择bean的方式。
在Spring源码中大量使用,通过导入实现了ImportSelector接口的类,实现对导入源的编程式处理
- public class MyImportSelector implements ImportSelector {
- @Override
- public String[] selectImports(AnnotationMetadata metadata) {
- // System.out.println("================");
- // System.out.println("提示:"+metadata.getClassName());
- // System.out.println(metadata.hasAnnotation("org.springframework.context.annotation.Configuration"));
- // Map
attributes = metadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan"); - // System.out.println(attributes);
- // System.out.println("================");
-
- //各种条件的判定,判定完毕后,决定是否装在指定的bean
- boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
- if(flag){
- return new String[]{"com.hao.bean.Dog"};
- }
- return new String[]{"com.hao.bean.Cat"};
- }
- }
- @Configuration
- //@ComponentScan(basePackages = "com.hao")
- @Import(MyImportSelector.class)
- public class SpringConfig6 {
- }
测试
- public class App6 {
- public static void main(String[] args) {
- ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig6.class);
- String[] names = ctx.getBeanDefinitionNames();
- for (String name : names) {
- System.out.println(name);
- }
- System.out.println("----------------------");
- }
- }

spring中定义了一个叫做BeanDefinition的东西,它才是控制bean初始化加载的核心。BeanDefinition接口中给出了若干种方法,可以控制bean的相关属性。说个最简单的,创建的对象是单例还是非单例,在BeanDefinition中定义了scope属性就可以控制这个。如果你感觉方式六没有给你开放出足够的对bean的控制操作,那么方式七你值得拥有。我们可以通过定义一个类,然后实现ImportBeanDefinitionRegistrar接口的方式定义bean,并且还可以让你对bean的初始化进行更加细粒度的控制。
导入实现了ImportBeanDefinitionRegistrar接口的类,通过BeanDefinition的注册器注册实名bean,实现对 容器中bean的裁定,例如对现有bean的覆盖,进而达成不修改源代码的情况下更换实现的效果
- public class MyRegistrar implements ImportBeanDefinitionRegistrar {
- @Override
- public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
- //1.使用元数据去做判定
-
- BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
- registry.registerBeanDefinition("bookService",beanDefinition);
- }
- }
- @Import(MyRegistrar.class)
- public class SpringConfig7 {
- }
测试:
- public class App7 {
- public static void main(String[] args) {
- ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig7.class);
- String[] names = ctx.getBeanDefinitionNames();
- for (String name : names) {
- System.out.println(name);
- }
- System.out.println("----------------------");
- }
- }

上述七种方式都是在容器初始化过程中进行bean的加载或者声明,但是这里有一个bug。这么多种方式,它们之间如果有冲突怎么办?谁能有最终裁定权?这是个好问题,当某种类型的bean被接二连三的使用各种方式加载后,在你对所有加载方式的加载顺序没有完全理解清晰之前,你还真不知道最后谁说了算。
导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean, 可以实现对容器中bean的最终裁定
- public interface BookSerivce {
- void check();
- }
- @Service("bookService")
- public class BookServiceImpl1 implements BookSerivce {
- @Override
- public void check() {
- System.out.println("book service 1..");
- }
- }
- public class BookServiceImpl2 implements BookSerivce {
- @Override
- public void check() {
- System.out.println("book service 2....");
- }
- }
- public class BookServiceImpl3 implements BookSerivce {
- @Override
- public void check() {
- System.out.println("book service 3......");
- }
- }
- public class MyRegistrar2 implements ImportBeanDefinitionRegistrar {
- @Override
- public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
- //1.使用元数据去做判定
-
- BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition();
- registry.registerBeanDefinition("bookService",beanDefinition);
- }
- }
- public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
- @Override
- public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
- BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
- registry.registerBeanDefinition("bookService",beanDefinition);
- }
-
- @Override
- public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
-
- }
- }
- @Import({BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar2.class, MyRegistrar.class})
- public class SpringConfig8 {
- }
测试:
- public class App8 {
- public static void main(String[] args) {
- ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig8.class);
- BookSerivce bookService = ctx.getBean("bookService", BookSerivce.class);
- bookService.check();
- }
- }

bean的定义由前期xml配置逐步演化成注解配置,本质是一样的,都是通过反射机制加载类名后创建对象,对象就是spring管控的bean
@Import注解可以指定加载某一个类作为spring管控的bean,如果被加载的类中还具有@Bean相关的定义,会被一同加载
spring开放出了若干种可编程控制的bean的初始化方式,通过分支语句由固定的加载bean转成了可以选择bean是否加载或者选择加载哪一种bean
- AnnotationConfigApplicationContext调用register方法
- @Import导入ImportSelector接口
- @Import导入ImportBeanDefinitionRegistrar接口
- @Import导入BeanDefinitionRegistryPostProcessor接口
以ImportSelector为例
1.根据任意条件确认是否加载bean

需要导入springboot
使用@Conditional注解的派生注解设置各种组合条件控制bean的加载
格式:@ConditionalOn***
- //@Import(MyImportSelector.class)
- @Import(Mouse.class)
- public class SpringConfig {
-
- @Bean
- //@ConditionalOnClass(Mouse.class) 一般不使用这种方式
- //@ConditionalOnClass(name = "com.huangzx.bean.Mouse") //有Mouse时加载
- //@ConditionalOnMissingClass("com.huangzx.bean.Wolf") //没有Wolf时加载
-
- @ConditionalOnBean(name = "jerry") //有Mouse bean,且Mouse的名字是jerry时加载
- @ConditionalOnMissingClass("com.huangzx.bean.Dog") //有Mouse bean,且Mouse的名字是jerry时加载,且没有Dog时加载
-
- @ConditionalOnWebApplication //是web程序时加载
- public Cat tom(){
- return new Cat();
- }
- }

后续可以单独配置bean,当有某种环境时,加载某些bean
这里当有mysql数据库连接时,加载Druid数据源
- public class SpringConfig {
- @Bean
- @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
- public DruidDataSource dataSource(){
- DruidDataSource ds = new DruidDataSource();
- return ds;
- }
- }