自动装配原理…
[TOC]
@Autowired、@Qualifier、@Primary的自动装配 @Autowired注解 @Autowired注解可以对类成员 变量、方法和构造函数进行标注,完成自动装配的工作。@Autowired注解可以放在类、接口以及方法上。
在使用@Autowired注解之前,我们对一个bean配置属性时,是用如下XML配置文件的形式进行配置的。
1 <property name="属性名" value=" 属性值" />
下面我们来看一下@Autowired注解的源码,如下所示。
这儿对@Autowired注解说明一下:
@Autowired注解默认是优先按照类型去容器中找对应的组件,相当于是调用了如下这个方法:
1 applicationContext.getBean(类名.class);
若找到则就赋值。
如果找到多个相同类型的组件,那么是将属性名称作为组件的id,到IOC容器中进行查找,这时就相当于是调用了如下这个方法:
1 applicationContext.getBean("组件的id" );
@Qualifier注解 @Autowired是根据类型进行自动装配的,如果需要按名称进行装配,那么就需要配合@Qualifier注解来使用了。
下面我们来看一下@Qualifier注解的源码,如下所示。
@Primary注解 在Spring中使用注解时,常常会使用到@Autowired这个注解,它默认是根据类型Type来自动注入的。但有些特殊情况,对同一个接口而言,可能会有几种不同的实现类,而在默认只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先使用哪一个实现类。
下面我们来看一下@Primary注解的源码,如下所示。
自动装配 在进行项目实战之前,我们先来说说什么是Spring组件的自动装配。Spring组件的自动装配就是Spring利用依赖注入,也就是我们通常所说的DI,完成对IOC容器中各个组件的依赖关系赋值。
测试@Autowired注解 这里,我们以之前项目中创建的BookDao、BookService和BookController为例进行说明。
BookDao
1 2 3 4 5 @Repository public class BookDao { }
BookService
1 2 3 4 5 6 7 8 9 10 @Service public class BookService { @Autowired private BookDao bookDao; public void print () { System.out.println(bookDao); } }
BookController
1 2 3 4 5 6 @Controller public class BookController { @Autowired private BookService bookService; }
可以看到,我们在BookService中使用@Autowired注解注入了BookDao,在BookController中使用@Autowired注解注入了BookService。为了方便测试,我们可以在BookService类中生成一个toString()方法,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service public class BookService { @Autowired private BookDao bookDao; public void print () { System.out.println(bookDao); } @Override public String toString () { return "BookService [bookDao=" + bookDao + "]" ; } }
为了更好的看到演示效果,我们在项目的com.zsxfa.config包下创建一个配置类,例如MainConfigOfAutowired,如下所示。
1 2 3 4 @ComponentScan({"com.zsxfa.service", "com.zsxfa.dao", "com.zsxfa.controller"}) @Configuration public class MainConfigOfAutowired {}
接下来,我们便来测试一下上面的程序。在项目的src/test/java目录下的com.zsxfa.test包中创建一个单元测试类,例如IOCTest_Autowired,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 public class IOCTest_Autowired { @Test public void test01 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (MainConfigOfAutowired.class); BookService bookService = applicationContext.getBean(BookService.class); System.out.println(bookService); applicationContext.close(); } }
测试方法比较简单,这里我就不做过多说明了。然后,运行一下IOCTest_Autowired类中的test01()方法,得出的输出结果信息如下所示。
可以看到,输出了BookDao信息。
那么问题来了,我们在BookService类中使用@Autowired注解注入的BookDao(最后输出了该BookDao的信息),和我们直接在Spring IOC容器中获取的BookDao是不是同一个对象呢?
为了说明这一点,我们可以在IOCTest_Autowired类的test01()方法中添加获取BookDao对象的方法,并输出获取到的BookDao对象,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class IOCTest_Autowired { @Test public void test01 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (MainConfigOfAutowired.class); BookService bookService = applicationContext.getBean(BookService.class); System.out.println(bookService); BookDao bookDao = applicationContext.getBean(BookDao.class); System.out.println(bookDao); applicationContext.close(); } }
可以看到,我们在BookService类中使用@Autowired注解注入的BookDao对象和直接从IOC容器中获取的BookDao对象是同一个对象。
你可能会问了,如果在Spring容器中存在对多个BookDao对象,那么这时又该如何处理呢?
首先,为了更加直观的看到我们使用@Autowired注解装配的是哪个BookDao对象,我们得对BookDao类进行改造,为其加上一个lable字段,并为其赋一个默认值,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Repository public class BookDao { private String lable = "1" ; public String getLable () { return lable; } public void setLable (String lable) { this .lable = lable; } @Override public String toString () { return "BookDao [lable=" + lable + "]" ; } }
然后,我们就在MainConfigOfAutowired配置类中注入一个BookDao对象,并且显示指定该对象在IOC容器中的bean的名称为bookDao2,并还为该对象的lable字段赋值为2,如下所示。
1 2 3 4 5 6 7 8 9 10 11 @ComponentScan({"com.zsxfa.service", "com.zsxfa.dao", "com.zsxfa.controller"}) @Configuration public class MainConfigOfAutowired { @Bean("bookDao2") public BookDao bookDao () { BookDao bookDao = new BookDao (); bookDao.setLable("2" ); return bookDao; } }
目前,在我们的IOC容器中就会注入两个BookDao对象。那此时,**@Autowired注解到底装配的是哪个BookDao对象呢?**
接着,我们来运行一下IOCTest_Autowired类中的test01()方法,发现输出的结果信息如下所示。
可以看到,结果信息输出了lable=1
,这说明,**@Autowired注解默认是优先按照类型去容器中找对应的组件,找到就赋值;如果找到多个相同类型的组件,那么再将属性的名称作为组件的id,到IOC容器中进行查找。**
那我们如何让@Autowired注解装配bookDao2呢? 其实很简单,我们只须将BookService类中的bookDao属性的名称全部修改为bookDao2即可,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service public class BookService { @Autowired private BookDao bookDao2; public void print () { System.out.println(bookDao2); } @Override public String toString () { return "BookService [bookDao2=" + bookDao2 + "]" ; } }
此时,我们再运行IOCTest_Autowired类中的test01()方法,输出的结果信息如下所示。
测试@Qualifier注解 从测试@Autowired注解的结果来看,**@Autowired注解默认是优先按照类型去容器中找对应的组件,找到就赋值;如果找到多个相同类型的组件,那么再将属性的名称作为组件的id,到IOC容器中进行查找。**
如果IOC容器中存在多个相同类型的组件时,那么我们可不可以显示指定@Autowired注解装配哪个组件呢?有些小伙伴肯定会说:废话!你都这么问了,那肯定可以啊!没错,确实是可以的!此时,@Qualifier注解就派上用场了!
在之前的测试案例中,Eclipse控制台中输出了BookDao [lable=2]
,这说明@Autowired注解装配了bookDao2,那我们如何显示的让@Autowired注解装配bookDao呢?
比较简单,我们只需要在BookService类里面的bookDao2字段上添加@Qualifier注解,显示指定@Autowired注解装配bookDao即可,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class BookService { @Qualifier("bookDao") @Autowired private BookDao bookDao2; public void print () { System.out.println(bookDao2); } @Override public String toString () { return "BookService [bookDao2=" + bookDao2 + "]" ; } }
可以看到,此时尽管字段的名称为bookDao2,但是我们使用了@Qualifier注解显示指定了@Autowired注解装配bookDao对象,所以,最终的结果中输出了bookDao对象的信息。
测试容器中无组件的情况 如果IOC容器中无相应的组件,那么会发生什么情况呢?这时我们可以做这样一件事情,先注释掉BookDao类上的@Repository注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class BookDao { private String lable = "1" ; public String getLable () { return lable; } public void setLable (String lable) { this .lable = lable; } @Override public String toString () { return "BookDao [lable=" + lable + "]" ; } }
然后再注释掉MainConfigOfAutowired配置类中的bookDao()方法上的@Bean注解,如下所示。
1 2 3 4 5 6 7 8 9 10 11 @Configuration @ComponentScan({"com.meimeixia.service", "com.meimeixia.dao", "com.meimeixia.controller"}) public class MainConfigOfAutowired { public BookDao bookDao () { BookDao bookDao = new BookDao (); bookDao.setLable("2" ); return bookDao; } }
此时IOC容器中不再有任何BookDao对象了。
接着,我们再次运行IOCTest_Autowired类中的test01()方法,发现Eclipse控制台报了一个错误,如下。
1 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bookService' : Unsatisfied dependency expressed through field 'bookDao2' ; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.zsxfa.dao.BookDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org .springframework.beans.factory.annotation.Qualifier(value=bookDao), @org .springframework.beans.factory.annotation.Autowired(required=true )}
此时,Spring抛出了异常,未找到相应的bean对象,那我们能不能让Spring不报错呢?那肯定可以啊!抛出的异常信息中都给出了相应的提示。
1 {@org .springframework.beans.factory.annotation.Qualifier(value=bookDao), @org .springframework.beans.factory.annotation.Autowired(required=true )}
解决方案就是在BookService类的@Autowired注解里面添加一个属性required=false
,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class BookService { @Qualifier("bookDao") @Autowired(required=false) private BookDao bookDao2; public void print () { System.out.println(bookDao2); } @Override public String toString () { return "BookService [bookDao2=" + bookDao2 + "]" ; } }
加上required=false
这个玩意的意思就是说找到就装配,找不到就拉到,就别装配了。
此时,还需要将IOCTest_Autowired类的test01()方法中直接从IOC容器中获取BookDao对象的代码注释掉,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class IOCTest_Autowired { @Test public void test01 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (MainConfigOfAutowired.class); BookService bookService = applicationContext.getBean(BookService.class); System.out.println(bookService); applicationContext.close(); } }
紧接着,我们再次运行以上test01()方法,输出的结果信息如下所示。
可以看到,当为@Autowired注解添加属性required=false
后,即使IOC容器中没有对应的对象,Spring也不会抛出异常了。不过,此时装配的对象就为null了。
测试完成后,我们还得恢复原样,即再次为BookDao类添加@Repository注解,并且在MainConfigOfAutowired配置类中的bookDao()方法上添加@Bean注解,好方便进一步的测试。
测试@Primary注解 在Spring中,对同一个接口而言,可能会有几种不同的实现类,而默认只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先使用哪一个实现类。
如果IOC容器中相同类型的组件有多个,那么我们不可避免地就要来回用@Qualifier注解来指定要装配哪个组件,这还是比较麻烦的,Spring正是帮我们考虑到了这样一种情况,就提供了这样一个比较强大的注解,即@Primary。我们可以利用这个注解让Spring进行自动装配的时候,默认使用首选的bean。
说了这么多,下面我们就用一个小例子来测试一下@Primary注解。
首先,我们在MainConfigOfAutowired配置类的bookDao()方法上添加上@Primary注解,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration @ComponentScan({"com.zsxfa.service", "com.zsxfa.dao", "com.zsxfa.controller"}) public class MainConfigOfAutowired { @Primary @Bean("bookDao2") public BookDao bookDao () { BookDao bookDao = new BookDao (); bookDao.setLable("2" ); return bookDao; } }
注意:此时,我们需要注释掉BookService类中bookDao字段上的@Qualifier注解,这是因为@Qualifier注解为显示指定装配哪个组件,如果使用了@Qualifier注解,无论是否使用了@Primary注解,都会装配@Qualifier注解标注的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class BookService { @Autowired(required=false) private BookDao bookDao; public void print () { System.out.println(bookDao); } @Override public String toString () { return "BookService [bookDao=" + bookDao + "]" ; } }
设置完成后,我们再次运行IOCTest_Autowired类中的test01()方法,输出的结果信息如下所示。
可以看到,此时lable的值为2,这说明装配了MainConfigOfAutowired配置类中注入的bookDao2。
那我们非要装配bookDao,可不可以呢?当然可以了,我们只须使用@Qualifier(“bookDao”)来显示指定装配bookDao即可。也就是说如果是在没有明确指定的情况下,那么就装配优先级最高的首选的那个bean,如果是在明确指定了的情况下,那么自然就是装配指定的那个bean了。
@Resource注解和@Inject注解 @Resource注解 @Resource注解是Java规范里面的,也可以说它是JSR250规范里面定义的一个注解。该注解默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,那么默认取字段名将其作为组件的名称在IOC容器中进行查找,如果注解写在setter方法上,那么默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的一点是,如果name属性一旦指定,那么就只会按照名称进行装配。
我们先看一下@Resource注解的源码,如下所示。
@Inject注解 @Inject注解也是Java规范里面的,也可以说它是JSR330规范里面定义的一个注解。该注解默认是根据参数名去寻找bean注入,支持Spring的@Primary注解优先注入,@Inject注解还可以增加@Named注解指定要注入的bean。
我们先看一下@Inject注解的源码,如下所示。
温馨提示,要想使用@Inject注解,需要在项目的pom.xml文件中添加如下依赖,即导入javax.inject这个包。
1 2 3 4 5 <dependency > <groupId > javax.inject</groupId > <artifactId > javax.inject</artifactId > <version > 1</version > </dependency >
测试@Resource注解 首先,我们将项目中的BookService类标注在bookDao字段上的@Autowired注解和@Qualifier注解注释掉,然后添加上@Resource注解,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Service public class BookService { @Resource private BookDao bookDao; public void print () { System.out.println(bookDao); } @Override public String toString () { return "BookService [bookDao=" + bookDao + "]" ; } }
然后,我们运行一下IOCTest_Autowired类中的test01()方法,输出的结果信息如下所示。
可以看到,使用@Resource注解也能够自动装配组件,只不过此时自动装配的是lable为1的bookDao,而不是我们在MainConfigOfAutowired配置类中配置的优先装配的lable为2的bookDao。MainConfigOfAutowired配置类中配置的lable为2的bookDao如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration @ComponentScan({"com.meimeixia.service", "com.meimeixia.dao", "com.meimeixia.controller"}) public class MainConfigOfAutowired { @Primary @Bean("bookDao2") public BookDao bookDao () { BookDao bookDao = new BookDao (); bookDao.setLable("2" ); return bookDao; } }
这也进一步说明,**@Resource注解和@Autowired注解的功能是一样的,都能实现自动装配,只不过@Resource注解默认是按照组件名称(即属性的名称)进行装配的。虽然@Resource注解具备自动装配这一功能,但是它是不支持@Primary注解优先注入的功能的,而且也不能像@Autowired注解一样能添加required=false
属性。**
我们在使用@Resource注解时,可以通过@Resource注解的name属性显示指定要装配的组件的名称。例如,我们要想装配lable为2的bookDao,只需要为@Resource注解添加 name="bookDao2"
属性即可,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Service public class BookService { @Resource(name="bookDao2") private BookDao bookDao; public void print () { System.out.println(bookDao); } @Override public String toString () { return "BookService [bookDao=" + bookDao + "]" ; } }
接着,我们再次运行IOCTest_Autowired类中的test01()方法,输出的结果信息如下所示。
可以看到,此时输出了lable为2的bookDao,说明@Resource注解可以通过name属性显示指定要装配的bean。
测试@Inject注解 在BookService类中,将@Resource注解注释掉,添加@Inject注解,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Service public class BookService { @Inject private BookDao bookDao; public void print () { System.out.println(bookDao); } @Override public String toString () { return "BookService [bookDao=" + bookDao + "]" ; } }
修改完毕后,我们运行IOCTest_Autowired类中的test01()方法,输出的结果信息如下所示。
可以看到,使用@Inject注解默认输出的是lable为2的bookDao。这是因为@Inject注解和@Autowired注解一样,默认优先装配使用了@Primary注解标注的组件。
其实,这也进一步说明了,**@Inject注解和@Autowired注解的功能是一样的,都能实现自动装配,而且它俩都支持@Primary注解优先注入的功能。只不过,@Inject注解不能像@Autowired注解一样能添加required=false
属性,因为它里面没啥属性。**
@Resource和@Inject这俩注解与@Autowired注解的区别 不同点
@Autowired是Spring中的专有注解,而@Resource是Java中JSR250规范里面定义的一个注解,@Inject是Java中JSR330规范里面定义的一个注解
@Autowired支持参数required=false
,而@Resource和@Inject都不支持
@Autowired和@Inject支持@Primary注解优先注入,而@Resource不支持
@Autowired通过@Qualifier指定注入特定bean,@Resource可以通过参数name指定注入bean,而@Inject需要通过@Named注解指定注入bean
相同点 三种注解都可以实现bean的自动装配。
实现方法、构造器位置的自动装配 再谈@Autowired注解
我们通过@Autowired注解的源码可以看出,在@Autowired注解上标注有如下的注解信息。
1 @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
可以看出@Autowired注解不仅可以标注在字段上,而且还可以标注在构造方法、实例方法以及参数上。
案例准备 首先,我们在项目中新建一个Boss类,在Boss类中有一个Car类的引用,并且我们使用@Component注解将Dog类加载到IOC容器中,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Component public class Boss { private Car car; public Car getCar () { return car; } public void setCar (Car car) { this .car = car; } @Override public String toString () { return "Boss [car=" + car + "]" ; } }
注意,Car类上也要标注@Component注解,即它也要被加载到IOC容器中。
新建好以上Boss类之后,我们还需要在MainConfigOfAutowired配置类的@ComponentScan注解中进行配置,使其能够扫描com.meimeixia.bean包下的类,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 @ComponentScan({"com.zsxfa.service", "com.zsxfa.dao", "com.zsxfa.controller","com.zsxfa.bean"}) @Configuration public class MainConfigOfAutowired { @Primary @Bean("bookDao2") public BookDao bookDao () { BookDao bookDao = new BookDao (); bookDao.setLable("2" ); return bookDao; } }
此时,我们就可以直接在Boss类中的car字段上添加@Autowired注解,使其自动装配。
标注在实例方法上 我们可以将@Autowired注解标注在setter方法上,如下所示。
1 2 3 4 @Autowired public void setCar (Car car) { this .car = car; }
当@Autowired注解标注在方法上时,Spring容器在创建当前对象的时候,就会调用相应的方法为对象赋值。如果标注的方法存在参数时,那么方法使用的参数和自定义类型的值,需要从IOC容器中获取。
然后,我们将IOCTest_Autowired类的test01()方法中有关获取和打印BookService信息的代码注释掉,新增获取和打印Boss信息的代码,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class IOCTest_Autowired { @Test public void test01 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (MainConfigOfAutowired.class); Boss boss = applicationContext.getBean(Boss.class); System.out.println(boss); applicationContext.close(); } }
运行以上test01()方法进行测试,可以看到,结果信息中输出了如下一行信息。
说明已经获取到了car的信息,也就是说可以将@Autowired注解标注在方法上。
为了验证最终的输出结果是否是从IOC容器中获取的,我们可以在IOCTest_Autowired类的test01()方法中直接获取Car对象的信息,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class IOCTest_Autowired { @Test public void test01 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (MainConfigOfAutowired.class); Boss boss = applicationContext.getBean(Boss.class); System.out.println(boss); Car car = applicationContext.getBean(Car.class); System.out.println(car); applicationContext.close(); } }
我们再次运行以上test01()方法进行测试,可以在输出的结果信息中看到如下两行内容。
这已然说明在Boss类中通过@Autowired注解获取到的Car对象和直接从IOC容器中获取到Car对象是同一个对象。
标注在构造方法上 在上面的案例中,我们在Boss类上使用了@Component注解,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Component public class Boss { private Car car; public Car getCar () { return car; } @Autowired public void setCar (Car car) { this .car = car; } @Override public String toString () { return "Boss [car=" + car + "]" ; } }
此时,Spring会默认将该类加载进IOC容器中,IOC容器启动的时候默认会调用bean的无参构造器创建对象,然后再进行初始化、赋值等操作。
接下来,我们为Boss类添加一个有参构造方法,然后去除setCar()方法上的@Autowired注解,将@Autowired注解标注在有参构造方法上,并在构造方法中打印一条信息,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Component public class Boss { private Car car; @Autowired public Boss (Car car) { this .car = car; System.out.println("Boss...有参构造器" ); } public Car getCar () { return car; } public void setCar (Car car) { this .car = car; } @Override public String toString () { return "Boss [car=" + car + "]" ; } }
接着,我们运行IOCTest_Autowired类中的test01()方法进行测试,可以看到输出结果信息中存在如下一行信息。
说明IOC容器在启动的时候调用了Boss类的有参构造方法。并且还可以从输出的如下两行信息中看出,通过Boss类的toString()方法打印出的Car对象和直接从IOC容器中获取的Car对象是同一个对象。
使用@Autowired注解标注在构造方法上时,构造方法中的参数对象也是从IOC容器中获取的。
使用@Autowired注解标注在构造方法上时,如果组件中只有一个有参构造方法,那么这个有参构造方法上的@Autowired注解可以省略,并且参数位置的组件还是可以自动从IOC容器中获取。
标注在参数上 我们也可以将@Autowired注解标注在参数上,例如,在Boss类中我们将构造方法上的@Autowired注解标注在构造方法的参数上,如下所示。
1 2 3 4 public Boss (@Autowired Car car) { this .car = car; System.out.println("Boss...有参构造器" ); }
当然了,也可以将@Autowired注解标注在setter方法的参数上,如下所示。
1 2 3 public void setCar (@Autowired Car car) { this .car = car; }
最终的效果与标注在字段、实例方法和构造方法上的效果都是一样的。
结论:无论@Autowired注解是标注在字段上、实例方法上、构造方法上还是参数上,参数位置的组件都是从IOC容器中获取。
如果Spring的bean中只有一个有参构造方法,并且这个有参构造方法只有一个参数,这个参数还是IOC容器中的对象,当@Autowired注解标注在这个构造方法的参数上时,那么我们可以将其省略掉,如下所示。
1 2 3 4 public Boss ( Car car) { this .car = car; System.out.println("Boss...有参构造器" ); }
标注在方法位置 @Autowired注解可以标注在某个方法的位置上。这里,为了更好的演示效果,我们新建一个Color类,在Color类中有一个Car类型的成员变量,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Color { public Car car; public Car getCar () { return car; } public void setCar (Car car) { this .car = car; } @Override public String toString () { return "Color [car=" + car + "]" ; } }
然后,我们在MainConfigOfAutowired配置类中实例化Color类,如下所示。
1 2 3 4 5 @Bean public Color color () { Color color = new Color (); return color; }
接着,我们在IOCTest_Autowired类中再创建一个test02()测试方法,如下所示。
1 2 3 4 5 6 7 8 9 @Test public void test02 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (MainConfigOfAutowired.class); Color color = applicationContext.getBean(Color.class); System.out.println(color); applicationContext.close(); }
紧接着,运行以上test02()方法,发现在输出的结果信息中存在如下一行信息。
Color [car=null]
说明此时的Color对象中的Car对象为空。此时,我们可以将Car对象作为一个参数传递到MainConfigOfAutowired配置类的color()方法中,并且将该Car对象设置到Color对象中,如下所示。
1 2 3 4 5 6 @Bean public Color color (Car car) { Color color = new Color (); color .setCar(car); return color ; }
当然了,我们也可以使用@Autowired注解来标注color()方法中的car参数,就像下面这样。
1 2 3 4 5 6 @Bean public Color color (@Autowired Car car) { Color color = new Color (); color.setCar(car); return color; }
接下来,我们再次运行test02()方法,可以看到在输出的结果信息中存在如下一行信息。
说明Car对象被成功创建并设置到Color对象中了。
至此,我们可以得出结论:如果方法只有一个IOC容器中的对象作为参数,当@Autowired注解标注在这个方法的参数上时,我们可以将@Autowired注解省略掉。也就说@Bean注解标注的方法在创建对象的时候,方法参数的值是从IOC容器中获取的,此外,标注在这个方法的参数上的@Autowired注解可以省略。
其实,我们用到最多的还是把@Autowired注解标注在方法位置,即使用@Bean注解+方法参数这种形式,此时,该方法参数的值从IOC容器中获取,并且还可以默认不写@Autowired注解,因为效果都是一样的,都能实现自动装配!
Aware注入spring底层组件&原理 自定义的组件要想使用Spring容器底层的一些组件,比如ApplicationContext(IOC容器)、底层的BeanFactory等等,那么只需要让自定义组件实现XxxAware接口即可。此时,Spring在创建对象的时候,会调用XxxAware接口中定义的方法注入相关的组件。
XxxAware接口概览 其实,我们之前使用过XxxAware接口,例如,我们之前创建的Dog类,就实现了ApplicationContextAware接口,Dog类的源码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Component public class Dog implements ApplicationContextAware { private ApplicationContext applicationContext; public Dog () { System.out.println("dog constructor..." ); } @PostConstruct public void init () { System.out.println("dog...@PostConstruct..." ); } @PreDestroy public void destory () { System.out.println("dog...@PreDestroy..." ); } @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { this .applicationContext = applicationContext; } }
从以上Dog类的源码中可以看出,实现ApplicationContextAware接口的话,需要实现setApplicationContext()方法。在IOC容器启动并创建Dog对象时,Spring会调用setApplicationContext()方法,并且会将ApplicationContext对象传入到setApplicationContext()方法中,我们只需要在Dog类中定义一个ApplicationContext类型的成员变量来接收setApplicationContext()方法中的参数,那么便可以在Dog类的其他方法中使用ApplicationContext对象了。
其实,在Spring中,类似于ApplicationContextAware接口的设计有很多,本质上,Spring中形如XxxAware这样的接口都继承了Aware接口,我们来看下Aware接口的源码,如下所示。
可以看到,Aware接口是Spring 3.1版本中引入的接口,在Aware接口中,并未定义任何方法。
接下来,我们看看都有哪些接口继承了Aware接口,如下所示。
XxxAware接口案例 接下来,我们就挑选几个常用的XxxAware接口来简单的说明一下。
ApplicationContextAware接口使用的比较多,我们先来说说这个接口,通过ApplicationContextAware接口我们可以获取到IOC容器。
首先,我们创建一个Red类,它得实现ApplicationContextAware接口,并在实现的setApplicationContext()方法中将ApplicationContext输出,如下所示。
1 2 3 4 5 6 7 8 9 10 11 public class Red implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { System.out.println("传入的IOC:" + applicationContext); this .applicationContext = applicationContext; } }
其实,我们也可以让Red类同时实现几个XxxAware接口,例如,使Red类再实现一个BeanNameAware接口,我们可以通过BeanNameAware接口获取到当前bean在Spring容器中的名称,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Red implements ApplicationContextAware , BeanNameAware { private ApplicationContext applicationContext; @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { System.out.println("传入的IOC:" + applicationContext); this .applicationContext = applicationContext; } @Override public void setBeanName (String name) { System.out.println("当前bean的名字:" + name); } }
当然了,我们可以再让Red类实现一个EmbeddedValueResolverAware接口,我们通过EmbeddedValueResolverAware接口能够获取到String值解析器,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Red implements ApplicationContextAware , BeanNameAware, EmbeddedValueResolverAware { private ApplicationContext applicationContext; @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { System.out.println("传入的IOC:" + applicationContext); this .applicationContext = applicationContext; } @Override public void setBeanName (String name) { System.out.println("当前bean的名字:" + name); } @Override public void setEmbeddedValueResolver (StringValueResolver resolver) { String resolveStringValue = resolver.resolveStringValue("你好,${os.name},我的年龄是#{20*18}" ); System.out.println("解析的字符串:" + resolveStringValue); } }
IOC容器启动时会自动地将String值的解析器(即StringValueResolver)传递过来给我们用,咱们可以用它来解析一些字符串,解析哪些字符串呢?比如包含#{}
这样的字符串。我们可以看一下StringValueResolver类的源码,如下所示。
从描述中可以看出,它是用来帮我们解析那些String类型的值的,如果这个String类型的值里面有一些占位符,那么也会帮我们把这些占位符给解析出来,最后返回一个解析后的值。
接着,我们需要在Red类上标注@Component注解将该类添加到IOC容器中,如下所示。
1 2 @Component public class Red implements ApplicationContextAware , BeanNameAware, EmbeddedValueResolverAware {
最后,运行IOCTest_Autowired类中的test02()方法,输出的结果信息如下所示。
在自定义的组件中获取到的IOC容器和测试方法中获取到的IOC容器是不是同一个东东呢?带着这样一个疑问,不妨试试运行一下以下test02()方法。
1 2 3 4 5 6 7 8 9 10 11 @Test public void test02 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (MainConfigOfAutowired.class); Color color = applicationContext.getBean(Color.class); System.out.println(color); System.out.println(applicationContext); applicationContext.close(); }
可以看到输出了如下所示的结果信息。
这已然说明了在咱们自定义的组件中获取到的IOC容器和测试方法中获取到的IOC容器是同一个
XxxAware原理 XxxAware接口的底层原理是由XxxAwareProcessor实现类实现的,也就是说每一个XxxAware接口都有它自己对应的XxxAwareProcessor实现类。 例如,我们这里以ApplicationContextAware接口为例,ApplicationContextAware接口的底层原理就是由ApplicationContextAwareProcessor类实现的。从ApplicationContextAwareProcessor类的源码可以看出,其实现了BeanPostProcessor接口,本质上是一个后置处理器。
接下来,我们就以分析ApplicationContextAware接口的原理为例,看看Spring是怎么将ApplicationContext对象注入到Red类中的。
首先,我们在Red类的setApplicationContext()方法上打一个断点,如下所示。
然后,我们以debug的方式来运行IOCTest_Autowired类中的test02()方法。
这里,我们可以看到,实际上ApplicationContext对象已经注入到Red类的setApplicationContext()方法中了。
接着,我们在IDEA的方法调用栈中找到postProcessBeforeInitialization()方法并鼠标单击它,如下所示,此时,自动定位到了postProcessBeforeInitialization()方法中。
其实,postProcessBeforeInitialization()方法所在的类就是ApplicationContextAwareProcessor。postProcessBeforeInitialization()方法的逻辑还算比较简单。
紧接着,我们来看下在postProcessBeforeInitialization()方法中调用的invokeAwareInterfaces()方法,如下所示。
@Profile 在实际的企业开发环境中,往往都会将环境分为开发环境、测试环境和生产环境,并且每个环境基本上都是互相隔离的,也就是说,开发环境、测试环境和生产环境它们之间是互不相通的。在以前的开发过程中,如果开发人员完成相应的功能模块并通过单元测试后,那么他会通过手动修改配置文件的形式,将项目的配置修改成测试环境,发布到测试环境中进行测试。测试通过后,再将配置修改为生产环境,发布到生产环境中。这样手动修改配置的方式,不仅增加了开发和运维的工作量,而且总是手工修改各项配置文件会很容易出问题。那么,有没有什么方式可以解决这些问题呢?答案是:有!通过@Profile注解就可以完全做到这点。
@Profile注解概述 在容器中如果存在同一类型的多个组件,那么可以使用@Profile注解标识要获取的是哪一个bean。也可以说@Profile注解是Spring为我们提供的可以根据当前环境,动态地激活和切换一系列组件的功能。这个功能在不同的环境使用不同的变量的情景下特别有用,例如,开发环境、测试环境、生产环境使用不同的数据源,在不改变代码的情况下,可以使用这个注解来动态地切换要连接的数据库。
接下来,我们来看下@Profile注解的源码,如下所示。
从其源码中我们可以得出如下三点结论:
@Profile注解不仅可以标注在方法上,也可以标注在配置类上。
如果@Profile注解标注在配置类上,那么只有是在指定的环境的时候,整个配置类里面的所有配置才会生效。
如果一个bean上没有使用@Profile注解进行标注,那么这个bean在任何环境下都会被注册到IOC容器中,当然了,前提是在整个配置类生效的情况下。
第一点很容易看出,勿须再说,后面两点如果你要是初次认识@Profile注解的话,那么是肯定看不出来的,得通过下面的讲解才能知道。
实战案例 接下来,我们就一起来看一个案例,即使用@Profile注解实现开发、测试和生产环境的配置和切换。这里,我们以开发过程中要用到的数据源为例。我们希望在开发环境中,数据源是连向A数据库的;在测试环境中,数据源是连向B数据库的,而且在这一过程中,测试人员压根就不需要改动任何代码;最终项目上线之后,数据源连向C数据库,而且最重要的一点是在整个过程中,我们不希望改动大量的代码,而实现数据源的切换。
环境搭建 首先,我们需要在pom.xml文件中添加c3p0数据源和MySQL驱动的依赖,如下所示。
1 2 3 4 5 6 7 8 9 10 <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9 .1 .2 </version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1 .44 </version> </dependency>
添加完以上依赖之后,我们还得在项目中新建一个配置类,例如MainConfigOfProfile,并在该配置类中模拟开发、测试、生产环境的数据源,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Configuration public class MainConfigOfProfile { @Bean("testDataSource") public DataSource dataSourceTest () throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser("root" ); dataSource.setPassword("123456" ); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test" ); dataSource.setDriverClass("com.mysql.jdbc.Driver" ); return dataSource; } @Bean("devDataSource") public DataSource dataSourceDev () throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser("root" ); dataSource.setPassword("123456" ); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud" ); dataSource.setDriverClass("com.mysql.jdbc.Driver" ); return dataSource; } @Bean("prodDataSource") public DataSource dataSourceProd () throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser("root" ); dataSource.setPassword("123456" ); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515" ); dataSource.setDriverClass("com.mysql.jdbc.Driver" ); return dataSource; } }
该配置类这样写,是一点儿问题都没有的,但你有没有想过这一点,在真实项目开发中,那些数据库连接的相关信息,例如用户名、密码以及MySQL数据库驱动类的全名,这些都是要抽取在一个配置文件中的。你想一想,是不是这么一回事啊!
因此,我们需要在项目的src/main/resources目录下新建一个配置文件,例如dbconfig.properties,在其中写上数据库连接的相关信息,如下所示。
1 2 3 db.user=root db.password=123456 db.driverClass=com.mysql.jdbc.Driver
那么如何在MainConfigOfProfile配置类中获取以上配置文件中的值呢?
该MainConfigOfProfile配置类实现了一个EmbeddedValueResolverAware接口,我们通过该接口能够获取到String值解析器。也就是说,IOC容器启动时会自动地将String值的解析器(即StringValueResolver)传递过来给我们用,咱们可以用它来解析一些字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @PropertySource("classpath:/dbconfig.properties") @Configuration public class MainConfigOfProfile implements EmbeddedValueResolverAware { @Value("${db.user}") private String user; private StringValueResolver valueResolver; private String dirverClass; @Bean("testDataSource") public DataSource dataSourceTest (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test" ); dataSource.setDriverClass("com.mysql.jdbc.Driver" ); return dataSource; } @Bean("devDataSource") public DataSource dataSourceDev (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud" ); dataSource.setDriverClass(dirverClass); return dataSource; } @Bean("prodDataSource") public DataSource dataSourceProd (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515" ); dataSource.setDriverClass(dirverClass); return dataSource; } @Override public void setEmbeddedValueResolver (StringValueResolver resolver) { this .valueResolver = resolver; dirverClass = valueResolver.resolveStringValue("${db.driverClass}" ); } }
其实,这个配置类相对来说还算是比较简单的,其中使用@Bean("devDataSource")
注解标注的是开发环境使用的数据源;使用@Bean("testDataSource")
注解标注的是测试环境使用的数据源;使用@Bean("prodDataSource")
注解标注的是生产环境使用的数据源。
接着,我们创建一个单元测试类,例如IOCTest_Profile,并在该类中新建一个test01()方法来进行测试,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class IOCTest_Profile { @Test public void test01 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (MainConfigOfProfile.class); String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class); for (String name : namesForType) { System.out.println(name); } applicationContext.close(); } }
最后,运行以上test01()方法,输出的结果信息如下所示。
可以看到三种不同的数据源成功注册到了IOC容器中,说明我们的环境搭建成功了。
根据环境注册bean 我们成功搭建环境之后,接下来,就是要实现根据不同的环境来向IOC容器中注册相应的bean了。也就是说,我们要实现在开发环境注册开发环境下使用的数据源;在测试环境注册测试环境下使用的数据源;在生产环境注册生产环境下使用的数据源。此时,@Profile注解就显示出其强大的特性了。
我们在MainConfigOfProfile配置类中为每个数据源添加@Profile注解标识,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @PropertySource("classpath:/dbconfig.properties") @Configuration public class MainConfigOfProfile implements EmbeddedValueResolverAware { @Value("${db.user}") private String user; private StringValueResolver valueResolver; private String dirverClass; @Profile("test") @Bean("testDataSource") public DataSource dataSourceTest (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test" ); dataSource.setDriverClass("com.mysql.jdbc.Driver" ); return dataSource; } @Profile("dev") @Bean("devDataSource") public DataSource dataSourceDev (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud" ); dataSource.setDriverClass(dirverClass); return dataSource; } @Profile("prod") @Bean("prodDataSource") public DataSource dataSourceProd (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515" ); dataSource.setDriverClass(dirverClass); return dataSource; } @Override public void setEmbeddedValueResolver (StringValueResolver resolver) { this .valueResolver = resolver; dirverClass = valueResolver.resolveStringValue("${db.driverClass}" ); } }
可以看到,我们使用@Profile("dev")
注解来标识在开发环境下注册devDataSource;使用@Profile("test")
注解来标识在测试环境下注册testDataSource;使用@Profile("prod")
注解来标识在生产环境下注册prodDataDource。
此时,我们运行IOCTest_Profile类中的test01()方法,发现IDEA控制台并未输出任何结果信息。 说明我们为不同的数据源添加@Profile注解后,默认是不会向IOC容器中注册bean的,需要我们根据环境显示指定向IOC容器中注册相应的bean。
换句话说,通过@Profile注解加了环境标识的bean,只有这个环境被激活的时候,相应的bean才会被注册到IOC容器中。
如果我们需要一个默认的环境,那么该怎么办呢?此时,我们可以通过@Profile("default")
注解来标识一个默认的环境,例如,我们将devDataSource环境标识为默认环境,如下所示。
1 2 3 4 5 6 7 8 9 10 11 @Profile("default") @Bean("devDataSource") public DataSource dataSourceDev (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud" ); dataSource.setDriverClass(dirverClass); return dataSource; }
此时,我们运行IOCTest_Profile类中的test01()方法,输出的结果信息:
devDataSource
可以看到,我们在devDataSource数据源上使用@Profile("default")
注解将其设置为默认的数据源,运行测试方法时IDEA控制台会输出devDataSource。
接下来,我们将devDataSource数据源上的@Profile("default")
注解还原成@Profile("dev")
注解,重新标识它为一个开发环境下注册的数据源,好方便下面的测试。
那么,我们如何根据不同的环境来注册相应的bean呢?例如,我们想在程序运行的时候,将其切换到测试环境下。
第一种方式就是根据命令行参数来确定环境,我们在运行程序的时候可以添加相应的命令行参数。例如,如果我们现在的环境是测试环境,那么可以在运行程序的时候添加如下命令行参数。
-Dspring.profiles.active=test
此时,点击Run
按钮运行IOCTest_Profile类中的test01()方法,输出的结果信息如下所示。
testDataSource
第二种方式就是通过写代码的方式来激活某种环境,其实主要是通过AnnotationConfigApplicationContext类的无参构造方法来实现,具体步骤如下:
在bean上加@Profile注解,其value属性值为环境标识,可以自定义
使用AnnotationConfigApplicationContext类的无参构造方法创建容器
设置容器环境,其值为第1步设置的环境标识
设置容器的配置类
刷新容器
温馨提示:2、4、5步其实是AnnotationConfigApplicationContext类中带参构造方法的步骤,以上这几个步骤相当于是把其带参构造方法拆开,在其中插入一条语句设置容器环境,这些我们可以在AnnotationConfigApplicationContext类的带参构造方法中看到,如下所示。
好了,我们要开始正式编写代码来激活某种环境了。我们先在程序中调用AnnotationConfigApplicationContext类的无参构造方法来创建一个IOC容器,然后在容器进行初始化之前,为其设置相应的环境,接着再为容器设置主配置类,最后刷新一下容器。例如,我们将IOC容器设置为测试环境,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void test02 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (); applicationContext.getEnvironment().setActiveProfiles("test" ); applicationContext.register(MainConfigOfProfile.class); applicationContext.refresh(); String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class); for (String name : namesForType) { System.out.println(name); } applicationContext.close(); }
此时,我们运行以上test02()方法,输出的结果信息如下所示。
testDataSource
可以看到,IDEA控制台输出了testDataSource,说明我们成功将IOC容器的环境设置为了测试环境。
如果此时测试环境里面还有一些其他的组件,比如Yellow,
1 2 3 4 5 @Profile("test") @Bean public Yellow yellow () { return new Yellow (); }
那么在测试环境被激活的情况下,测试环境下的所有bean都会被注册到IOC容器中。如果你要是不信的话,那么你可以试着修改一下IOCTest_Profile类中的test02()方法,即在其中获取Yellow组件并打印看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void test02 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (); applicationContext.getEnvironment().setActiveProfiles("test" ); applicationContext.register(MainConfigOfProfile.class); applicationContext.refresh(); String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class); for (String name : namesForType) { System.out.println(name); } Yellow yellow = applicationContext.getBean(Yellow.class); System.out.println(yellow); applicationContext.close(); }
运行以上test02()方法,你将会看到如下所示的结果信息。
testDataSource com.zsxfa.bean.Yellow@50378a4
这佐证了如果测试环境被激活,那么测试环境下的所有bean都会被注册到IOC容器中的这一结论。
@Profile注解不仅可以标注在方法上,也可以标注在配置类上。如果标注在配置类上,那么只有是在指定的环境的时候,整个配置类里面的所有配置才会生效。例如,我们在MainConfigOfProfile配置类上标注上@Profile("dev")
注解,如下所示。
1 2 3 4 5 6 @Profile("dev") @PropertySource("classpath:/dbconfig.properties") @Configuration public class MainConfigOfProfile implements EmbeddedValueResolverAware { }
然后,我们来运行IOCTest_Profile类中的test02()方法,在运行该方法之前,记得要把获取Yellow组件并打印的两行代码给注释掉,要不然运行test02()方法之后,Eclipse控制台就会报错。这时,咱们再来运行test02()方法,会发现IDEA控制台中并未输出任何信息。
这是因为我们在test02()方法中指定了当前的环境为测试环境,而MainConfigOfProfile配置类上标注的注解为@Profile("dev")
,说明该配置类中的所有配置只有在开发环境下才会生效。所以,此时没有任何数据源注册到IOC容器中,自然IDEA控制台中就不会输出任何信息了。
还记得在一开头就说过,如果一个bean上没有使用@Profile注解进行标注,那么这个bean在任何环境下都会被注册到IOC容器中 吗?现在咱们就来验证这一点。
首先,我们要将MainConfigOfProfile配置类上标注的@Profile("dev")
注解和yellow方法上的@Profile("test")
给注释掉,好方便接下来的测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 @PropertySource("classpath:/dbconfig.properties") @Configuration public class MainConfigOfProfile implements EmbeddedValueResolverAware { @Value("${db.user}") private String user; private StringValueResolver valueResolver; private String dirverClass; @Bean public Yellow yellow () { return new Yellow (); } @Profile("test") @Bean("testDataSource") public DataSource dataSourceTest (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test" ); dataSource.setDriverClass(dirverClass); return dataSource; } @Profile("dev") @Bean("devDataSource") public DataSource dataSourceDev (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud" ); dataSource.setDriverClass(dirverClass); return dataSource; } @Profile("prod") @Bean("prodDataSource") public DataSource dataSourceProd (@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515" ); dataSource.setDriverClass(dirverClass); return dataSource; } @Override public void setEmbeddedValueResolver (StringValueResolver resolver) { this .valueResolver = resolver; dirverClass = valueResolver.resolveStringValue("${db.driverClass}" ); } }
接着,修改一下IOCTest_Profile类中的test02()方法,即放开获取Yellow组件并打印的两行代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Test public void test02 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (); applicationContext.getEnvironment().setActiveProfiles("test" ); applicationContext.register(MainConfigOfProfile.class); applicationContext.refresh(); String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class); for (String name : namesForType) { System.out.println(name); } Yellow yellow = applicationContext.getBean(Yellow.class); System.out.println(yellow); applicationContext.close(); }
可以看到,当前的环境指定为了开发环境,那么此时Yellow这个组件会被注册到IOC容器中吗?
紧接着,运行IOCTest_Profile类中的test02()方法,输出的结果信息如下所示。
testDataSource com.zsxfa.bean.Yellow@50378a4
从以上输出结果中可以看到,Yellow组件上并没有使用@Profile注解进行标注,但是它在开发环境下被注册到IOC容器中了。
如果此时将当前的环境指定为生产环境,那么Yellow这个组件还会被注册到IOC容器中吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Test public void test02 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (); applicationContext.getEnvironment().setActiveProfiles("prod" ); applicationContext.register(MainConfigOfProfile.class); applicationContext.refresh(); String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class); for (String name : namesForType) { System.out.println(name); } Yellow yellow = applicationContext.getBean(Yellow.class); System.out.println(yellow); applicationContext.close(); }
运行以上test02()方法,发现输出的结果信息如下所示。
prodDataSource com.zsxfa.bean.Yellow@50378a4
这进一步说明了,虽然Yellow组件上并没有使用@Profile注解进行标注,但是它也在生产环境下被注册到IOC容器中了。
至此,如果一个bean上没有使用@Profile注解进行标注,那么这个bean在任何环境下都会被注册到IOC容器中 这一结论就得到完美证明了。