Spring源码解析之基础应用(一)
基础应用
在学习spring源码之前,我们需要将spring中的一些基础概念弄清,比如:@Component、@Autowired、@Resource、@DependsOn、@Lookup、后置处理器……等。有些是大家平常就在用的注解,诸如:@Component、@Autowired、@Resource;有些则是大家不常用的,诸如:@DependsOn、@Lookup、后置处理器。当然,我们需要学习的spring基础概念不止之前说的这些,笔者将在三到四章的范围内和大家一起学习理解这些基本概念,然后和大家一起向着spring源码前进。
IOC和DI
首先,有有一个老生常谈的问题:IOC(控制翻转,Inversion of Control)和DI(依赖注入,Dependency Injection)的区别是什么?我们先来看官网的一段解释:
IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.
这段文字大致的意思是:控制反转(IoC)也称为依赖注入(DI),是一个定义对象依赖的过程,对象只和构造参数,工厂方法参数,对象实例属性或工厂方法返回相关。容器在创建这些 bean 的时候注入这些依赖。这个过程是一个反向的过程,所以命名为依赖反转,对象实例的创建由其提供的构造方法或服务定位机制来实现。
上面这句话有点抽象,但我们至少能提取到一点,就是IOC即DI。下面笔者来说下对这句话的愚见:在spring中,我们要创建OrderService和UserService这些的bean,一般都是在这两个类上添加@Component注解,spring在扫描到这两个类时,会帮我们实例化,原本实例化OrderService和UserService的工作从程序员转交到spring,这种行为称为控制反转。如果OrderService类中依赖UserService,并加上@Autowired注解,spring会帮我们将UserService注入到OrderService中,这种行为称为依赖注入。
既然IOC和DI是两种行为,那为什么spring官网会说IOC也称为DI呢?因为IOC和DI是一体两面相互依赖的。试想一下,比如spring创建OrderService和UserService总有先后顺序,假定OrderService在UserService之前创建,当spring通过IOC行为将OrderService创建完毕后,要通过DI行为注入OrderService的依赖项UserService,spring发现还没创建UserService,又要通过IOC行为,将UserService创建出来,然后在注入到OrderService。因此,IOC和DI是不可拆分的。
依赖注入(DI)
依赖注入存在两种变体:基于构造方法的依赖注入和基于Setter方法的依赖注入。
基于构造函数的依赖注入主要有下面几种:
基于ref的构造方法注入
package org.example.beans; public class ThingOne { ThingTwo thingTwo; ThingThree thingThree; public ThingOne(ThingTwo thingTwo, ThingThree thingThree) { this.thingTwo = thingTwo; this.thingThree = thingThree; } @Override public String toString() { return "ThingOne{" + "thingTwo=" + thingTwo + ", thingThree=" + thingThree + '}'; } } public class ThingTwo { } public class ThingThree { }
<!--基于ref方式依赖注入--> <bean id="thingOne" class="org.example.beans.ThingOne"> <constructor-arg ref="thingThree"></constructor-arg> <constructor-arg ref="thingTwo"></constructor-arg> </bean> <bean id="thingTwo" class="org.example.beans.ThingTwo"></bean> <bean id="thingThree" class="org.example.beans.ThingThree"></bean>
基于type 、value构造方法依赖注入
package org.example.beans; public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
<!--基于type 、value方式依赖注入--> <bean id="tom" class="org.example.beans.Person"> <constructor-arg type="int" value="19"></constructor-arg> <constructor-arg type="java.lang.String" value="Tom"></constructor-arg> </bean>
基于name 、value构造方法依赖注入
<!--基于name、value依赖方式注入--> <bean id="amy" class="org.example.beans.Person"> <constructor-arg name="age" value="16"></constructor-arg> <constructor-arg name="name" value="Amy"></constructor-arg> </bean>
基于index、value构造方法依赖注入
<!--index、value依赖方式注入--> <bean id="sam" class="org.example.beans.Person"> <constructor-arg index="0" value="Sam"></constructor-arg> <constructor-arg index="1" value="9"></constructor-arg> </bean>
测试用例:
@Test public void test01() { ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); ThingOne thingOne = ac.getBean(ThingOne.class); System.out.println(thingOne); System.out.println(ac.getBean("tom")); System.out.println(ac.getBean("amy")); System.out.println(ac.getBean("sam")); }
运行结果:
ThingOne{thingTwo=org.example.beans.ThingTwo@74a10858, thingThree=org.example.beans.ThingThree@23fe1d71} Person{name='Tom', age=19} Person{name='Amy', age=16} Person{name='Sam', age=9}
基于setter方法的依赖注入如下:
package org.example.beans; public class Book { private String name; private Person author; public String getName() { return name; } public void setName(String name) { this.name = name; } public Person getAuthor() { return author; } public void setPerson(Person author) { this.author = author; } @Override public String toString() { return "Book{" + "name='" + name + '\'' + ", author=" + author + '}'; } }
<bean id="caoxueqin" class="org.example.beans.Person"> <constructor-arg name="name" value="曹雪芹"></constructor-arg> <constructor-arg name="age" value="100"></constructor-arg> </bean> <bean id="honglou" class="org.example.beans.Book"> <property name="name" value="红楼梦"></property> <property name="person" ref="caoxueqin"></property> </bean>
测试用例:
@Test public void test02() { ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); System.out.println(ac.getBean("honglou")); }
运行结果:
Book{name='红楼梦', author=Person{name='曹雪芹', age=100}}
这里有一点需要注意,就是Book的author的setter方法,本来应该是setAuthor(Person author),这里笔者特意改成setPerson,在XML配置中对应的property的name本应是author也改为person,然而person的注入成功注入到author这个字段,可以看出基于setter方法的依赖注入是查找setter方法,然后传入方法所需要的值,而非找到这个类的所有字段,再利用反射直接对字段的值进行设置。
自动装配(Autowiring modes)
当我们使用基于XML配置时,我们可以在<bean>标签使用autowire进行自动装配配置。有四种自动装配:
模型 | 说明 |
no | 不使用自动装配,bean的默认装配模型为no。 |
byName | spring根据属性名查找beanName一样的bean进行属性注入。 |
byType | spring根据属性类型查找相同属性的bean进行属性注入。如果属性的类型为接口,spring容器中存在多个实现的bean,则会抛出异常;如果spring没有在容器中找到能与属性匹配的bean,则不会注入该属性。 |
constructor | 将bean作为构造函数的参数传入,如果参数不存在bean容器中,则会抛出异常。 |
首先我们来测试下byName的自动装配模型:
package org.example.service; public class UserService { private OrderService orderService; private OrderService orderSvc; public OrderService getOrderService() { return orderService; } public void setOrderService(OrderService orderService) { this.orderService = orderService; } public OrderService getOrderSvc() { return orderSvc; } public void setOrderSvc(OrderService orderSvc) { this.orderSvc = orderSvc; } }
<bean id="orderService" class="org.example.service.OrderService"></bean> <bean id="userService1" class="org.example.service.UserService" autowire="byName"></bean> <bean id="userService2" class="org.example.service.UserService" autowire="byType"></bean>
测试用例:
@Test public void test03() { ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); UserService userService1 = ac.getBean("userService1", UserService.class); System.out.println("userService1.orderService:" + userService1.getOrderService()); System.out.println("userService1.orderSvc:" + userService1.getOrderSvc()); UserService userService2 = ac.getBean("userService2", UserService.class); System.out.println("userService2.orderService:" + userService2.getOrderService()); System.out.println("userService2.orderSvc:" + userService2.getOrderSvc()); }
运行结果:
userService1.orderService:org.example.service.OrderService@10683d9d userService1.orderSvc:null userService2.orderService:org.example.service.OrderService@10683d9d userService2.orderSvc:org.example.service.OrderService@10683d9d
可以看到,UserService中有两个OrderService类型的字段:orderService和orderSvc,我们不再设置property,让spring根据byName和byType的方式进行自动装配,userService1使用的是byName进行注入,spring容器可以找到beanName为orderService的bean并注入到UserService与之同名的orderService字段,而UserService的字段orderSvc因为没有与之同名的bean,则无法注入。userService2使用的byType进行注入,只要找到类型匹配的bean则会注入进去,所以userService2的orderService和orderSvc都不为null,且指向同一个对象。
如果spring通过byType自动装配时发现一个类型存在多种实现,则会报错。比如A1Service和A2Service分别实现了AService接口,并在BService实现注入AService的Setter方法,在spring容器加载XML文件时,则会报找到多个实现类的错误。
package org.example.service; public interface AService { } public class A1Service implements AService { } public class A2Service implements AService { } public class BService { AService service; public AService getService() { return service; } public void setService(AService service) { this.service = service; } }
<bean id="bService" class="org.example.service.BService" autowire="byType"></bean> <bean id="a1Service" class="org.example.service.A1Service"></bean> <bean id="a2Service" class="org.example.service.A2Service"></bean>
测试用例在执行ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");报如下错误:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bService' defined in class path resource [spring.xml]: Unsatisfied dependency expressed through bean property 'service'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.example.service.AService' available: expected single matching bean but found 2: a1Service,a2Service org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bService' defined in class path resource [spring.xml]: Unsatisfied dependency expressed through bean property 'service'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.example.service.AService' available: expected single matching bean but found 2: a1Service,a2Service ……
下面我们再来看看通过构造函数进行自动装配的例子:
package org.example.service; public class CService { A1Service a1Service; A2Service a2Service; public CService(A1Service a1Service, A2Service a2Service) { this.a1Service = a1Service; this.a2Service = a2Service; } @Override public String toString() { return "CService{" + "a1Service=" + a1Service + ", a2Service=" + a2Service + '}'; } }
<bean id="a1Service" class="org.example.service.A1Service"></bean> <bean id="a2Service" class="org.example.service.A2Service"></bean> <bean id="cService" class="org.example.service.CService" autowire="constructor"></bean>
测试用例:
@Test public void test04() { ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); System.out.println(ac.getBean(CService.class)); }
运行结果:
CService{a1Service=org.example.service.A1Service@954b04f, a2Service=org.example.service.A2Service@149494d8}
可以看到,spring在创建CService的对象时会将构造函数所需的两个参数a1Service和a2Service传入,完成bean的创建。
另外,我们也可以在beans标签里面指定default-autowire属性,可以设置全局的自动装配属性。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byType" > …… </beans>