上篇博文讲Spring的IOC容器时说道,虽然容器功能强大,但容器自己只是个空壳,须要咱们主动放入装配对象,并告诉它对象之间的协做关系,而后容器才能按照咱们的指示发挥它的魔力,完成装配bean的使命。这里,咱们把Spring建立应用对象之间的协做关系的行为成为装配。Spring提供了不少装配bean的方式供咱们在开发中选择,咱们经常使用到的有三种装配机制:自动装配、Java注解和XML配置。一般咱们将第一种称为隐式的装配机制,后面两种为显示的装配机制。实际应用中,基于便利性考虑,首选的确定是隐式的自动化装配机制,只有当须要注入的bean的源码不是由本身的程序来维护,而是引入第三方的应用组件的时候,才考虑显示的方式装配bean。固然,各类装配方式在实际应用中是能够自由选择搭配的,编码过程当中也没必要拘泥哪种,适用就好。本篇博文先来说述隐式的装配机制——bean的自动化装配。
spring
你必定很好奇Spring是怎么来实现其自动化装配机制的,其实Spring主要经过下面两个方面来实现:编程
下面,咱们分别来看看Spring如何经过组件扫描和自动装配来为咱们的应用程序自动化的装配bean。咱们先定义一个汽车接口:数组
1 package spring.facade; 2 3 public interface Car { 4 void drive(); 5 }
组件扫描的要义在于经过扫描控制,让Spring自动的去发现应用程序中的bean。不过程序中的对象那么多,Spring怎么知道哪些对象是须要它去管理建立的呢?这就涉及到Spring的一个组件注解——@Component,被该注解标注的类即为Spring的组件类,Spring容器加载过程当中会自动的为该类建立bean(PS:实际上Spring的组件注解按照语义化的分类还有@Controller @Repository @Service等等,分别做用于控制层、持久层和业务层,此处仅是举例演示,不作区分讲解)。因此,咱们能够将接口的一个实现标注上该注解,代表实现类是要被Spring建立实例的——
安全
1 package spring.impl; 2 3 import org.springframework.stereotype.Component; 4 import spring.facade.Car; 5 6 @Component 7 public class QQCar implements Car { 8 @Override 9 public void drive() { 10 System.out.println("开QQ车"); 11 } 12 }
不过,Spring的注解扫描默认是不开启的,因此咱们还须要显示的配置注解启动。这里一样有两种方式,Java注解和XML的方式,咱们分别展现出来——app
Java配置类CarConfig :ide
package spring.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @ComponentScan public class CarConfig { }
XML配置文件applicationContext.xml:函数
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--启动注解扫描--> <context:component-scan base-package="spring"/> </beans>
接下来咱们编写测试类,看看Spring是否是自动的去发现了咱们注解为组件的bean并为咱们建立了对象——测试
1 package spring.test; 2 3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.test.context.ContextConfiguration; 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 import spring.config.CarConfig; 9 import spring.impl.QQCar; 10 import static org.junit.Assert.assertNotNull; 11 12 /** 13 * 注解释义: 14 * @RunWith(SpringJUnit4ClassRunner.class) 测试在Spring环境中运行 15 * @ContextConfiguration 上下文配置注解,指定配置文件(Java类或XML文件)的位置 16 */ 17 @RunWith(SpringJUnit4ClassRunner.class) 18 //@ContextConfiguration(classes = CarConfig.class) //加载Java配置类的方式 19 @ContextConfiguration(locations = "classpath:resource/applicationContext.xml") //加载XML配置的方式 20 public class CarTest { 21 @Autowired 22 private QQCar car ; 23 24 @Test 25 public void carTest(){ 26 assertNotNull(car); 27 } 28 }
虽然如今的编程趋势是愈来愈多的使用Java注解的方式,可是上面的测试你会发现,经过XML注解的方式可以测试成功,而Java注解的方式倒是失败的,测试会抛出NoSuchBeanDefinitionException的异常,表示没有QQCar的组件定义,也就是Spring没有发现它,Why? 缘由也很简单,那就是基于Java注解的方式启动的注解扫描默认状况下只能扫描配置类所在的包以及其的子包,若是要明确扫描其它包中的组件,须要在启动扫描的注解 @ComponetScan 中显示的注明,如改为 @ComponentScan("spring.impl"),上诉的测试就能经过了。若是有多个包要扫描,能够这样配置:@ComponentScan(basePackages = {"spring.impl","spring.test"}) 不过这样字符串的表示方式是类型不安全的,并且写死包名的方式不利于代码重构,咱们能够指定包中所含的类或接口来指定要扫描的包,因而能够这样标注: @ComponentScan(basePackageClasses = QQCar.class) ,多个包一样能够用{}来以数组形式的表示。不过这样对重构依然不友好,最好的方式就是在要扫描的包中定义一个空标接口,该接口仅仅用来指定包扫描的范围,如此将重构的影响降到最低。this
前文的讲述只是阐明如何将一个类定义成Spring的组件并启动Spring的组件扫描,并且咱们已经经过测试证明,Spring确实扫描到了咱们指定的组件类并为咱们建立了对象。不过,建立的对象只是独立的存在,并无和其余对象产生依赖协做;实际应用中,对象之间的依赖协做是再常见不过了,而要将Spring经过组件扫描为咱们建立的对象根据实际业务创建起相互的依赖协做,就须要利用Spring的自动装配。便于演示,咱们再定义一个Man类,Man的工做就是开车,咱们先经过构造器注入的方式来知足依赖,看Spring是否会给咱们自动注入咱们须要的Car的实例对象——编码
package spring.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import spring.facade.Car; @Component public class Man { private Car car; public Man() { } @Autowired public Man(QQCar car) { this.car = car; } public void work() { car.drive(); } }
测试方法——
1 package spring.test; 2 3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.test.context.ContextConfiguration; 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 import spring.config.CarConfig; 9 import spring.impl.Man; 10 11 @RunWith(SpringJUnit4ClassRunner.class) 12 @ContextConfiguration(classes = CarConfig.class) 13 public class CarTest { 14 15 @Autowired 16 Man man; 17 18 @Test 19 public void carTest() { 20 man.work(); 21 } 22 }
如以上代码,测试固然是成功的,在测试类中,Man做为组件类被Spring扫描并建立了一个对象实例,该实例调用work方法的时候,须要Car的实例对象,而咱们在有参构造函数上经过 @Autowired 注解代表了对象的依赖关系,程序运行过程当中,Spring会自动为咱们注入Car的实例对象来知足对象依赖,这就是自动装配的精要所在。实际上,不仅是构造器上能够用 @Autowired 注解,在属性的Setter方法上,甚至普通的方法上,均可以用@Autowired 注解来知足对象之间的依赖,实现自动注入的功能——
1 package spring.impl; 2 3 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.stereotype.Component; 6 import spring.facade.Car; 7 8 @Component 9 public class Man { 10 private Car car; 11 12 public Man() { 13 } 14 //构造器实现自动装配 15 // @Autowired 16 public Man(QQCar car) { 17 this.car = car; 18 } 19 20 //Setter方法实现自动装配 21 // @Autowired 22 public void setCar(QQCar car) { 23 this.car = car; 24 } 25 //普通方法实现自动装配 26 @Autowired 27 public void insertCar(QQCar car) { 28 this.car = car; 29 } 30 31 public void work() { 32 car.drive(); 33 } 34 }
咱们将Man类中添加不一样的方法测试,依然是能够成功的。不过有一点要注意,在非构造器实现自动装配的时候,虽然咱们没有本身new对象,但Spring建立实例会经过Man的默认的构造器,此时的Man类中若是定义了有参构造器,就必定要把默认构造器构造出来,否则会抛无默认构造器的异常,记住:必定养成类中写默认构造器的习惯,便于扩展。
若是你足够细心,你会发现博主上面知足自动装配的测试代码中,注入的Car并无采用多态的写法,代码显得很低级。其实我是为了测试经过,故意注入了具体的实现,实际业务中固然不会这么局限的去写代码。由于博主Car的接口还有一个奔驰车的实现类BenzCar,若是用多态的写法,自动装配会有产生歧义性问题,会抛 NoUniqueBeanDefinitionException 异常。那么,面对这种歧义性,如何去解决呢?你必定知道Spring容器管理的每一个bean都会有一个ID做为惟一标识,在上面的示例中,咱们描述QQCar类为Spring的组件的时候并无明确的设置ID,可是Spring默认会将组件类的类名首字母小写来做为bean的ID,而咱们也可根据咱们本身的业务须要自定义ID标识——
package spring.impl; import org.springframework.stereotype.Component; import spring.facade.Car; //这里指定 chenbenbuyi 为组件的ID @Component("chenbenbuyi") public class QQCar implements Car { @Override public void drive() { System.out.println("开QQ车"); } }
但是测试发现,这并无解决接口参数在自动装配时的歧义性问题,由于在组件上自定义ID是一种后发行为,当你让Spring在装配阶段从多个接口实现中选择要自动注入的对象实例时,Spring没法选择——就比如你只跟我说你要开一辆车,每辆车也都有惟一的车牌号,但我仍是不知道你要开什么车。怎么办呢?这里有多种解决方案,咱们能够经过 @Primary注解将咱们明确须要自动注入的实现类标注为首选的bean,就想这样——
1 package spring.impl; 2 3 import org.springframework.context.annotation.Primary; 4 import org.springframework.stereotype.Component; 5 import spring.facade.Car; 6 7 @Component 8 @Primary 9 public class BenzCar implements Car { 10 @Override 11 public void drive() { 12 System.out.println("开奔驰车"); 13 } 14 }
当自动装配的时候,Spring面对歧义性时,会优先选择被标注为首选的bean进行自动注入。固然,咱们还能够采用限定符注解,在使用@Autowired 完成自动装配的时候限定只让某个bean做为自动注入的bean——
1 package spring.impl; 2 3 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.beans.factory.annotation.Qualifier; 6 import org.springframework.stereotype.Component; 7 import spring.facade.Car; 8 9 @Component 10 public class Man { 11 private Car car; 12 13 public Man() { 14 } 15 //普通方法实现自动装配 16 @Autowired 17 @Qualifier("chenbenbuyi") //限定ID为 chenbenbuyi 的bean被装配进来 18 public void insertCar(Car car) { 19 this.car = car; 20 } 21 22 public void work() { 23 car.drive(); 24 } 25 }
自此,关于Spring的自动装配就阐述得差很少了,下一节系列文章会接着讲解Spring的另外两种经常使用的装配机制——Java注解和XML配置。博文所述皆为原创,如要转载,请注明出处;若是阐述得不恰当的地方,欢迎指教,不胜感激。