10. Spring容器配置
本文内容
- 快速入门
@Configuration
和@Bean`详解- 其他扫描方式
快速入门
Spring 新的 Java 配置支持中的核心是 @Configuration
类和 @Bean
。
@Bean 注解用于表示一个方法实例化、配置和初始化一个由 Spring IoC 容器管理的新对象。对比Spring 的XML 配置,@Bean 注解与元素的作用相同。
@Configuration
注释一个类表明它的主要目的是作为 bean 定义的来源。@Configuration
类允许通过调用同一类中的其他 @Bean
方法来定义 bean 间的依赖关系。对比pring 的XML 配置,@Bean 注解与元素的作用相同。
之前的基于XML 配置文件通常是使用ClassPathXmlApplicationContext
来配置容器,而Java-based方式使用AnnotationConfigApplicationContext
。下面上案例。
定义一个配置类AppConfig
@Configuration //注解是配置类
public class AppConfig {
@Bean // 该方法定义了一个bean
public RepositoryBase repositoryBase() {
return new RepositoryA();
}
}
上面的配置类相当于下面的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 class="com.crab.spring.ioc.demo07.RepositoryA" id="repositoryBase"/>
</beans>
运行容器并获取bean使用。
@org.junit.Test
public void test_java_config() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
RepositoryBase repositoryBase = context.getBean(RepositoryBase.class);
System.out.println(repositoryBase);
}
com.crab.spring.ioc.demo07.RepositoryA@159f197
使用起来很简洁丝滑。
@Bean
详解
对比Spring 的XML 配置,@Bean
注解与元素的作用相同。所以类似地,支持name
、init-method
、destroy-method
、autowiring
。可以在 @Configuration
或 @Component
类中使用 @Bean 注解来声明一个bean。
最常见用法声明一个bean并指定名称、描述
@Configuration
public class AppConfig {
@Bean
public RepositoryBase repositoryBase() {
return new RepositoryA();
}
}
// 指定了名称
@Configuration
public class AppConfig {
// 指定了名称
@Bean("repositoryBase")
public RepositoryBase repositoryBase() {
return new RepositoryA();
}
}
// 指定了名称和别名
@Configuration
public class AppConfig {
// 指定了名称和别名
@Bean({"repositoryBase", "repositoryBase2", "repositoryBase4"})
public RepositoryBase repositoryBase() {
return new RepositoryA();
}
}
// 通过 @Description指定bean的描述
@Bean({"repositoryBase", "repositoryBase2", "repositoryBase4"})
@Description("这是一个bean描述")
public RepositoryBase repositoryBase() {
return new RepositoryA();
}
通过接口的default方法声明bean
public interface BaseConfig {
@Bean
default RepositoryA repositoryA(){
return new RepositoryA();
}
}
@Configuration
public class BaseConfigImpl implements BaseConfig {
}
注入依赖项
可以类构造方法的方式注入以来,也可以在同一个 @Configuration
内通过方法名直接注入依赖。
@Configuration
public class AppConfig2 {
@Bean
public RepositoryA repositoryA() {
return new RepositoryA();
}
// 1 类似构造函数方式注入依赖 RepositoryA
@Bean
public Service1 service1(RepositoryA repositoryA) {
return new Service1(repositoryA);
}
// 2 同一个 @Configuration 可通过方法名直接注入依赖
@Bean
public Service1 service2() {
return new Service1(repositoryA());
}
}
接收生命周期回调
支持bean初始化和bean销毁生命周期的回调接口。
@Configuration
public class AppConfig3 {
// 指定初始化和销毁回调方法
@Bean(initMethod = "init", destroyMethod = "destroy")
public Service2 service2() {
return new Service2();
}
}
// 测试方法
@org.junit.Test
public void test_callback() {
System.out.println("容器开始初始化");
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig3.class);
Service2 bean = context.getBean(Service2.class);
System.out.println(bean);
System.out.println("容器开始销毁");
context.close();
}
// 测试结果 成功回调
容器开始初始化
Service2 初始化
com.crab.spring.ioc.demo07.Service2@159f197
容器开始销毁
Service2 销毁
默认情况下,使用 Java 配置定义的具有公共的close
或shutdown
方法的 bean 会自动加入销毁回调。若不想被调用,可在**@Bean**配置空的销毁方法。
@Configuration
public class AppConfig4 {
// 有默认的公共的 shutdown 指定为空就不会被调用
@Bean(initMethod = "")
public Service4 service4() {
return new Service4();
}
}
指定bean的作用域
@Bean
声明一个bean的作用域是单例的,可以通过@Scope
指定作用域。
@Configuration
public class AppConfig4 {
@Bean
@Scope("prototype")
public Service4 service4() {
return new Service4();
}
}
@Configuration
详解
@Configuration 是一个类级别的注解,表明一个对象是 bean 定义的来源。@Configuration 类通过 @Bean 注释的方法声明 bean。对@Configuration 类上的@Bean 方法的调用也可用于定义bean 间的依赖关系。
同一个Configuration
内注入 bean之间依赖
@Configuration
public class AppConfig2 {
@Bean
public RepositoryA repositoryA() {
return new RepositoryA();
}
// 1 类似构造函数方式注入依赖 RepositoryA
@Bean
public Service1 service1(RepositoryA repositoryA) {
return new Service1(repositoryA);
}
// 2 同一个 @Configuration 可通过方法名直接注入依赖
@Bean
public Service1 service2() {
return new Service1(repositoryA());
}
}
@Configuration
注入内部工作细节
分析下面的案例,在同一个配置类,repositoryA()
调用了2次,是执行了2次返回了2个RepositoryA?还是只执行一次返回同一个RepositoryA?单从编程的角度来看,是2次,看一下测试结果。
@Configuration
public class AppConfig5 {
// 疑问:此方法被调用了2次,是执行了2次返回了2个RepositoryA?还是只执行一次返回同一个RepositoryA?
@Bean
public RepositoryA repositoryA() {
System.out.println("new 一个 RepositoryA");
return new RepositoryA();
}
@Bean
public Service5 service5() {
Service5 service5 = new Service5();
service5.setRepositoryA(repositoryA());
return service5;
}
@Bean
public Service6 service6() {
Service6 service6 = new Service6();
service6.setRepositoryA(repositoryA());
return service6;
}
}
测试结果
@org.junit.Test
public void test_configuration_more() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig5.class);
RepositoryA repositoryA = context.getBean(RepositoryA.class);
Service5 service5 = context.getBean(Service5.class);
Service6 service6 = context.getBean(Service6.class);
System.out.println("RepositoryA是否是同一个对象:");
System.out.println(repositoryA);
System.out.println(service5.getRepositoryA());
System.out.println(service6.getRepositoryA());
System.out.println("AppConfig5对象:");
System.out.println(context.getBean(AppConfig5.class));
}
// 结果
new 一个 RepositoryA
RepositoryA是否是同一个对象:
com.crab.spring.ioc.demo07.RepositoryA@4b14c583
com.crab.spring.ioc.demo07.RepositoryA@4b14c583
com.crab.spring.ioc.demo07.RepositoryA@4b14c583
AppConfig5对象:
// AppConfig5是一个被CGLIB代理的类,有$$EnhancerBySpringCGLIB$$标识
com.crab.spring.ioc.demo07.AppConfig5$$EnhancerBySpringCGLIB$$a996d6ad@65466a6a
结论是只调用了1次,返回的是同一个实例。原因:在 Spring 中,实例化的 bean 默认具有单例范围。所有 @Configuration 类在启动时都使用 CGLIB
进行子类化。在子类中,子方法在调用父方法并创建新实例之前首先检查容器中是否有任何缓存(作用域)bean,此处第一次调用repositoryA()
实例化的bean被缓存了,后面调用直接返回了。
CGLIB
代理在后面AOP的章节专门展开,此处当了解。
在@Configuration
和@Component
中使用@Bean
的区别
在上面的AppConfig5
基础上,将@Configuration
改成@Component
,直观看下区别。
@Component
public class AppConfig5 {
// 省略
}
输出结果
new 一个 RepositoryA
new 一个 RepositoryA
new 一个 RepositoryA
RepositoryA是否是同一个对象:
com.crab.spring.ioc.demo07.RepositoryA@226a82c4
com.crab.spring.ioc.demo07.RepositoryA@731f8236
com.crab.spring.ioc.demo07.RepositoryA@255b53dc
AppConfig5对象:
com.crab.spring.ioc.demo07.AppConfig5@1dd92fe2
从结论分析:
- repositoryA()被调用了3次,
RepositoryA
被实例化了3次 Service5
和Service6
中的RepositoryA
依赖对象不是同一个AppConfig5
只是一个普通的对象实例,不会被代理。
所以,在@Configuration
和@Component
中使用@Bean
的区别是:
@Component
内不能通过@Bean
标记的方法处理依赖注入,@Configuration
中可以@Component
标识的类不会被代理,@Configuration
标识的类会被代理
扩展:
@Component
中使用@Bean
如何处理依赖注入?
总结
本文介绍Java-based方式的容器配置,使用起来更简洁,同时重点介绍了@Bean 和 @Configuration的用法和细节。