封面画师:T5-茨舞(微博) 封面ID:104959772
参考视频:黑马程序员Spring视频教程,全面深度讲解spring5底层原理
源码仓库:mofan212/advanced-spring (github.com)
43. FactoryBean
FactoryBean 是一个接口,可以实现该接口,并指定一个泛型,在重写的方法指定泛型类型对象的创建,然后将实现类交由 Spring 管理,最后 Spring 容器中会增加泛型类型的 Bean。这个 Bean 并不是完全受 Spring 管理,或者说部分受 Spring 管理。
为什么这么说呢?
首先定义一个 Bean2,交由 Spring 管理,但它不是重点:
@Component
public class Bean2 {
}
然后定义 Bean1,它未交由 Spring 管理,但是在其内部注入了 Bean2、定义初始化方法、实现 Aware 接口:
@Slf4j
public class Bean1 implements BeanFactoryAware {
private Bean2 bean2;
@Autowired
public void setBean2(Bean2 bean2) {
this.bean2 = bean2;
}
public Bean2 getBean2() {
return this.bean2;
}
@PostConstruct
public void init() {
log.debug("init");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
log.debug("setBeanFactory({})", beanFactory);
}
}
定义 Bean1FactoryBean,实现 FactoryBean 接口,指定泛型为 Bean1,将其交由 Spring 管理,Bean 的名称是 bean1:
@Slf4j
@Component("bean1")
public class Bean1FactoryBean implements FactoryBean<Bean1> {
@Override
public Bean1 getObject() throws Exception {
Bean1 bean1 = new Bean1();
log.debug("create bean: {}", bean1);
return bean1;
}
@Override
public Class<?> getObjectType() {
return Bean1.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
使用这种方式添加到 Spring 容器中的 Bean 的名称是 bean1,但 Bean 的类型不是 Bean1FactoryBean,或者 FactoryBean,而是 Bean1。
@ComponentScan
public class A43 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A43.class);
Bean1 bean1 = context.getBean("bean1", Bean1.class);
System.out.println(bean1);
context.close();
}
}
运行 main() 方法后,控制台打印出:
indi.mofan.a43.Bean1FactoryBean - create bean: indi.mofan.a43.Bean1@2667f029 indi.mofan.a43.Bean1@2667f029
Bean1 类型的 Bean 被成功添加到 Spring 容器中,但根据打印的日志信息可以看出这个 Bean 没有经历依赖注入阶段、没有回调 Aware 接口、没有经历初始化阶段,其创建是由重写的 getObject() 方法完成的。
这个 Bean 就真的没有经历 Spring Bean 的生命周期中的任何阶段吗?
定义 Bean1PostProcessor,实现 BeanPostProcessor 接口,在 bean1 初始化前后打印日志信息:
@Slf4j
@Component
public class Bean1PostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ("bean1".equals(beanName) && bean instanceof Bean1) {
log.debug("before [{}] init", beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("bean1".equals(beanName) && bean instanceof Bean1) {
log.debug("after [{}] init", beanName);
}
return bean;
}
}
执行 main() 方法后,控制台打印出:
indi.mofan.a43.Bean1FactoryBean - create bean: indi.mofan.a43.Bean1@6a28ffa4 indi.mofan.a43.Bean1PostProcessor - after [bean1] init indi.mofan.a43.Bean1@6a28ffa4
bean1 进行了初始化后的增强逻辑,但未进行初始化前的增强逻辑。
创建代理对象的时机就是在初始化后,因此由 FactoryBean 创建的 Bean 可以进行代理增强 。
FactoryBean接口
FactoryBean 接口中有三个可以被重写的方法:
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
其中:
getObject()用于构造 Bean 对象getObjectType()用于返回 Bean 对象的类型,以便可以通过类型从容器中获取 BeanisSingleton()每次获取的 Bean 对象是否是单例的
从容器中获取 Bean 时可以通过名称获取、可以通过类型获取、也可以通过名称和类型一起获取。如果重写的 getObjectType() 方法返回了 null,那么 仅仅 类型从容器中获取 Bean 时,将抛出 NoSuchBeanDefinitionException 异常,并提示没有指定类型的 Bean。
如果重写的 isSingleton() 方法返回 true,那么每次充容器中获取 Bean 对象都是同一个,反之则不是。
注意: 由 FactoryBean 构造的单例 Bean 不会存放在 DefaultSingletonBeanRegistry 的 singletonFactories 中,而是在 AbstractAutowireCapableBeanFactory 的 factoryBeanInstanceCache 中。
获取
FactoryBean类型的 Bean
肯定不能简单地通过名称获取,那会返回其泛型参数类型的 Bean,那通过类型获取呢?比如:
context.getBean(Bean1FactoryBean.class)
答案是可行的。
除此之外,还可以在名称前添加 &,然后通过名称来获取(有点指针的味道?):
context.getBean("&bean1")
44. @Indexed
Spring 在进行组件扫描时,会遍历项目中依赖的所有 Jar 包中类路径下所有的文件,找到被 @Component 及其衍生注解标记的类,然后把它们组装成 BeanDefinition 添加到 Spring 容器中。
如果扫描的返回过大,势必会大大地影响项目启动速度。
为了优化扫描速度,引入以下依赖,Spring 将扫描过程提前到编译期:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<optional>true</optional>
</dependency>
现有如下类信息:
@Component
public class Bean1 {
}
@Component
public class Bean2 {
}
@Component
public class Bean3 {
}
这几个类都与 A44 存放于同一包路径下:
public class A44 {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 组件扫描核心类
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
scanner.scan(A44.class.getPackage().getName());
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);
}
}
运行 main() 方法,控制台打印出:
bean2 bean3 bean1 org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory
这没什么奇怪的,bean1、bean2 和 bean3 都被添加到 Spring 容器中。
在编译生成的 target 目录下的 classes/META-INF/spring.components 文件里有以下信息:
indi.mofan.a44.Bean1=org.springframework.stereotype.Component
indi.mofan.a44.Bean2=org.springframework.stereotype.Component
indi.mofan.a44.Bean3=org.springframework.stereotype.Component
如果删除最后两条信息,再次运行 main() 方法呢?
bean1 org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory
此时只有 bean1 被添加到 Spring 容器中,也就是说会先以 spring.components 文件中的信息为主。
那 spring.components 是怎么什么的?
它是在引入 spring-context-indexer 依赖后,在编译期根据类是否被 @Indexed 注解标记,生成 spring.components 文件及内容。
到目前为止,虽然都没显式使用 @Indexed 注解,但它包含在 @Component 注解中:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
总结
导入 spring-context-indexer 依赖后,在编译期根据 @Indexed 生成 META-INF/spring.components 文件。
Spring 在扫描组件时,如果发现 META-INF/spring.components 文件存在,以它为准加载 BeanDefinition,反之遍历包含 Jar 包类路径下所有 class 信息。
45. 代理进一步理解
在 Spring 的代理中,依赖注入和初始化针对的是目标对象,代理对象和目标对象是两个对象,两者的成员变量不会共享。
确保项目中已导入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
依赖注入和初始化针对的是目标对象
现有如下类信息:
@Slf4j
@Component
public class Bean1 {
protected Bean2 bean2;
protected boolean initialized;
@Autowired
public void setBean2(Bean2 bean2) {
log.debug("setBean2(Bean2 bean2)");
this.bean2 = bean2;
}
@PostConstruct
public void init() {
log.debug("init");
initialized = true;
}
public Bean2 getBean2() {
log.debug("getBean2()");
return bean2;
}
public boolean isInitialized() {
log.debug("isInitialized()");
return initialized;
}
}
@Component
public class Bean2 {
}
为 Bean1 中的每个方法定制一个前置通知:
@Aspect
@Component
public class MyAspect {
/**
* 对 Bean1 中所有的方法进行匹配
*/
@Before("execution(* indi.mofan.a45.Bean1.*(..))")
public void before() {
System.out.println("before");
}
}
有一 SpringBoot 主启动类,它与 Bean1、Bean2 和 MyAspect 在同一包路径下,确保它们能被自动添加到 Spring 容器中:
@SpringBootApplication
public class A45 {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A45.class, args);
context.close();
}
}
运行 main() 方法,控制台输出:
indi.mofan.a45.Bean1 - setBean2(Bean2 bean2) indi.mofan.a45.Bean1 - init
Bean1 中的依赖注入和初始化被成功执行,但 并没有被增强。
由于 Bean1 被增强了,从 Spring 容器中获取的对象将是代理对象:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A45.class, args);
Bean1 proxy = context.getBean(Bean1.class);
proxy.setBean2(new Bean2());
proxy.init();
context.close();
}
before indi.mofan.a45.Bean1 - setBean2(Bean2 bean2) before indi.mofan.a45.Bean1 - init
主动调用的 setBean2() 和 init() 方法 都被增强。
代理对象与目标对象的成员变量不共享
尝试打印代理对象和目标对象的成员变量信息(直接访问,不使用方法):
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A45.class, args);
Bean1 proxy = context.getBean(Bean1.class);
showProxyAndTarget(proxy);
context.close();
}
@SneakyThrows
public static void showProxyAndTarget(Bean1 proxy) {
System.out.println(">>>>> 代理中的成员变量");
System.out.println("\tinitialized = " + proxy.initialized);
System.out.println("\tbean2 = " + proxy.bean2);
if (proxy instanceof Advised) {
Advised advised = (Advised) proxy;
System.out.println(">>>>> 目标中的成员变量");
Bean1 target = (Bean1) advised.getTargetSource().getTarget();
System.out.println("\tinitialized = " + target.initialized);
System.out.println("\tbean2 = " + target.bean2);
}
}
>>>>> 代理中的成员变量 initialized = false bean2 = null >>>>> 目标中的成员变量 initialized = true bean2 = indi.mofan.a45.Bean2@771db12c
由于依赖注入和初始化只针对目标对象,因此代理对象中的成员变量的值都是初始值。
在实际应用过程中,不会直接去访问成员变量,而是通过方法去访问:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A45.class, args);
Bean1 proxy = context.getBean(Bean1.class);
showProxyAndTarget(proxy);
System.out.println(">>>>>>>>>>>>>>>>>>>");
System.out.println(proxy.getBean2());
System.out.println(proxy.isInitialized());
context.close();
}
before indi.mofan.a45.Bean1 - getBean2() indi.mofan.a45.Bean2@771db12c before indi.mofan.a45.Bean1 - isInitialized() true
通过方法访问代理对象的成员变量时,这些方法会被增强,同时代理对象中的方法又会去调用目标对象的方法,从而读取出正确的值。
只会对能被重写的方法进行增强
在 Bean1 中增加几个方法:
@Component
public class Bean1 {
// --snip--
public void m1() {
System.out.println("m1() 成员方法");
}
final public void m2() {
System.out.println("m2() final 方法");
}
static public void m3() {
System.out.println("m3() static 方法");
}
private void m4() {
System.out.println("m4() private 方法");
}
}
@SneakyThrows
public static void main(String[] args) {
// --snip--
// static、final、private 修饰的方法不会被增强
proxy.m1();
proxy.m2();
Bean1.m3();
Method m4 = Bean1.class.getDeclaredMethod("m4");
m4.setAccessible(true);
m4.invoke(proxy);
context.close();
}
before m1() 成员方法 m2() final 方法 m3() static 方法 m4() private 方法
能被重写的成员方法成功被增强,但被 final 修饰的、被 static 修饰的方法和私有方法由于无法被重写,因此它们不能被增强。如果想增强这些方法,可以使用 AspectJ 编译器增强或者 Agent 类加载。
46. @Value 注入底层
现有一 Bean1 类如下:
public class Bean1 {
@Value("${JAVA_HOME}")
private String home;
@Value("18")
private int age;
}
需要解析 @Value("${JAVA_HOME}") 和 @Value("18") 的值,其中 JAVA_HOME 以系统环境变量填充,18 为整型。
解析分为两步:
- 获取
@Value注解中value属性值; - 解析属性值
@Configuration
@SuppressWarnings("all")
public class A46 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A46.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
ContextAnnotationAutowireCandidateResolver resolver =
new ContextAnnotationAutowireCandidateResolver();
resolver.setBeanFactory(beanFactory);
test1(context, resolver);
test2(context, resolver);
}
@SneakyThrows
private static void test1(AnnotationConfigApplicationContext context,
ContextAnnotationAutowireCandidateResolver resolver) {
DependencyDescriptor dd1 =
new DependencyDescriptor(Bean1.class.getDeclaredField("home"), false);
// 获取 @Value 的内容
String value = resolver.getSuggestedValue(dd1).toString();
System.out.println(value);
// 解析 ${}
value = context.getEnvironment().resolvePlaceholders(value);
System.out.println(value);
}
}
${JAVA_HOME}
D:\environment\JDK1.8
@SneakyThrows
private static void test2(AnnotationConfigApplicationContext context,
ContextAnnotationAutowireCandidateResolver resolver) {
DependencyDescriptor dd1 =
new DependencyDescriptor(Bean1.class.getDeclaredField("age"), false);
// 获取 @Value 的内容
String value = resolver.getSuggestedValue(dd1).toString();
System.out.println("@Value 的 value 属性值: " + value);
// 解析 ${}
value = context.getEnvironment().resolvePlaceholders(value);
System.out.println("解析得到的值: " + value);
System.out.println("解析得到的值的类型: " + value.getClass());
// 转成字段的类型
Object age = context.getBeanFactory()
.getTypeConverter()
.convertIfNecessary(value, dd1.getDependencyType());
System.out.println("转换后的类型: " + age.getClass());
}
@Value 的 value 属性值: 18 解析得到的值: 18 解析得到的值的类型: class java.lang.String 转换后的类型: class java.lang.Integer
EL 表达式的解析
假设有如下几个类:
public class Bean2 {
@Value("#{@bean3}")
private Bean3 bean3;
}
@Component("bean3")
public class Bean3 {
}
static class Bean4 {
@Value("#{'hello, ' + '${JAVA_HOME}'}")
private String value;
}
同样要求解析 @Value 中的 value 属性值。
如果沿用 test2() 方法进行解析,控制台打印出:
@Value 的 value 属性值: #{@bean3}
解析得到的值: #{@bean3}
解析得到的值的类型: class java.lang.String
Exception in thread "main" org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'indi.mofan.a46.A46$Bean3'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'indi.mofan.a46.A46$Bean3': no matching editors or conversion strategy found
最后一步数据转换出了问题,无法将 String 转换成 A46$Bean3 类型,也就是说解析 @bean3 失败了,程序仍然把它当成字符串,而不是注入的 Bean。
为了解析成功,需要在转换前解析 #{}:
@SneakyThrows
public static void main(String[] args) {
// --snip--
test3(context, resolver, Bean2.class.getDeclaredField("bean3"));
System.out.println(">>>>>>>>>>>>>>>>>>>");
test3(context, resolver, Bean4.class.getDeclaredField("value"));
}
private static void test3(AnnotationConfigApplicationContext context,
ContextAnnotationAutowireCandidateResolver resolver,
Field field) {
DependencyDescriptor dd1 = new DependencyDescriptor(field, false);
// 获取 @Value 的内容
String value = resolver.getSuggestedValue(dd1).toString();
System.out.println("@Value 的 value 属性值: " + value);
// 解析 ${}
value = context.getEnvironment().resolvePlaceholders(value);
System.out.println("解析得到的值: " + value);
System.out.println("解析得到的值的类型: " + value.getClass());
// 解析 #{}
Object bean3 = context.getBeanFactory()
.getBeanExpressionResolver()
.evaluate(value, new BeanExpressionContext(context.getBeanFactory(), null));
// 类型转换
Object result = context.getBeanFactory()
.getTypeConverter()
.convertIfNecessary(bean3, dd1.getDependencyType());
System.out.println("转换后的类型: " + result.getClass());
}
@Value 的 value 属性值: #{@bean3}
解析得到的值: #{@bean3}
解析得到的值的类型: class java.lang.String
转换后的类型: class indi.mofan.a46.A46$Bean3
>>>>>>>>>>>>>>>>>>>
@Value 的 value 属性值: #{'hello, ' + '${JAVA_HOME}'}
解析得到的值: #{'hello, ' + 'D:\environment\JDK1.8'}
解析得到的值的类型: class java.lang.String
转换后的类型: class java.lang.String
47. @Autowired 注入底层
47.1 注入方式
按成员变量类型注入
现有一 Bean1 类如下:
static class Bean1 {
@Autowired
private Bean2 bean2;
}
需要被注入的对象所在类:
@Component("bean2")
static class Bean2 {
}
从容器中获取需要被注入的 Bean 对象:
@Configuration
public class A47_1{
@SneakyThrows
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A47_1.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
DependencyDescriptor dd1 =
new DependencyDescriptor(Bean1.class.getDeclaredField("bean2"), false);
System.out.println(beanFactory.doResolveDependency(dd1, "bean1", null, null));
}
}
indi.mofan.a47.A47_1$Bean2@222545dc
按参数类型注入
对 Bean1 进行修改:
static class Bean1 {
// --snip--
@Autowired
public void setBean2(Bean2 bean2) {
this.bean2 = bean2;
}
}
根据 setBean2() 方法的 Bean2 类型参数进行注入:
@SneakyThrows
public static void main(String[] args) {
// --snip--
Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
DependencyDescriptor dd2 =
new DependencyDescriptor(new MethodParameter(setBean2, 0), false);
System.out.println(beanFactory.doResolveDependency(dd2, "bean1", null, null));
}
indi.mofan.a47.A47_1$Bean2@222545dc
包装为
Optional<Bean2>
对 Bean1 进行修改:
static class Bean1 {
// --snip--
@Autowired
private Optional<Bean2> bean3;
}
如果直接按照以下方式获取 DependencyDescriptor 对象:
DependencyDescriptor dd3 =
new DependencyDescriptor(Bean1.class.getDeclaredField("bean3"), false);
其 dd3.getDependencyType() 方法将返回 Optional 的 Class 对象,这显然是不对的。
Spring 提供为 DependencyDescriptor 提供了解决这个问题的方法,即「增加嵌套等级」来获取内嵌类型:
dd3.increaseNestingLevel();
执行 increaseNestingLevel() 方法后,dd3.getDependencyType() 方法返回的 Bean2 的 Class 对象。
因此注入 Optional<Bean2> 类型的成员变量应该按照:
@SneakyThrows
public static void main(String[] args) {
// --snip--
DependencyDescriptor dd3 =
new DependencyDescriptor(Bean1.class.getDeclaredField("bean3"), false);
if (Optional.class.equals(dd3.getDependencyType())) {
dd3.increaseNestingLevel();
Object result = beanFactory.doResolveDependency(dd3, "bean1", null, null);
System.out.println(Optional.ofNullable(result));
}
}
Optional[indi.mofan.a47.A47_1$Bean2@222545dc]
注入 Optional 对象和使用 @Autowired(required = false) 的作用是一样的,当容器中不存在目标 Bean 时,不会抛出 NoSuchBeanDefinitionException 异常。
包装为
ObjectFactory<Bean2>
对 Bean1 进行修改:
static class Bean1 {
// --snip--
@Autowired
private ObjectFactory<Bean2> bean4;
}
注入 ObjectFactory<Bean2> 类型的对象与注入 Optional<Bean2> 类型的对象类似,只不过 ObjectFactory 提供了 延迟注入 的能力,也就是说 Bean2 对象不会立即被注入,而是在需要时才被注入。
ObjectFactory 是一个函数式接口:
@FunctionalInterface
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
注入的应该是 ObjectFactory 对象,在调用该对象的 getObject() 方法时,Bean2 对象才被注入:
@SneakyThrows
public static void main(String[] args) {
// --snip--
DependencyDescriptor dd4 =
new DependencyDescriptor(Bean1.class.getDeclaredField("bean4"), false);
if (ObjectFactory.class.equals(dd4.getDependencyType())) {
dd4.increaseNestingLevel();
ObjectFactory<Bean2> objectFactory = () ->
(Bean2) beanFactory.doResolveDependency(dd4, "bean1", null, null);
System.out.println(objectFactory.getObject());
}
}
indi.mofan.a47.A47_1$Bean2@222545dc
与 ObjectFactory 类似的还有个名为 ObjectProvider 的接口,后者继承了前者。
与 ObjectFactory 相比,ObjectProvider 提供了类似于 Optional 的安全注入功能,当容器中不存在目标 Bean 时,不会抛出 NoSuchBeanDefinitionException 异常。ObjectProvider 提供的 getIfAvailable() 在获取不存在的 Bean 时,不会抛出异常,而是返回 null。
对
@Lazy的处理
对 Bean1 进行修改,在成员变量 bean2 上使用 @Lazy 注解:
static class Bean1 {
@Autowired
@Lazy
private Bean2 bean2;
// --snip--
}
对于 @Lazy 注解标记的成员变量,注入的对象不再是目标对象,而是其代理对象,因此不能使用 DefaultListableBeanFactory 对象的 doResolveDependency() 方法来获取注入的对象。
@SneakyThrows
public static void main(String[] args) {
// --snip--
DependencyDescriptor dd5 =
new DependencyDescriptor(Bean1.class.getDeclaredField("bean2"), false);
ContextAnnotationAutowireCandidateResolver resolver =
new ContextAnnotationAutowireCandidateResolver();
resolver.setBeanFactory(beanFactory);
// 根据 @Lazy 创建代理对象
Object proxy = resolver.getLazyResolutionProxyIfNecessary(dd5, "bean1");
System.out.println(proxy);
System.out.println(proxy.getClass());
}
indi.mofan.a47.A47_1$Bean2@222545dc class indi.mofan.a47.A47_1$Bean2$$EnhancerBySpringCGLIB$$d631a20c
@Lazy 实现的 延迟注入 (前面讲的 ObjectFactory 和 ObjectProvider 也有延迟注入功能,但与 @Lazy 的实现不一样)不是不注入,而是注入目标对象的代理对象,当使用到代理对象中的方法时,代理对象就会去 Spring 容器中寻找真正的目标对象,然后调用目标对象对应的方法。
@Lazy 的实现细节可以在 ContextAnnotationAutowireCandidateResolver 中看到:
public class ContextAnnotationAutowireCandidateResolver
extends QualifierAnnotationAutowireCandidateResolver {
@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor,
@Nullable String beanName) {
// 如果有 @Lazy 注解,就创建代理对象
return (isLazy(descriptor) ?
buildLazyResolutionProxy(descriptor, beanName) : null);
}
protected boolean isLazy(DependencyDescriptor descriptor) {
for (Annotation ann : descriptor.getAnnotations()) {
// 获取 @Lazy 注解信息
Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
if (lazy != null && lazy.value()) {
return true;
}
}
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
// --snip--
}
return false;
}
protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor,
final @Nullable String beanName) {
// --snip--
// 获取代理对象
return pf.getProxy(dlbf.getBeanClassLoader());
}
}
补充:包装为
Provider<Bean2>
Provider 接口是由 JSR-330 提出,要想使用此接口,需要导入以下依赖:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<!-- 不要惊讶,版本号就是 1 -->
<version>1</version>
</dependency>
对 Bean1 进行修改:
static class Bean1 {
// --snip--
@Autowired
private Provider<Bean2> bean5;
}
注入 Provider 类型的对象与注入 ObjectFactory<Bean2> 类型的对象极其相似,Provider 也提供了 延迟注入 的能力,注入的是 Provider 对象,在调用该对象的 get() 方法时,Bean2 对象才被注入:
@SneakyThrows
public static void main(String[] args) {
// --snip--
DependencyDescriptor dd6 = new DependencyDescriptor(Bean1.class.getDeclaredField("bean5"), false);
if (Provider.class.equals(dd6.getDependencyType())) {
dd6.increaseNestingLevel();
Provider<Bean2> provider = () ->
(Bean2) beanFactory.doResolveDependency(dd6, "bean1", null, null);
System.out.println(provider.get());
}
}
indi.mofan.a47.A47_1$Bean2@222545dc
Optional 类型、ObjectFactory 类型、ObjectProvider 类型、JSR-330 提供的类型的注入逻辑可在 DefaultListableBeanFactory#resolveDependency() 方法中看到:
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor,
@Nullable String requestingBeanName,
@Nullable Set<String> autowiredBeanNames,
@Nullable TypeConverter typeConverter) throws BeansException {
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
if (Optional.class == descriptor.getDependencyType()) {
// 对 Optional 的处理
}
else if (ObjectFactory.class == descriptor.getDependencyType() ||
ObjectProvider.class == descriptor.getDependencyType()) {
// 对 ObjectFactory、ObjectProvider 的处理
}
else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
// 对 JSR-330 提供的类型的处理
}
else {
// 其他处理
}
}
47.2 类型匹配细节
无论是 @Value 注入,还是 @Autowired 注入,最终都会调用 DefaultListableBeanFactory#doResolveDependency() 方法。
现有如下几个类:
interface Dao<T> {
}
@Component("dao1")
static class Dao1 implements Dao<Student> {
}
@Component("dao2")
static class Dao2 implements Dao<Teacher> {
}
static class Student {
}
static class Teacher {
}
interface Service {
}
@Component("service1")
static class Service1 implements Service {
}
@Component("service2")
static class Service2 implements Service {
}
@Component("service3")
static class Service3 implements Service {
}
有一目标类 Target,对其进行依赖注入:
static class Target {
@Autowired
private Service[] serviceArray;
@Autowired
private List<Service> serviceList;
@Autowired
private ConfigurableApplicationContext applicationContext;
@Autowired
private Dao<Teacher> dao;
@Autowired
@Qualifier("service2")
private Service service;
}
数组类型
Spring 容器中肯定不存在数组类型且元素类型为 Service 的 Bean 对象,因此注入的 Service 数组应当是容器中 Service 类型的 Bean 数组:
@Configuration
@SuppressWarnings("all")
public class A47_2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A47_2.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
testArray(beanFactory);
}
@SneakyThrows
private static void testArray(DefaultListableBeanFactory beanFactory) {
DependencyDescriptor dd1 =
new DependencyDescriptor(Target.class.getDeclaredField("serviceArray"), true);
if (dd1.getDependencyType().isArray()) {
// 获取数组中的元素类型
Class<?> componentType = dd1.getDependencyType().getComponentType();
System.out.println(componentType);
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
beanFactory,
componentType
);
List<Object> beans = new ArrayList<>();
for (String name : names) {
System.out.println(name);
Object bean = dd1.resolveCandidate(name, componentType, beanFactory);
beans.add(bean);
}
Object array = beanFactory.getTypeConverter()
.convertIfNecessary(beans, dd1.getDependencyType());
System.out.println(array);
}
}
}
interface indi.mofan.a47.A47_2$Service service3 service2 service1 [Lindi.mofan.a47.A47_2$Service;@49139829
相关源码可在 DefaultListableBeanFactory#resolveMultipleBeans() 方法中看到。
List类型
注入 List<Service> 类型数据的逻辑与注入 Service[] 类型数据的逻辑类似,只不过在容器中寻找目标 Bean 时不再通过数组元素类型,而是通过 List 的泛型类型:
@SneakyThrows
private static void testList(DefaultListableBeanFactory beanFactory) {
DependencyDescriptor dd2 =
new DependencyDescriptor(Target.class.getDeclaredField("serviceList"), true);
if (List.class.equals(dd2.getDependencyType())) {
// 获取泛型信息
Class<?> resolve = dd2.getResolvableType().getGeneric().resolve();
System.out.println(resolve);
List<Object> list = new ArrayList<>();
String[] names =
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, resolve);
for (String name : names) {
Object bean = dd2.resolveCandidate(name, resolve, beanFactory);
list.add(bean);
}
System.out.println(list);
}
}
interface indi.mofan.a47.A47_2$Service [indi.mofan.a47.A47_2$Service3@35e2d654, indi.mofan.a47.A47_2$Service2@1bd4fdd, indi.mofan.a47.A47_2$Service1@55183b20]
注意: 对于注入的集合类型数据,注入的类型必须是 Collection 及其 子接口,比如不支持直接注入 ArrayList 类型的数据。
相关源码可在 DefaultListableBeanFactory#resolveMultipleBeans() 方法中看到:
@Nullable
private Object resolveMultipleBeans(DependencyDescriptor descriptor,
@Nullable String beanName,
@Nullable Set<String> autowiredBeanNames,
@Nullable TypeConverter typeConverter) {
Class<?> type = descriptor.getDependencyType();
if (descriptor instanceof StreamDependencyDescriptor) {
// --snip--
}
else if (type.isArray()) {
// --snip--
}
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
// 就是这里的判断
}
else if (Map.class == type) {
// --snip--
}
else {
return null;
}
}
从源码中可以看到,@Autowired 还支持 Map 类型数据的注入,此时注入的 Map 的 key 是 Bean 的名称,value 是 Bean 对象,这种方式常常配合策略模式使用。需要注意的是,只支持注入 Map 接口,不支持其子类。
特殊类型
ConfigurableApplicationContext
ConfigurableApplicationContext 是 ApplicationContext 接口的子接口。
需要注意的是,在 Spring 容器中并不存在 ConfigurableApplicationContext 类型、或 ApplicationContext 类型的 Bean。
Spring 容器中的所有单例 Bean 对象存放在 DefaultListableBeanFactory 中,在 DefaultListableBeanFactory 父类 DefaultSingletonBeanRegistry 中有一成员变量:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// --snip--
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// --snip--
}
singletonObjects 用于存放 Spring 容器中的所有单例 Bean 对象。
类似 ApplicationContext、BeanFactory 类型的对象则是放在 DefaultListableBeanFactory 中的 resolvableDependencies 中:
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
// --snip--
private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);
// --snip--
}
这些特殊对象是在调用 ApplicationContext 的 refresh() 方法时添加到 resolvableDependencies 中的。可在 AbstractApplicationContext 的 refresh() 方法中看到:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// --snip--
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
// --snip--
}
}
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// --snip--
// BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
// --snip--
}
因此在注入诸如 ConfigurableApplicationContext 特殊类型的对象时,不能直接使用 getBean() 方法获取,而是应该从 resolvableDependencies 集合中获取。
@SneakyThrows
private static void testApplicationContext(DefaultListableBeanFactory beanFactory) {
DependencyDescriptor dd3 = new DependencyDescriptor(
Target.class.getDeclaredField("applicationContext"),
true
);
Field resolvableDependencies =
DefaultListableBeanFactory.class.getDeclaredField("resolvableDependencies");
resolvableDependencies.setAccessible(true);
Map<Class<?>, Object> dependencies =
(Map<Class<?>, Object>) resolvableDependencies.get(beanFactory);
for (Map.Entry<Class<?>, Object> entry : dependencies.entrySet()) {
// 左边类型 右边类型
if (entry.getKey().isAssignableFrom(dd3.getDependencyType())) {
System.out.println(entry.getValue());
break;
}
}
}
org.springframework.beans.factory.support.DefaultListableBeanFactory@7364985f: defining beans
[org.springframework.context.annotation.internalConfigurationAnnotationProcessor,
org.springframework.context.annotation.internalAutowiredAnnotationProcessor,
org.springframework.context.annotation.internalCommonAnnotationProcessor,
org.springframework.context.event.internalEventListenerProcessor,
org.springframework.context.event.internalEventListenerFactory,
a47_2,service3,service2,service1,dao2,dao1]; root of factory hierarchy
泛型类型
容器中 Dao 类型的 Bean 有多个,而依赖注入的是 Dao<Teacher> 类型的对象,因此需要判断容器中的 Bean 对象泛型类型是否为指定类型。判断逻辑可以使用 ContextAnnotationAutowireCandidateResolver 的 isAutowireCandidate() 方法:
@SneakyThrows
private static void testGeneric(DefaultListableBeanFactory beanFactory) {
DependencyDescriptor dd4 =
new DependencyDescriptor(Target.class.getDeclaredField("dao"), true);
Class<?> type = dd4.getDependencyType();
ContextAnnotationAutowireCandidateResolver resolver =
new ContextAnnotationAutowireCandidateResolver();
resolver.setBeanFactory(beanFactory);
// 循环所有的目标类型 Bean 名称
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
BeanDefinition bd = beanFactory.getMergedBeanDefinition(name);
// 对比 BeanDefinition 的泛型与 DependencyDescriptor 的泛型是否匹配
if (resolver.isAutowireCandidate(new BeanDefinitionHolder(bd, name), dd4)) {
System.out.println(name);
System.out.println(dd4.resolveCandidate(name, type, beanFactory));
}
}
}
dao2 indi.mofan.a47.A47_2$Dao2@74f0ea28
@Qualifier
当容器中存在多个相同类型的 Bean 对象,在执行依赖注入时可以使用 @Qualifier 注解来指定需要注入的 Bean 对象的名称。判断逻辑同样使用 ContextAnnotationAutowireCandidateResolver 的 isAutowireCandidate() 方法:
@SneakyThrows
private static void testQualifier(DefaultListableBeanFactory beanFactory) {
DependencyDescriptor dd5 =
new DependencyDescriptor(Target.class.getDeclaredField("service"), true);
Class<?> type = dd5.getDependencyType();
ContextAnnotationAutowireCandidateResolver resolver =
new ContextAnnotationAutowireCandidateResolver();
resolver.setBeanFactory(beanFactory);
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
BeanDefinition bd = beanFactory.getMergedBeanDefinition(name);
// DependencyDescriptor 对象中包含了 @Qualifier 注解信息
if (resolver.isAutowireCandidate(new BeanDefinitionHolder(bd, name), dd5)) {
System.out.println(name);
System.out.println(dd5.resolveCandidate(name, type, beanFactory));
}
}
}
service2 indi.mofan.a47.A47_2$Service2@1bd4fdd
@Primary
当容器中存在多个相同类型的 Bean 对象时,在执行依赖注入时除了可以使用 @Qualifier 注解外,还可以在被注入的 Bean 对象所在类上使用 @Primary 注解,指定执行依赖注入时使用的主要 Bean 对象。
如果 Bean 对象的所在类被 @Primary 注解标记,那么在构造 BeanDefinition 时就会记录这个信息。
通常情况下,@Primary 注解只有一个作用在同种类型的 Bean 上,存在多个时,Spring 依旧无法区分。
static class Target1 {
@Autowired
private Service service;
}
interface Service {
}
@Component("service1")
static class Service1 implements Service {
}
@Primary
@Component("service2")
static class Service2 implements Service {
}
@Component("service3")
static class Service3 implements Service {
}
@SneakyThrows
private static void testPrimary(DefaultListableBeanFactory beanFactory) {
DependencyDescriptor dd =
new DependencyDescriptor(Target1.class.getDeclaredField("service"), false);
Class<?> type = dd.getDependencyType();
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
if (beanFactory.getMergedBeanDefinition(name).isPrimary()) {
System.out.println("primary: " + name);
}
}
}
primary: service2
默认规则
当容器中存在多个相同类型的 Bean 对象时,除了使用 @Qualifier 或 @Primary 注解外,@Autowired 注解还支持按照成员变量名称进行匹配。
static class Target2 {
@Autowired
private Service service3;
}
interface Service {
}
@Component("service1")
static class Service1 implements Service {
}
@Primary
@Component("service2")
static class Service2 implements Service {
}
@Component("service3")
static class Service3 implements Service {
}
@SneakyThrows
private static void testDefault(DefaultListableBeanFactory beanFactory) {
DependencyDescriptor dd =
new DependencyDescriptor(Target2.class.getDeclaredField("service3"), false);
Class<?> type = dd.getDependencyType();
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
if (name.equals(dd.getDependencyName())) {
System.out.println("default: " + name);
}
}
}
default: service3
48. 事件 - 监听器
Spring 提供了事件发布 - 监听(订阅)机制,利用该机制可以令主要业务与附加业务解耦,是观察者模式的一种体现。
本节介绍事件的监听,事件的发布将在下一节进行介绍。
48.1 监听器的实现
实现
ApplicationListener接口
需求模拟:假设现有一个【主线业务】,要求在【主线业务】执行完成之后,执行【发送短信】、【发送邮件】。
常规写法是将这三种业务依次写在一个方法里,程序顺序执行它们,但这将增加代码的耦合性。
利用 Spring 提供的事件发布 - 监听(订阅)机制来解决这个问题可以大大地降低代码的耦合性。
首先定义事件的类型,用于后续的发布和监听,该事件类必须继承 ApplicationEvent 抽象类:
static class MyEvent extends ApplicationEvent {
private static final long serialVersionUID = -1541319641201302606L;
public MyEvent(Object source) {
super(source);
}
}
【主线业务】执行完成后,发送事件:
@Slf4j
@Component
static class MyService {
@Autowired
private ApplicationEventPublisher publisher;
public void doBusiness() {
log.debug("主线业务");
publisher.publishEvent(new MyEvent("MyService.doBusiness()"));
}
}
之后定义两个监听器,监听发布的事件,执行【发送短信】、【发送邮件】,定义的监听器需要实现 ApplicationListener 接口,并交由 Spring 管理:
@Slf4j
@Component
static class SmsApplicationListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.debug("发送短信");
}
}
@Slf4j
@Component
static class EmailApplicationListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.debug("发送邮件");
}
}
最后调用 doBusiness() 串联整个逻辑:
@Slf4j
@Configuration
public class A48_1 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A48_1.class);
context.getBean(MyService.class).doBusiness();
context.close();
}
// --snip--
}
运行 main() 方法后,控制台打印出:
[main] indi.mofan.a48.A48_1$MyService - 主线业务 [main] i.m.a.A48_1$EmailApplicationListener - 发送邮件 [main] i.m.a.A48_1$SmsApplicationListener - 发送短信
使用
@EventListener注解
监听器的实现除了实现 ApplicationListener 接口外,还可以使用 @EventListener 注解。比如:
@Slf4j
@Component
static class SmsService {
@EventListener
public void listener(MyEvent myEvent) {
log.debug("发送短信");
}
}
@Slf4j
@Component
static class EmailService {
@EventListener
public void listener(MyEvent myEvent) {
log.debug("发送邮件");
}
}
运行 main() 方法后,控制台输出相同的内容。
48.2 异步事件
从上文控制台输出的信息可知,事件都是在主线程 main 中被监听,都是同步执行的。
那怎么发送异步事件呢?
ApplicationEventPublisher 底层利用了 SimpleApplicationEventMulticaster 来发布事件,SimpleApplicationEventMulticaster 在发布事件时可以指定是否使用线程池,如果实现了线程池,那么就是异步事件,反之为同步。
因此可以先添加一个线程池 Bean,然后使 SimpleApplicationEventMulticaster 利用这个线程池 Bean 来发送事件:
@Bean
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
return executor;
}
/**
* 方法名称必须是 applicationEventMulticaster,才能对 Bean 进行覆盖
*/
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster(ThreadPoolTaskExecutor executor) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
// 使用线程池异步发送事件
multicaster.setTaskExecutor(executor);
return multicaster;
}
注意:SimpleApplicationEventMulticaster 类型的 Bean 的名称必须是 applicationEventMulticaster,这样才能覆盖原本未使用线程池的 Bean。
@Configuration
public class A48_2 {
@SneakyThrows
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A48_2.class);
context.getBean(MyService.class).doBusiness();
// 睡两秒,消息监听成功后才关闭容器
TimeUnit.SECONDS.sleep(2);
context.close();
}
// --snip--
}
运行 main() 方法后,控制台打印出:
[main] indi.mofan.a48.A48_2$MyService - 主线业务 [executor-1] indi.mofan.a48.A48_2$EmailService - 发送邮件 [executor-2] indi.mofan.a48.A48_2$SmsService - 发送短信
【主线业务】、【发送短信】和【发送邮件】都在不同线程中被执行。
【补充】
@Async实现异步事件
实际开发中,在监听器方法或监听器类上添加 @Async 注解即可实现异步事件。比如:
@Slf4j
@Component
static class SmsService {
@Async
@EventListener
public void listener(MyEvent myEvent) {
log.debug("发送短信");
}
}
@Slf4j
@Component
static class EmailService {
@Async
@EventListener
public void listener(MyEvent myEvent) {
log.debug("发送邮件");
}
}
运行 main() 方法后,根据控制台打印出的信息可知仍是同步消息,这是因为 没有开启异步支持。
在配置类上使用 @EnableAsync 注解,开启异步支持:
@Slf4j
@Configuration
@EnableAsync(proxyTargetClass = true)
public class TestAsync {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(TestAsync.class);
context.getBean(MyService.class).doBusiness();
context.close();
}
// --snip--
@Bean
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
return executor;
}
}
也可以注入自行实现的线程池 Bean,使用这个 Bean 来发布异步事件。当然也可以不注入,Spring 提供了默认的线程池。
建议: 使用 @EnableAsync 注解时,尽量指定 proxyTargetClass 的属性值为 true,采用 CGLib 动态代理,避免监听器类实现接口而监听器方法又未在基类中声明时,导致使用默认 JDK 动态代理失败。
运行 main() 方法,控制台打印出:
[main] indi.mofan.a48.TestAsync$MyService - 主线业务 [executor-2] indi.mofan.a48.TestAsync$SmsService - 发送短信 [executor-1] i.mofan.a48.TestAsync$EmailService - 发送邮件
48.3 自定义事件监听注解
无论是实现 ApplicationListener 接口,还是使用 @EventListener 注解,监听器类都需要交由 Spring 管理,那 Spring 是怎么实现事件的监听的呢?
以自定义时间监听注解为例,简单了解 Spring 的事件监听机制。
自定义 @MyListener 注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyListener {
}
定义事件类型 MyEvent:
static class MyEvent extends ApplicationEvent {
private static final long serialVersionUID = -6388410688691384516L;
public MyEvent(Object source) {
super(source);
}
}
定义两个监听器类,监听 MyEvent 事件:
@Slf4j
@Component
static class SmsService {
@MyListener
public void listener(MyEvent myEvent) {
log.debug("发送短信");
}
}
@Slf4j
@Component
static class EmailService {
@MyListener
public void listener(MyEvent myEvent) {
log.debug("发送邮件");
}
}
再定义 MyService,在该业务类中的 doBusiness() 方法发送 MyEvent 事件:
@Slf4j
@Component
static class MyService {
@Autowired
private ApplicationEventPublisher publisher;
public void doBusiness() {
log.debug("主线业务");
publisher.publishEvent(new MyEvent("MyService.doBusiness()"));
}
}
接下来就是解析 @MyListener 注解了:
- 首先需要获取容器中的所有 Bean 对象
- 查看这些 Bean 对象中是否存在被
@MyListener注解标记的方法 - 将被
@MyListener注解标记的方法转换成事件监听器添加到 Spring 容器中(适配器模式),这些事件监听器需要判断监听的事件类型是否与原始方法的参数类型一致,一致的情况下才执行方法 - 在第一步中需要拿到容器中的所有 Bean 对象,因此前面的逻辑要在 Spring 中所有单例 Bean 初始化完成后才执行,可以使用
SmartInitializingSingleton接口实现
@Bean
public SmartInitializingSingleton smartInitializingSingleton(ConfigurableApplicationContext context) {
// Spring 中所有单例 Bean 初始化完成后调用此处理器
return () -> {
for (String name : context.getBeanDefinitionNames()) {
Object bean = context.getBean(name);
for (Method method : bean.getClass().getMethods()) {
if (method.isAnnotationPresent(MyListener.class)) {
// 添加事件监听器
context.addApplicationListener((event) -> {
// 监听器方法的事件类型
Class<?> eventType = method.getParameterTypes()[0];
if (eventType.isAssignableFrom(event.getClass())) {
try {
method.invoke(bean, event);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
}
}
};
}
@Configuration
public class A48_3 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A48_3.class);
context.getBean(MyService.class).doBusiness();
context.close();
}
// --snip--
}
[main] indi.mofan.a48.A48_3$MyService - 主线业务 [main] indi.mofan.a48.A48_3$EmailService - 发送邮件 [main] indi.mofan.a48.A48_3$SmsService - 发送短信
48.4 【补充】监听器执行顺序
当一个事件有多个对应的监听器时,这些监听器的执行顺序是不确定的。
如果需要监听器按指定的顺序执行,可以使监听器类实现 SmartApplicationListener 接口,重写 getOrder() 方法,指定监听器优先级。除此之外,使用 @Order 注解、在实现 ApplicationListener 接口的基础上再实现 Ordered 接口也能实现相同的功能。
@Slf4j
@Component
static class SmsService1 {
@Order(1)
@EventListener
public void listener(MyEvent myEvent) {
log.debug("发送短信-1");
}
}
@Slf4j
@Component
static class EmailService1 {
@Order(2)
@EventListener
public void listener(MyEvent myEvent) {
log.debug("发送邮件-1");
}
}
@Slf4j
@Component
static class SmsService2 implements ApplicationListener<MyEvent>, Ordered {
@Override
public int getOrder() {
return 3;
}
@Override
public void onApplicationEvent(MyEvent event) {
log.debug("发送短信-2");
}
}
@Slf4j
@Component
static class EmailService2 implements SmartApplicationListener {
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return MyEvent.class.equals(eventType);
}
@Override
public int getOrder() {
return 4;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
log.debug("发送邮件-2");
}
}
[main] i.m.a.TestSmartApplicationListener$MyService - 主线业务 [main] i.m.a.TestSmartApplicationListener$SmsService1 - 发送短信-1 [main] i.m.a.TestSmartApplicationListener$EmailService1 - 发送邮件-1 [main] i.m.a.TestSmartApplicationListener$SmsService2 - 发送短信-2 [main] i.m.a.TestSmartApplicationListener$EmailService2 - 发送邮件-2
48.5 【补充】事件的传播
当 Spring 容器嵌套 Spring 容器时,通过子容器发布事件,能够在父容器监听到。
public static void main(String[] args) {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.registerBean(MyListener.class);
parent.refresh();
AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext();
child.setParent(parent);
child.refresh();
// 通过子容器发布事件,能够在父容器监听到
child.publishEvent(new MyEvent("子容器发送的事件..."));
}
static class MyEvent extends ApplicationEvent {
private static final long serialVersionUID = -7002403082731659626L;
public MyEvent(String source) {
super(source);
}
}
@Slf4j
static class MyListener {
@EventListener
public void listener(MyEvent myEvent) {
log.debug(String.valueOf(myEvent.getSource()));
}
}
[main] i.m.a.EventPropagationTest$MyListener - 子容器发送的事件...
48.6 【补充】带泛型的事件
现有一个事件类 MutationEvent,接收一个泛型参数:
@Getter
static class MutationEvent<T> extends ApplicationEvent {
private static final long serialVersionUID = -2718823625228147843L;
private final T source;
private final String type;
public MutationEvent(T data, String type) {
super(data);
this.source = data;
this.type = type;
}
}
事件对应的监听器 MutationEventListener:
@Getter
@AllArgsConstructor
static class Pizza {
private final String name;
private final double price;
}
@Getter
@AllArgsConstructor
static class ChineseHamburger {
private final double price;
private final String size;
}
static class MutationEventListener {
@EventListener
public void handlePizza(MutationEvent<Pizza> event) {
System.out.println("监听到 Pizza...");
System.out.println("类型是: " + event.getType());
Pizza pizza = event.getSource();
System.out.println("Pizza 名称为: " + pizza.getName()
+ ", 价格为: " + pizza.getPrice());
}
@EventListener
public void handleChineseHamburger(MutationEvent<ChineseHamburger> event) {
System.out.println("监听到肉夹馍...");
System.out.println("类型是: " + event.getType());
ChineseHamburger hamburger = event.getSource();
System.out.println("肉夹馍的价格是: " + hamburger.getPrice()
+ ", 大小是: " + hamburger.getSize());
}
}
尝试发布 MutationEvent<Pizza> 类型的事件,看看监听器是否能监听到:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(MutationEventListener.class);
context.refresh();
Pizza pizza = new Pizza("NewYorkPizza", 25);
context.publishEvent(new MutationEvent<>(pizza, "ONE"));
}
运行 main() 方法后,抛出 ClassCastException 异常,提示无法将 Pizza 转换成 ChineseHamburger,事件监听失败。
由于泛型擦除,无法通过事件真正的内部对象类型来分发事件,为了解决这个问题,需要使类实现 ResolvableTypeProvider 接口。
如果未实现 ResolvableTypeProvider 接口:
- 但实现了
ApplicationEvent接口,尽管在监听器方法和发布事件时都指定了泛型参数信息,但所有的监听器方法都会被执行,由此可能产生ClassCastException - 也未实现
ApplicationEvent接口,就算发送的泛型事件的内部对象类型与监听器指定的泛型事件的内部对象类型一样,也不会监听成功
@Getter
static class MutationEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
// --snip--
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forInstance(this.source));
}
}
再次运行 main() 方法后,不再抛出异常,控制台打印出:
监听到 Pizza... 类型是: ONE Pizza 名称为: NewYorkPizza, 价格为: 25.0
再发布泛型参数类型为 ChineseHamburger 的事件:
public static void main(String[] args) {
// --snip--
ChineseHamburger hamburger = new ChineseHamburger(18, "M");
context.publishEvent(new MutationEvent<>(hamburger, "TWO"));
}
监听到 Pizza... 类型是: ONE Pizza 名称为: NewYorkPizza, 价格为: 25.0 监听到肉夹馍... 类型是: TWO 肉夹馍的价格是: 18.0, 大小是: M
49. 事件 - 发布器
49.1 自定义事件发布
前文说到,事件的发布使用了 SimpleApplicationEventMulticaster,它的顶层接口是 ApplicationEventMulticaster,尝试自定义 ApplicationEventMulticaster 的实现类,实现事件的发布。
ApplicationEventMulticaster 接口的抽象方法有很多,本节只实现重要方法,采用默认适配器处理:
abstract static class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster {
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
}
@Override
public void addApplicationListenerBean(String listenerBeanName) {
}
@Override
public void removeApplicationListener(ApplicationListener<?> listener) {
}
@Override
public void removeApplicationListenerBean(String listenerBeanName) {
}
@Override
public void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate) {
}
@Override
public void removeApplicationListenerBeans(Predicate<String> predicate) {
}
@Override
public void removeAllListeners() {
}
@Override
public void multicastEvent(ApplicationEvent event) {
}
@Override
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
}
}
需要实现的方法有两个:
addApplicationListenerBean():收集容器中所有的监听器multicastEvent():发布事件
收集监听器时,需要获取监听器支持的事件类型,将原始的监听器封装为支持事件类型检查的监听器,这种监听器在发布事件时,使用线程池支持发布异步事件。
发布事件时,遍历容器中所有监听器,当监听器支持的事件类型与发布的事件类型一致时才发布事件。
@Bean
@SuppressWarnings("all")
public ApplicationEventMulticaster applicationEventMulticaster(ConfigurableApplicationContext context, ThreadPoolTaskExecutor executor) {
return new AbstractApplicationEventMulticaster() {
private List<GenericApplicationListener> listeners = new ArrayList<>();
// 收集监听器
public void addApplicationListenerBean(String name) {
ApplicationListener listener = context.getBean(name, ApplicationListener.class);
// System.out.println(listener);
// 获取该监听器支持的事件类型
ResolvableType type = ResolvableType.forClass(listener.getClass()).getInterfaces()[0].getGeneric();
// System.out.println(type);
// 将原始的 listener 封装为支持事件类型检查的 listener
GenericApplicationListener genericApplicationListener = new GenericApplicationListener() {
/**
* 是否支持某种事件类型
* @param eventType 真实的事件类型
* @return 是否支持某事件类型
*/
public boolean supportsEventType(ResolvableType eventType) {
return type.isAssignableFrom(eventType);
}
public void onApplicationEvent(ApplicationEvent event) {
executor.submit(() -> listener.onApplicationEvent(event));
}
};
listeners.add(genericApplicationListener);
}
// 发布事件
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
for (GenericApplicationListener listener : listeners) {
if (listener.supportsEventType(ResolvableType.forClass(event.getClass()))) {
listener.onApplicationEvent(event);
}
}
}
};
}
49.2 非 ApplicationEvent 事件
如果发送的事件不是 ApplicationEvent 类型时,Spring 会将其包装为 PayloadApplicationEvent 并用泛型技术解析事件对象的原始类型。
包装为 PayloadApplicationEvent 类型的逻辑无需实现,直接使用即可。
interface Inter {
}
static class Bean1 implements Inter {
}
@Bean
@SuppressWarnings("all")
public ApplicationEventMulticaster applicationEventMulticaster(ConfigurableApplicationContext context, ThreadPoolTaskExecutor executor) {
return new A49.AbstractApplicationEventMulticaster() {
private List<GenericApplicationListener> listeners = new ArrayList<>();
{
listeners.add(new GenericApplicationListener() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof PayloadApplicationEvent) {
PayloadApplicationEvent<?> payloadApplicationEvent =
(PayloadApplicationEvent<?>) event;
System.out.println(payloadApplicationEvent.getPayload());
}
}
@Override
public boolean supportsEventType(ResolvableType eventType) {
System.out.println(eventType);
// eventType --> PayloadApplicationEvent<Object>
// eventType --> PayloadApplicationEvent<String>
return (Inter.class.isAssignableFrom(eventType.getGeneric().toClass()));
}
});
}
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, null);
}
@SuppressWarnings("all")
@Override
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
listeners.stream().filter(applicationListener -> {
if (eventType == null) {
return false;
}
if (applicationListener instanceof GenericApplicationListener) {
GenericApplicationListener listener =
(GenericApplicationListener) applicationListener;
return listener.supportsEventType(eventType);
}
return false;
}).forEach(listener -> listener.onApplicationEvent(event));
}
};
}
@Configuration
public class TestEventPublisher {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(TestEventPublisher.class);
context.publishEvent(new Object());
context.publishEvent("aaaa");
context.publishEvent(new Bean1());
}
}
运行 main() 方法后,控制台打印出:
org.springframework.context.PayloadApplicationEvent<java.lang.Object> org.springframework.context.PayloadApplicationEvent<java.lang.String> org.springframework.context.PayloadApplicationEvent<indi.mofan.a49.TestEventPublisher$Bean1> indi.mofan.a49.TestEventPublisher$Bean1@7ba18f1b
49.3 【补充】Spring 内置事件
Spring 的内置事件有很多,在此列举几个与 Spring 容器启动相关的事件,如果需要在 Spring 容器启动的某个时刻进行一些操作,就可以监听这些事件:
| 事件类型 | 触发时机 |
|---|---|
| ContextRefreshedEvent | 在调用 ConfigurableApplicationContext 接口中的 refresh() 方法时触发 |
| ContextStartedEvent | 在调用 ConfigurableApplicationContext 的 start() 方法时触发 |
| ContextStoppedEvent | 在调用 ConfigurableApplicationContext 的 stop() 方法时触发 |
| ContextClosedEvent | 当 ApplicationContext 被关闭时触发该事件,也就是调用 close() 方法触发 |