文章目录
介绍
前面的文章介绍了Spring IoC容器使用XML形式的配置元数据来生产和装配Bean,本篇文章讲述如何使用Java注解来生产和装配Bean。
讲解仍然以上篇文章的工程为基础,先将Bean的装配转移到使用Java注解,这意味着Bean的定义仍然使用XML,只是将依赖注入转移到使用Java注解。
最后将Bean的定义也转移到使用Java注解。
移除XML中的依赖注入
spring-ioc-test.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="svcA" class="test.service.ServiceA">
<!-- <constructor-arg name="serviceB" ref="svcB" />
<constructor-arg name="repositoryA" ref="repoA" /> -->
</bean>
<bean id="svcB" class="test.service.ServiceB">
<!-- <property name="repositoyB" ref="repoB" /> -->
</bean>
<bean id="repoA" class="test.repository.RepositoryA" />
<bean id="repoB" class="test.repository.RepositoryB" />
</beans>
这里,与之前唯一的不同就是把<constructor-arg/>
和<property/>
这两个标记的内容使用XML的注释标记<!-- -->
注释掉了,这样就相当于把svcA
和svcB
这两个Bean的依赖注入去掉了。
我们也可以看到,XML标记在没有内容时可以有两种形式,比如标记:
<bean id=...> </bean>
<bean id=... />
XML中启用基于注解的装配:annotation-config标记
既然要使用基于注解来装配Bean,那么就需要告诉Spring IoC容器我们要启用基于注解的装配,在哪里告诉呢?还是在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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<!-- 下面的内容与上面的一样,省略 -->
</beans>
使用<annotation-config />
标记即可告诉Spring IoC容器我们要启用基于注解的装配,标记名称也是很容易记忆。
不过这个标记是在Spring的context命名空间,所以需要在根标记<beans/>
中添加上该命名空间及其对应的模式定义(XSD文件,XML Schema Definition),关于XML的命名空间和XSD暂不细说,可以简单理解为就是定义标记的集合且区分标记以避免标记名称重复。
Spring的context命名空间是:
xmlns:context="http://www.springframework.org/schema/context"
对应的模式定义以key-value的形式添加到xsi:schemaLocation
的值中,key是命名空间,value是XSD文件,中间以空格分开:
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
后面引用该命名空间的标记时,加上命名空间前缀即可,以冒号分隔,如:
<context:annotation-config />
实际上,这个标记的原理是用了Spring框架的BeanPostProcessor
功能扩展,这个标记就是向Spring IoC容器注册了AutowiredAnnotationBeanPostProcessor
、CommonAnnotationBeanPostProcessor
、PersistenceAnnotationBeanPostProcessor
等Bean后处理器,它们就是用来解析@Autowired
等注解并处理装配流程的。
因此,我们其实可以不用这个标记,而是在XML中自己定义这些后处理器的Bean,Spring IoC容器也会识别它们,并用它们来处理装配流程。当然,通常情况下,我们显然没有必要这样做去增加自己的工作。
基于注解的自动装配:@Autowired注解
现在,Spring IoC容器(实例化的时候)已经可以通过XML得知启用基于注解的Bean装配(依赖注入)了,那究竟用哪个注解呢?答案就是@Autowired
注解。
因为在我们的例子中只有ServiceA和ServiceB这两个组件才需要装配,下面就只列出这两个组件的代码。
ServiceA.java
package test.service;
import org.springframework.beans.factory.annotation.Autowired;
import test.repository.RepositoryA;
public class ServiceA {
private ServiceB serviceB;
private RepositoryA repositoryA;
@Autowired
public ServiceA (ServiceB serviceB, RepositoryA repositoryA) {
this.serviceB = serviceB;
this.repositoryA = repositoryA;
}
public void doWork() {
System.out.println("ServiceA doWork start");
serviceB.doWork();
repositoryA.save();
System.out.println("ServiceA doWork end");
}
}
可以看到,与之前的代码唯一不同的就是在构造方法的前面添加了@Autowired
注解。
ServiceB.java
package test.service;
import org.springframework.beans.factory.annotation.Autowired;
import test.repository.RepositoryB;
public class ServiceB {
@Autowired
private RepositoryB repositoyB;
public void doWork() {
System.out.println("ServiceB doWork start");
repositoyB.update();
System.out.println("ServiceB doWork end");
}
}
这里,与之前的代码有比较大的不同,但是更简洁了,原因就是:
- 去掉了属性
repositoyB
的getter和setter方法; - 属性
repositoyB
的前面添加了@Autowired
注解。
验证
现在,大家可以运行一下工程进行验证一下是否正确。
可以看到,使用基于注解来进行自动装配(依赖注入)比基于XML的要简洁很多,但只能使用在自己开发的组件当中,因为你没法为第三方组件添加注解,这时候只能使用XML来装配了。
基本等价的注解:@Resource注解
@Resource
注解与@Autowired
注解基本等价,它是JSR-250规范中定义的,全限定名是javax.annotation.Resource
它也可以用在类的属性和方法上,不过它是默认是按照Bean名称来装配的,@Autowired
是按照类型来装配的。
这里不再详细讨论。
基本等价的注解:@Inject注解
@Inject
注解与@Autowired
注解基本等价,它是JSR-330规范中定义的,全限定名是javax.inject.Inject
它也可以用在类的属性和方法上,跟@Autowired
一样是按照类型来装配的。
如果需要按照Bean名称来装配,@Autowired
需要跟@Qualifier
一起使用,@Inject
需要跟@Named
一起使用。
这里不再详细讨论。
再谈注解的本质
从这个例子我们也可以再次看到注解的本质实际上就是标记、元数据,它的三要素是:
- 定义:
@Autowired
注解显然是由Spring框架为我们定义的,名称起的也是相当有含义的,就是自动装配的意思,表示使用的地方要执行自动装配。 - 解析处理:
@Autowired
的解析和处理是由Spring框架中的AutowiredAnnotationBeanPostProcessor
这个Bean后处理器来执行的; - 使用:就是我们来使用了。
总结
@Autowired
注解可以修饰类的属性、构造方法、setter方法和其他方法;@Autowired
注解默认按照Bean的类型来自动装配;- 与
@Autowired
注解基本等价的有JSR规范定义的@Resource
注解和@Inject
注解。
Bean的实例化和自动装配(依赖注入)还有很多细节需要讨论,这里涉及到依赖关系的解析过程,以后慢慢细说,比如:
- 一些原子类型、字符串等字面值常量的注入;
- 一些集合类型的注入;
- null和空字符串的注入;
- XML中使用p命名空间和c命名空间来注入;
- 使用外部配置文件(比如properties文件和yml文件)中的值来注入;
- 内部Bean的注入;
- 复合属性的注入;
- 指定Bean不尽心自动装配;
- 自动装配的匹配,可以通过类型、名称、参数位置索引等;
- 泛型的自动装配;
- 没有符合条件的Bean怎么处理注入的;
- 有多个符合条件的Bean时怎么选择一个注入(使用primary和qualify);
- 依赖的解析过程,循环依赖的问题;
- Bean实例化的时机(强制在另一个Bean之后实例化、延迟实例化);
- XML和自动装配重复的问题;
- 高级一点的:工厂方法生成Bean,内部类的Bean,Lookup方法注入,方法替代,Bean定义的继承等