依赖注入DI指的是Spring IoC容器对对象的依赖对象的处理过程,对象的依赖对象,说起来比较拗口,其实指的就是:如果一个对象A的某一属性为对象B,则对象B就是对象A的依赖对象,对象A创建的过程中也要创建对象B并注入到对象A,之后对象A才能正常工作。
Spring IoC可通过如下三种方式注入依赖对象:
- 构造器参数
- 工厂方法参数
- Setter方法
Spring DI因此也分为两种:基于构造器的DI和基于Setter的DI。
我们需要用代码来说明Spring两种方式DI的区别。
代码准备
创建一个Spring项目,Spring的任何版本都可以,在pom中加入依赖即可:
org.springframework
spring-context
5.2.8.RELEASE
org.springframework
spring-beans
5.2.8.RELEASE
创建两个基础的接口,以及他们的实现类,不需要包含任何业务逻辑,我们只是为了说明依赖注入的概念。
public interface IDependencyA {
public void test();
}
public interface IDependencyB {
public void testB();
}
@Service
public class DependencyA implements IDependencyA {
@Override
public void test(){
System.out.println("I am DependencyA test...");
}
}
@Service
public class DependencyB implements IDependencyB{
private IDependencyA dependencyA;
public static void main(String[] args) {
System.out.println("I am a new comer...hello world ");
}
@Override
public void testB(){
System.out.print("This is DependencyB's test and ...");
dependencyA.test();
}
public DependencyB(){
//this.dependencyA =springTest;
System.out.println("create DependencyB...");
}
}
其中类DependencyB包含了一个类型为IDependencyA的属性dependencyA,因此,DependencyB依赖于对象dependencyA。
在添加一个配置类,配置类啥也不干,仅指定包扫描路径,包扫描路径就是为了告诉Spring从什么地方加载bean:
@Configuration
@ComponentScan(value={"SpringTest"})
public class MyConfiguration {
}
还要简单约定一下,目前来看Spring项目使用注解的情况应该会多于使用xml配置的情况,因此我们的文章主要基于注解而非基于xml配置文件。除非某些特殊案例只能使用xml、无法使用注解替代的情况下,才给出xml文件的配置。
最后增添加一个启动类:
public class App {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
System.out.println("Now start to getbean...");
DependencyB dependencyB = applicationContext.getBean(DependencyB.class);
dependencyB.testB();
}
}
下面我们就用这个简单的例子来说明Spring不同的DI方式。
基于构造器的DI
第一步,我们啥也不干,直接运行启动类:
create DependencyB...
Now start to getbean...
This is DependencyB's test and ...Exception in thread "main" java.lang.NullPointerException
at SpringTest.DependencyB.testB(DependencyB.java:14)
at SpringTest.App.main(App.java:11)
DependencyB的依赖对象dependencyA没有创建,所以报了空指针异常。
我们并没有给Spring IoC任何DI的指示,所以DI没有工作,dependencyA没有注入到dependencyB中。
基于构造器的DI包含两种方式:
- 构造器参数
- 工厂方法参数
先来看第一种,通过构造方法参数注入,修改DependencyB的构造方法,增加一个IDependencyA的参数:
public DependencyB(IDependencyA dependencyA){
this.dependencyA =dependencyA;
System.out.println("create DependencyB...");
}
再次运行启动类:
create DependencyB...
Now start to getbean...
This is DependencyB's test and ...I am DependencyA test...
发现dependencyA已经通过构造器参数成功注入到dependencyB中。
另外一种基于构造器参数的DI是通过工厂方法,这种情况下使用xml配置会更加方便:
指定factory-method,Spring IoC会调用该方法创建bean,可以通过构造器参数constructor-arg指定依赖对象完成注入。
DependencyB需要增加静态工厂方法factoryMethod:
public static DependencyB factoryMethod(IDependencyA dependencyA){
DependencyB dependencyB = new DependencyB();
dependencyB.dependencyA =dependencyA;
System.out.println("create DependencyB...");
return dependencyB;
}
基于Setter的DI
Spring IoC通过调用对象的setter方法注入依赖对象的方式。
我们去掉DepedencyB的构造方法的参数,添加setDepencyB方法:
@Component
public class DependencyB implements IDependencyB{
private IDependencyA dependencyA;
public static void main(String[] args) {
System.out.println("I am a new comer...hello world ");
}
@Override
public void testB(){
System.out.print("This is DependencyB's test and ...");
dependencyA.test();
}
public void setDependencyA(IDependencyA dependencyA){
System.out.println("here you call set method...");
this.dependencyA=dependencyA;
}
public DependencyB(){
}
}
然后直接运行启动类:
Now start to getbean...
This is DependencyB's test and ...Exception in thread "main" java.lang.NullPointerException
at springTest.DependencyB.testB(DependencyB.java:14)
at springTest.App.main(App.java:11)
显然是不行的,因为我们并没有指定DependencyB的属性dependencyA需要注入,此时Spring IoC并不知道需要为DependencyB注入什么属性,所以我们需要显示指定一下(后面我们会知道Spring其实有自动装配的概念,不指定也可以)。
一种方式是通过注解指定,setDependencyA方法添加@Autowired注解:
@Autowired
public void setDependencyA(IDependencyA dependencyA){
System.out.println("here you call set method...");
this.dependencyA=dependencyA;
}
再次运行启动类,就OK了。@Autowired注解我们暂不分析,后面学习自动装配的时候会详细说。
另外一种,当然可以通过xml指定,所以我们需要在项目的resource目录下创建一个xml文件mySpring.xml:
在xml文件中通过property标签,以及ref指定了类之间的依赖关系。
我们还需要重新创建一个基于xml的启动类:
public class AppByXML {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("mySpring.xml");
System.out.println("Now start to getbean...");
DependencyB dependencyB = applicationContext.getBean(DependencyB.class);
dependencyB.testB();
}
}
运行启动类:
here you call set method...
Now start to getbean...
This is DependencyB's test and ...I am DependencyA test...
dependencyA被成功注入了dependencyB中了,从log中可以清晰的看到Spring是通过调用setter方法实现注入的。
这种情况下,setter方法必须是public的,否则Spring执行注入的时候会因为找不到setter方法而抛异常。
基于构造器的 or 基于Setter的 DI?
既然有基于构造器的和基于Setter的DI,那我们在项目中应该怎么选?
Spring官网说,Spring Team一般推荐使用基于构造器的DI,因为基于构造器的DI有个好处是当你创建对象后,依赖对象也一同准备好了,可以避免后续业务处理的错误。
但是对于一个相对复杂的类来说,依赖对象一大堆,构造器就会很丑陋,所以,其实项目中还是基于Setter、或者自动装配更好用。
另外,基于构造器和基于Setter的DI是可以结合使用的,对于强制要求的依赖对象可以放在构造器中,可以在编译阶段就发现依赖对象没有准备好的问题,避免运行期错误。
另外一种依赖 depend on
除了我们上面所说的,一个对象作为另外一个对象的属性这种类型的依赖之外,还有另外一种依赖:一个对象的运行是依赖于另外一个对象的但是另外一个对象并不是当前对象的属性。
这种情况可以使用@DependsOn注解或xml的 depend on标签:
配置很麻烦
你不觉得DI的配置、尤其是xml方式的配置,很麻烦吗?一个一个的类、其中的一个一个依赖都需要通过xml进行配置,尤其是当你需要修改一个类、引入新的依赖对象或者去掉一个依赖对象的时候,你不得不同时打开xml配置文件做相应的修改。
确实非常麻烦,但是Spring为你想到了一个很好的处理办法:自动装配。
下一篇文章学习。