封面画师:T5-茨舞(微博) 封面ID:104959772
参考视频:黑马程序员Spring视频教程,全面深度讲解spring5底层原理
源码仓库:mofan212/advanced-spring (github.com)
43. FactoryBean
FactoryBean
是一个接口,可以实现该接口,并指定一个泛型,在重写的方法指定泛型类型对象的创建,然后将实现类交由 Spring 管理,最后 Spring 容器中会增加泛型类型的 Bean。这个 Bean 并不是完全受 Spring 管理,或者说部分受 Spring 管理。
为什么这么说呢?
首先定义一个 Bean2
,交由 Spring 管理,但它不是重点:
1 2 3 @Component public class Bean2 {}
然后定义 Bean1
,它未交由 Spring 管理,但是在其内部注入了 Bean2
、定义初始化方法、实现 Aware
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @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
。
1 2 3 4 5 6 7 8 9 10 @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
初始化前后打印日志信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @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
接口中有三个可以被重写的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 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 对象的类型,以便可以通过类型从容器中获取 Bean
isSingleton()
每次获取的 Bean 对象是否是单例的
从容器中获取 Bean 时可以通过名称获取、可以通过类型获取、也可以通过名称和类型一起获取。如果重写的 getObjectType()
方法返回了 null
,那么 仅仅 类型从容器中获取 Bean 时,将抛出 NoSuchBeanDefinitionException
异常,并提示没有指定类型的 Bean。
如果重写的 isSingleton()
方法返回 true
,那么每次充容器中获取 Bean 对象都是同一个,反之则不是。
注意: 由 FactoryBean
构造的单例 Bean 不会存放在 DefaultSingletonBeanRegistry
的 singletonFactories
中,而是在 AbstractAutowireCapableBeanFactory
的 factoryBeanInstanceCache
中。
获取 FactoryBean
类型的 Bean
肯定不能简单地通过名称获取,那会返回其泛型参数类型的 Bean,那通过类型获取呢?比如:
1 context.getBean(Bean1FactoryBean.class)
答案是可行的。
除此之外,还可以在名称前添加 &
,然后通过名称来获取(有点指针的味道?):
1 context.getBean("&bean1" )
44. @Indexed
Spring 在进行组件扫描时,会遍历项目中依赖的所有 Jar 包中类路径下所有的文件,找到被 @Component
及其衍生注解标记的类,然后把它们组装成 BeanDefinition 添加到 Spring 容器中。
如果扫描的返回过大,势必会大大地影响项目启动速度。
为了优化扫描速度,引入以下依赖,Spring 将扫描过程提前到编译期:
1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context-indexer</artifactId > <optional > true</optional > </dependency >
现有如下类信息:
1 2 3 4 5 6 7 8 9 10 11 @Component public class Bean1 {} @Component public class Bean2 {} @Component public class Bean3 {}
这几个类都与 A44
存放于同一包路径下:
1 2 3 4 5 6 7 8 9 10 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
文件里有以下信息:
1 2 3 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
注解中:
1 2 3 4 5 6 7 @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 的代理中,依赖注入和初始化针对的是目标对象,代理对象和目标对象是两个对象,两者的成员变量不会共享。
确保项目中已导入以下依赖:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency >
依赖注入和初始化针对的是目标对象
现有如下类信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @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; } }
1 2 3 @Component public class Bean2 {}
为 Bean1
中的每个方法定制一个前置通知:
1 2 3 4 5 6 7 8 9 10 11 @Aspect @Component public class MyAspect { @Before("execution(* indi.mofan.a45.Bean1.*(..))") public void before () { System.out.println("before" ); } }
有一 SpringBoot 主启动类,它与 Bean1
、Bean2
和 MyAspect
在同一包路径下,确保它们能被自动添加到 Spring 容器中:
1 2 3 4 5 6 7 @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 容器中获取的对象将是代理对象:
1 2 3 4 5 6 7 8 9 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()
方法 都被增强。
代理对象与目标对象的成员变量不共享
尝试打印代理对象和目标对象的成员变量信息(直接访问,不使用方法):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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
由于依赖注入和初始化只针对目标对象,因此代理对象中的成员变量的值都是初始值。
在实际应用过程中,不会直接去访问成员变量,而是通过方法去访问:
1 2 3 4 5 6 7 8 9 10 11 12 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
中增加几个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Component public class Bean1 { 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 方法" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @SneakyThrows public static void main (String[] args) { 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
类如下:
1 2 3 4 5 6 public class Bean1 { @Value("${JAVA_HOME}") private String home; @Value("18") private int age; }
需要解析 @Value("${JAVA_HOME}")
和 @Value("18")
的值,其中 JAVA_HOME
以系统环境变量填充,18
为整型。
解析分为两步:
获取 @Value
注解中 value
属性值;
解析属性值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @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 ); 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @SneakyThrows private static void test2 (AnnotationConfigApplicationContext context, ContextAnnotationAutowireCandidateResolver resolver) { DependencyDescriptor dd1 = new DependencyDescriptor (Bean1.class.getDeclaredField("age" ), false ); 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 表达式的解析
假设有如下几个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 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。
为了解析成功,需要在转换前解析 #{}
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @SneakyThrows public static void main (String[] args) { 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 ); 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
类如下:
1 2 3 4 static class Bean1 { @Autowired private Bean2 bean2; }
需要被注入的对象所在类:
1 2 3 @Component("bean2") static class Bean2 {}
从容器中获取需要被注入的 Bean 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 @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
进行修改:
1 2 3 4 5 6 7 8 static class Bean1 { @Autowired public void setBean2 (Bean2 bean2) { this .bean2 = bean2; } }
根据 setBean2()
方法的 Bean2
类型参数进行注入:
1 2 3 4 5 6 7 8 9 @SneakyThrows public static void main (String[] args) { 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
进行修改:
1 2 3 4 5 6 static class Bean1 { @Autowired private Optional<Bean2> bean3; }
如果直接按照以下方式获取 DependencyDescriptor
对象:
1 2 DependencyDescriptor dd3 = new DependencyDescriptor (Bean1.class.getDeclaredField("bean3" ), false );
其 dd3.getDependencyType()
方法将返回 Optional
的 Class
对象,这显然是不对的。
Spring 提供为 DependencyDescriptor
提供了解决这个问题的方法,即“增加嵌套等级”来获取内嵌类型:
1 dd3.increaseNestingLevel();
执行 increaseNestingLevel()
方法后,dd3.getDependencyType()
方法返回的 Bean2
的 Class
对象。
因此注入 Optional<Bean2>
类型的成员变量应该按照:
1 2 3 4 5 6 7 8 9 10 11 12 @SneakyThrows public static void main (String[] args) { 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
进行修改:
1 2 3 4 5 6 static class Bean1 { @Autowired private ObjectFactory<Bean2> bean4; }
注入 ObjectFactory<Bean2>
类型的对象与注入 Optional<Bean2>
类型的对象类似,只不过 ObjectFactory
提供了 延迟注入 的能力,也就是说 Bean2
对象不会立即被注入,而是在需要时才被注入。
ObjectFactory
是一个函数式接口:
1 2 3 4 @FunctionalInterface public interface ObjectFactory <T> { T getObject () throws BeansException; }
注入的应该是 ObjectFactory
对象,在调用该对象的 getObject()
方法时,Bean2
对象才被注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 @SneakyThrows public static void main (String[] args) { 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
注解:
1 2 3 4 5 6 7 static class Bean1 { @Autowired @Lazy private Bean2 bean2; }
对于 @Lazy
注解标记的成员变量,注入的对象不再是目标对象,而是其代理对象,因此不能使用 DefaultListableBeanFactory
对象的 doResolveDependency()
方法来获取注入的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @SneakyThrows public static void main (String[] args) { DependencyDescriptor dd5 = new DependencyDescriptor (Bean1.class.getDeclaredField("bean2" ), false ); ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver (); resolver.setBeanFactory(beanFactory); 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
中看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver { @Override @Nullable public Object getLazyResolutionProxyIfNecessary (DependencyDescriptor descriptor, @Nullable String beanName) { return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null ); } protected boolean isLazy (DependencyDescriptor descriptor) { for (Annotation ann : descriptor.getAnnotations()) { Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class); if (lazy != null && lazy.value()) { return true ; } } MethodParameter methodParam = descriptor.getMethodParameter(); if (methodParam != null ) { } return false ; } protected Object buildLazyResolutionProxy (final DependencyDescriptor descriptor, final @Nullable String beanName) { return pf.getProxy(dlbf.getBeanClassLoader()); } }
补充:包装为 Provider<Bean2>
Provider
接口是由 JSR-330 提出,要想使用此接口,需要导入以下依赖:
1 2 3 4 5 6 <dependency > <groupId > javax.inject</groupId > <artifactId > javax.inject</artifactId > <version > 1</version > </dependency >
对 Bean1
进行修改:
1 2 3 4 5 6 static class Bean1 { @Autowired private Provider<Bean2> bean5; }
注入 Provider
类型的对象与注入 ObjectFactory<Bean2>
类型的对象极其相似,Provider
也提供了 延迟注入 的能力,注入的是 Provider
对象,在调用该对象的 get()
方法时,Bean2
对象才被注入:
1 2 3 4 5 6 7 8 9 10 11 12 @SneakyThrows public static void main (String[] args) { 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()
方法中看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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()) { } else if (ObjectFactory.class == descriptor.getDependencyType() || ObjectProvider.class == descriptor.getDependencyType()) { } else if (javaxInjectProviderClass == descriptor.getDependencyType()) { } else { } }
47.2 类型匹配细节
无论是 @Value
注入,还是 @Autowired
注入,最终都会调用 DefaultListableBeanFactory#doResolveDependency()
方法。
现有如下几个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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
,对其进行依赖注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 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 数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @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
的泛型类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @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()
方法中看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Nullable private Object resolveMultipleBeans (DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) { Class<?> type = descriptor.getDependencyType(); if (descriptor instanceof StreamDependencyDescriptor) { } else if (type.isArray()) { } else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { } else if (Map.class == type) { } else { return null ; } }
从源码中可以看到,@Autowired
还支持 Map
类型数据的注入,此时注入的 Map 的 key 是 Bean 的名称,value 是 Bean 对象,这种方式常常配合策略模式使用。需要注意的是,只支持注入 Map
接口,不支持其子类。
特殊类型 ConfigurableApplicationContext
ConfigurableApplicationContext
是 ApplicationContext
接口的子接口。
需要注意的是,在 Spring 容器中并不存在 ConfigurableApplicationContext
类型、或 ApplicationContext
类型的 Bean。
Spring 容器中的所有单例 Bean 对象存放在 DefaultListableBeanFactory
中,在 DefaultListableBeanFactory
父类 DefaultSingletonBeanRegistry
中有一成员变量:
1 2 3 4 5 6 7 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { private final Map<String, Object> singletonObjects = new ConcurrentHashMap <>(256 ); }
singletonObjects
用于存放 Spring 容器中的所有单例 Bean 对象。
类似 ApplicationContext
、BeanFactory
类型的对象则是放在 DefaultListableBeanFactory
中的 resolvableDependencies
中:
1 2 3 4 5 6 7 8 public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory , BeanDefinitionRegistry, Serializable { private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap <>(16 ); }
这些特殊对象是在调用 ApplicationContext
的 refresh()
方法时添加到 resolvableDependencies
中的。可在 AbstractApplicationContext
的 refresh()
方法中看到:
1 2 3 4 5 6 7 8 9 10 public void refresh () throws BeansException, IllegalStateException { synchronized (this .startupShutdownMonitor) { prepareBeanFactory(beanFactory); } }
1 2 3 4 5 6 7 8 9 10 11 12 protected void prepareBeanFactory (ConfigurableListableBeanFactory beanFactory) { beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this ); beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this ); beanFactory.registerResolvableDependency(ApplicationContext.class, this ); }
因此在注入诸如 ConfigurableApplicationContext
特殊类型的对象时,不能直接使用 getBean()
方法获取,而是应该从 resolvableDependencies
集合中获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @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()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @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); for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) { BeanDefinition bd = beanFactory.getMergedBeanDefinition(name); 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()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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); 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 依旧无法区分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 {}
1 2 3 4 5 6 7 8 9 10 11 @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
注解还支持按照成员变量名称进行匹配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 {}
1 2 3 4 5 6 7 8 9 10 11 @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
抽象类:
1 2 3 4 5 6 7 static class MyEvent extends ApplicationEvent { private static final long serialVersionUID = -1541319641201302606L ; public MyEvent (Object source) { super (source); } }
【主线业务】执行完成后,发送事件:
1 2 3 4 5 6 7 8 9 10 11 12 @Slf4j @Component static class MyService { @Autowired private ApplicationEventPublisher publisher; public void doBusiness () { log.debug("主线业务" ); publisher.publishEvent(new MyEvent ("MyService.doBusiness()" )); } }
之后定义两个监听器,监听发布的事件,执行【发送短信】、【发送邮件】,定义的监听器需要实现 ApplicationListener
接口,并交由 Spring 管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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()
串联整个逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 @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(); } }
运行 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
注解。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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 来发送事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Bean public ThreadPoolTaskExecutor executor () { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor (); executor.setCorePoolSize(3 ); executor.setMaxPoolSize(10 ); executor.setQueueCapacity(100 ); return executor; } @Bean public SimpleApplicationEventMulticaster applicationEventMulticaster (ThreadPoolTaskExecutor executor) { SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster (); multicaster.setTaskExecutor(executor); return multicaster; }
注意 :SimpleApplicationEventMulticaster
类型的 Bean 的名称必须是 applicationEventMulticaster
,这样才能覆盖原本未使用线程池的 Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @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(); } }
运行 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
注解即可实现异步事件。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @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
注解,开启异步支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @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(); } @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
注解:
1 2 3 4 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface MyListener {}
定义事件类型 MyEvent
:
1 2 3 4 5 6 7 static class MyEvent extends ApplicationEvent { private static final long serialVersionUID = -6388410688691384516L ; public MyEvent (Object source) { super (source); } }
定义两个监听器类,监听 MyEvent
事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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
事件:
1 2 3 4 5 6 7 8 9 10 11 @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
接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Bean public SmartInitializingSingleton smartInitializingSingleton (ConfigurableApplicationContext context) { 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); } } }); } } } }; }
1 2 3 4 5 6 7 8 9 10 11 @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(); } }
[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
接口也能实现相同的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @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 容器时,通过子容器发布事件,能够在父容器监听到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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
,接收一个泛型参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @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>
类型的事件,看看监听器是否能监听到:
1 2 3 4 5 6 7 8 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
接口,就算发送的泛型事件的内部对象类型与监听器指定的泛型事件的内部对象类型一样,也不会监听成功。
1 2 3 4 5 6 7 8 9 10 @Getter static class MutationEvent <T> extends ApplicationEvent implements ResolvableTypeProvider { @Override public ResolvableType getResolvableType () { return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(this .source)); } }
再次运行 main()
方法后,不再抛出异常,控制台打印出:
监听到 Pizza...
类型是: ONE
Pizza 名称为: NewYorkPizza, 价格为: 25.0
再发布泛型参数类型为 ChineseHamburger
的事件:
1 2 3 4 5 6 public static void main (String[] args) { 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
接口的抽象方法有很多,本节只实现重要方法,采用默认适配器处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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()
:发布事件
收集监听器时,需要获取监听器支持的事件类型,将原始的监听器封装为支持事件类型检查的监听器,这种监听器在发布事件时,使用线程池支持发布异步事件。
发布事件时,遍历容器中所有监听器,当监听器支持的事件类型与发布的事件类型一致时才发布事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @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); ResolvableType type = ResolvableType.forClass(listener.getClass()).getInterfaces()[0 ].getGeneric(); GenericApplicationListener genericApplicationListener = new GenericApplicationListener () { 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
类型的逻辑无需实现,直接使用即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 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); 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)); } }; }
1 2 3 4 5 6 7 8 9 10 11 @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()
方法触发