7、自动装配Bean
在 Spring 中有三种装配方式:
- XML 显式配置:使用配置文件;
- Java 显式配置:实现零配置文件;
-
自动装配:自动注入属性值
- 可以减少 setter 和构造方法的使用;
- 可以随着对象的发展而更新:例如需要向类增加新的依赖,无需修改配置即可自动注入依赖。
7.1、搭建环境
“一个人有一只宠物。”
Pet类
public class Pet {
private String type;
public Pet() {
}
public Pet(String type) {
this.type = type;
}
@Override
public String toString() {
return "Pet{" +
"type='" + type + '\'' +
'}';
}
public String getType() {
return type;
}
}
Person 类
public class Person {
private String name;
private Pet pet;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", pet=" + pet +
'}';
}
public String getName() {
return name;
}
public Pet getPet() {
return pet;
}
}
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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pet" class="indi.jaywee.pojo.Pet"/>
<bean id="person" class="indi.jaywee.pojo.Person">
<property name="pet" ref="pet"/>
</bean>
</beans>
Test 类
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean("person", Person.class);
System.out.println(person);
}
测试结果
Person 对象创建成功,Pet 装配成功。
7.1、XML自动装配
使用 XML 配置元数据时,使用 Bean 元素的 autowire 属性开启自动装配。
7.1.1、自动装配模式
7.1.2、测试
去掉 ref 属性,并增加注册几个 Bean,测试使用 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">
<bean id="pet" class="indi.jaywee.pojo.Pet">
<constructor-arg name="type" value="myPet"/>
</bean>
<bean id="pet1" class="indi.jaywee.pojo.Pet">
<constructor-arg name="type" value="myPet1"/>
</bean>
<bean id="pet2" class="indi.jaywee.pojo.Pet">
<constructor-arg name="type" value="myPet2"/>
</bean>
<bean id="person" class="indi.jaywee.pojo.Person" autowire=""/>
</beans>
1、no
不会为 Person 装配 pet 属性。
2、byName
匹配到 id 为 pet 的 Bean 并装配。
-
如果去掉 id 为 pet 的 Bean,即没有与属性同名的Bean,则不会装配。
3、byType
匹配到多个 Bean ,报错。
-
只保留一个 Bean,则自动装配相应的Bean:
4、constructor
Person 中没有 Pet 的构造器。
-
在 Person 类中增加一个构造器
public Person(Pet pet) { this.pet = pet; }
- 如果只匹配到一个Bean,则自动装配;
- 如果匹配到0个到多个Bean,则不装配。
7.2、注解自动装配
在使用注解之前,需要引入以下配置。
-
导入 context 配置;
-
设置注解支持:
<context:annotation-config/>
<?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>
-
相比没有使用注解的 XML 文件,增加了有关 context 的一个 xmlns 和两个 xsi 标记。
7.2.1、@Autowired
自动装配
- 可以在属性、setter、构造器使用;
工作机制:先 byType ,再 byName
- Spring 查找容器中是否存在属性类型的 Bean;
- 如果容器中只存在一个匹配的 Bean ,即 Bean 只在 XML 中配置了一次,则自动装配;
- 如果容器中存在多个匹配的 Bean,即 Bean 在 XML 中配置了多次,则匹配属性名:
- 存在与属性同名的 Bean,自动装配;
- 不存在同名 Bean,可以添加注解 @Qualifier(value = "beanId") 来匹配 Bean
通过测试,来解释以上工作机制。
测试
-
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:annotation-config/> <bean id="pet" class="indi.jaywee.pojo.Pet"> <constructor-arg name="type" value="myPet"/> </bean> <bean id="pet1" class="indi.jaywee.pojo.Pet"> <constructor-arg name="type" value="myPet1"/> </bean> <bean id="person" class="indi.jaywee.pojo.Person"> <property name="name" value="jaywee"/> </bean> <bean id="person" class="indi.jaywee.pojo.Person"/> </beans>
-
在 pet 属性上使用 @Autowired 注解
public class Person { private String name; @Autowired private Pet pet; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", pet=" + pet + '}'; } public String getName() { return name; } public Pet getPet() { return pet; } }
-
测试结果:
- pet 属性不为 null,自动装配成功;
- pet 的 type 值为 myPet ,说明注入了 id 为 pet 的 Bean 对象;
进一步测试
-
测试byType:删除 id 为 pet 和 pet1 的 Bean ,保留 id 为 pet2 的 Bean
-
结果:注入了 id 为 pet2 的 Bean
-
-
测试byName:删除 id 为 pet 的 Bean ,保留 id 为 pet1 和 pet2 的 Bean
-
匹配到两个 Bean ,并且无法匹配属性名。
-
解决:在 pet 属性上使用 @Qualifier(value = "beanId") 注解,匹配容器中对应的 Bean。
@Autowired @Qualifier(value = "pet1") private Pet pet;
-
7.2.2、@Resource
Java 提供的注解,工作机制类似@Autowired,可以自行测试。
相比 @Autowired ,拥有name属性,可以指定具体的Bean。
@Resource(name = "pet1")
private Pet pet;
8、使用注解开发
8.1、关于注解
注解实现原理:反射机制和动态代理方法。
-
注解注入比 XML 注入先执行,因此 XML 配置会覆盖注解配置;
-
在 Spring4 后,要使用注解开发,必须要有 aop 的 jar 包支持:
-
要使用注解开发,需要设置配置信息:
<?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:component-scan base-package=""/> </beans>
8.1.1、注解的类型
-
类级别的注解:添加在类上面。
- @Component
- 元注解为 @Component 的注解
- @Repository
- @Controller
- @Service
- 自定义注解
- Java EE6 的 @ManagedBean、@Named
-
类内部的注解:添加在类内部的属性或方法上。
- @Autowired
- @Resource
- @Value
- ...
8.1.2、工作原理
IOC 容器根据 XML 配置,进行以下操作:
- 扫描
.class
文件,将包含类级别注解的 Bean 注册到 BeanFactory 中; - 注册相应注解的后置处理器(post-processor),注册的同时将其实例化,用于处理类内部的注解;
- 将其处理器放到 BeanFactory的 beanPostProcessors 列表中;
- 创建 Bean 的过程中,属性注入或者初始化 Bean 时,调用相应的处理器进行处理。
8.1.3、后置处理器
后置处理器(post-processor)主要用于处理类内部的注解。
- 要使用注解的话,必须要在 XML 中注册相应的处理器,作为支持;
- 基于第1点说明,如果要使用多种注解,相应的就要注册很多个处理器。这样的话非常麻烦,因此可以使用
<context:annotation-config/>
标签来隐式注册它们。
<context:annotation-config/>
标签
隐式注册了5个后置处理器,对应支持的类内部的注解才生效。
后置处理器 | 处理注解 | 参考文档 |
---|---|---|
ConfigurationClass PostProcessor |
@Configuration | ConfigurationClassPostProcessor |
AutowiredAnnotationBean PostProcessor |
@Autowired @Value @Inject |
AutowiredAnnotationBeanPostProcessor |
CommonAnnotationBean PostProcessor |
@PostConstruct、@PreDestroy @Resource @WebServiceRef @EJB |
CommonAnnotationBeanPostProcessor |
PersistenceAnnotationBean PostProcessor |
@PersistenceUnit @PersistenceContext |
PersistenceAnnotationBeanPostProcessor |
EventListenerMethod Processor |
@EventListener | EventListenerMethodProcessor |
<context:component-scan>
标签
扫描指定包下的 Bean ,被扫描的 Bean 中包含的类级别的注解才会生效,Bean 才会被注册到容器中。
- base-package 属性:用于指定扫描的包路径;
- 使用该标签,隐式启用了
<context:annotation-config/>
标签。即使用当前标签时,不再需要引入<context:annotation-config/>
标签。
8.1.4、注解小结
- 注解有2种类型:类级别、类内部;
- IOC容器会自动注册类级别注解的 Bean,前提是有
<context:component-scan>
配置支持; - 要使用类内部的注解,需要有相应处理器的支持,处理器需要在 XML 中注册;
- 通过
<context:annotation-config/>
标签来隐式注册处理器; - 要让类级别注解生效,就要
<context:component-scan>
配置支持,而这个标签又隐式启动了<context:annotation-config/>
; - 在使用时,我们只需要引入相关配置,以及
<context:component-scan>
标签。
8.2、环境搭建
先搭建一个没有使用注解的环境,再引入注解开发。
8.1.1、实体类
Major 类
public class Major {
/**
* 专业名
*/
private String name;
public Major() {
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Major{" +
"name='" + name + '\'' +
'}';
}
}
Student 类
public class Student {
/**
* 姓名
*/
private String name;
/**
* 专业
*/
private Major major;
public Student() {
}
@Override
public String toString() {
return "Student{" + "\n" +
"name='" + name + '\'' + "\n" +
"major=" + major + "\n" +
'}';
}
public void setName(String name) {
this.name = name;
}
public void setMajor(Major major) {
this.major = major;
}
}
8.1.2、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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="major" class="indi.jaywee.pojo.Major">
<property name="name" value="Java"/>
</bean>
<bean id="student" class="indi.jaywee.pojo.Student">
<property name="name" value="jaywee"/>
<property name="major" ref="major"/>
</bean>
</beans>
8.1.3、测试
JUnit
@Test
public void test() {
// 实例化容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取Bean
Student student = context.getBean("student", Student.class);
System.out.println(student);
}
运行结果
Bean 对象被 IOC 容器成功创建并组装了,基本环境搭建完成。
8.2.4、引入注解
基本环境已搭建完成,现在引入支持注解的配置,来进行注解开发。
注意扫描包的路径
<?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:component-scan base-package="indi.jaywee/>
</beans>
当前运行结果:
8.3、注册Bean
将包含类级别注解的 Bean 注册到容器中,由容器管理。
相当于在XML文件中使用<bean/>
标签,可以使用 value 属性设置 Bean 的 Name 。
- @Component:注册 Java Bean
- 元注解为 @Component 的注解
- @Repository:注册 DAO
- @Controller:注册 Controller
- @Service:注册 Service
8.3.1、@Component
在 Java Bean上方添加 @Component 注解,将其注册到容器中。
@Component
public class Student {
...
}
-
自动注册的 Bean 名:开头小写的类名,即 student。
Student student = context.getBean("student", Student.class);
-
可以通过 value 属性,显式设置 Bean 名,value可以省略:
@Component(value = "myStudent") public class Student { ... } @Component("myStudent") // 直接填写Bean名 public class Student { ... }
Student student = context.getBean("myStudent", Student.class);
-
结果
8.3.2、@Component的衍生注解
即元注解为 @Component 的注解:
- @Repository:只能用于DAO
- @Controller:用于控制层
- @Service:用于业务层
添加注解
在类上方添加注解,将其注册到容器中。
@Repository
public class StudentDaoImpl implements StudentDao{
...
}
@Controller
public class StudentController {
...
}
@Service
public class StudentServiceImpl implements StudentService{
...
}
Bean名
-
默认 Bean 名:开头小写的非限定类名:
StudentDao dao = context.getBean("studentDaoImpl", StudentDaoImpl.class); StudentController controller = context.getBean("studentController", StudentController.class; StudentService service = context.getBean("studentServiceImpl", StudentService.class);
-
可以通过 value 属性,显式设置 Bean 名。也可以省略 value,直接填写 Bean 名:
@Component(value = "daoImpl") public class Student { ... } @Controller("controller") // 直接填写Bean名 public class StudentController { ... } @Component(value = "daoImpl") // 直接填写Bean名 public class Student { ... }
-
显式设置 Bean 名后,默认的 Bean 名失效
StudentDao dao = context.getBean("daoImpl", StudentDaoImpl.class); StudentController controller = context.getBean("controller", StudentController.class); StudentDao dao = context.getBean("daoImpl", StudentDaoImpl.class);
8.3.3、@Bean
用于方法,将方法返回的对象注册到容器中。
@Bean
public Major getMajor1() {
...
}
-
自动注册的 Bean 名:方法名,即 getMajor1
Major major = context.getBean("getMajor1", Major.class);
-
可以通过 value 属性,显式设置 Bean 名:
@Bean(value = "m1") public Major getMajor1() { ... }
Major major = context.getBean("m1", Major.class);
8.4、装配Bean
8.4.1、基本数据类型
@Value
用于基本数据类型的属性的注入。
@Value("jaywee")
private String name;
@Value("Java")
private String name;
8.4.2、引用数据类型
@Autowired
用于引用数据类型(其他 Bean依赖)的注入。
@Autowired
private Major major;
@Resource
和 @Autowired 的效果类似,可以使用 name 属性指定具体的 Bean。
具体有关这 2个注解的使用,参照 7、自动装配Bean
8.5、微调注解
@Qualifier
用于属性、构造方法和方法的参数,为其选择一个特定的 Bean,通常与 @Autowired 结合使用。
例如:会为 pet 属性注入 id 为 pet1 的 Bean
public class Person {
@Resource
@Qualifier(value = "pet1")
private Pet pet;
public Person(@Qualifier(value = "pet1")Pet pet) {
this.pet = pet;
}
}
等价于使用 @Resource 及其 name 属性,并且推荐使用这种方式。
public class Person {
@Resource(name = "pet1")
private Pet pet;
public Person(@Qualifier(value = "pet1")Pet pet) {
this.pet = pet;
}
}
@Nullable
用于标记属性或方法参数,表示该属性可以为 null。
@Nullable
private Pet pet;
也可以通过 @Autowired 的 Required = false实现。
@Autowired(required = false)
private Pet pet;
8.6、小结
XML与注解
- XML 适用于任何场合,维护简单;
- 注解在一定程度上可以替代 XML 配置文件,但是维护相对复杂;
- 最佳实践:
- XML 管理 Bean:即手动在 XML 中注册 Bean,而不是用类级别注解;
- 注解装配 Bean:即使用类内部注解,自动注入属性值。