亲宝软件园·资讯

展开

Spring(七)核心容器 - 钩子接口

龙四丶 人气:0
[toc] # 前言 Spring 提供了非常多的扩展接口,官方将这些接口称之为钩子,这些钩子会在特定的时间被回调,以此来增强 Spring 功能,众多优秀的框架也是通过扩展这些接口,来实现自身特定的功能,如 SpringBoot、mybatis 等。 # 1、Aware 系列接口 Aware 从字面意思理解就是“知道”、“感知”的意思,是用来获取 Spring 内部对象的接口。Aware 自身是一个顶级接口,它有一系列子接口,在一个 Bean 中实现这些子接口并重写里面的 set 方法后,Spring 容器启动时,就会回调该 set 方法,而相应的对象会通过方法参数传递进去。我们以其中的 ApplicationContextAware 接口为例。 **ApplicationContextAware** 大部分 Aware 系列接口都有一个规律,它们以对象名称为前缀,获取的就是该对象,所以 ApplicationContextAware 获取的对象是 ApplicationContext 。 ```java public interface ApplicationContextAware extends Aware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; } ``` ApplicationContextAware 源码非常简单,其继承了 Aware 接口,并定义一个 set 方法,参数就是 ApplicationContext 对象,当然,其它系列的 Aware 接口也是类似的定义。其具体使用方式如下: ```java public class Test implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } ``` 在 Spring 启动过程中,会回调 setApplicationContext 方法,并传入 ApplicationContext 对象,之后就可对该对象进行操作。其它系列的 Aware 接口也是如此使用。具体的调用时机会在后面详细介绍。 以下是几种常用的 Aware 接口: - BeanFactoryAware:获取 BeanFactory 对象,它是基础的容器接口。 - BeanNameAware:获取 Bean 的名称。 - EnvironmentAware:获取 Environment 对象,它表示整个的运行时环境,可以设置和获取配置属性。 - ApplicationEventPublisherAware:获取 ApplicationEventPublisher 对象,它是用来发布事件的。 - ResourceLoaderAware:获取 ResourceLoader 对象,它是获取资源的工具。 # 2、InitializingBean InitializingBean 是一个可以在 Bean 的生命周期执行自定义操作的接口,凡是实现该接口的 Bean,在初始化阶段都可以执行自定义的操作。 ```java public interface InitializingBean { void afterPropertiesSet() throws Exception; } ``` 从 InitializingBean 源码中可以看出它有一个 afterPropertiesSet 方法,当一个 Bean 实现该接口时,在 Bean 的初始化阶段,会回调 afterPropertiesSet 方法,其初始化阶段具体指 Bean 设置完属性之后。 该接口使用方式如下: ```java @Component public class Test implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("Test 执行初始化"); } } ``` 定义启动类: ```java @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } } ``` 结果: ``` ... 2020-02-24 08:43:41.435 INFO 26193 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpTraceFilter' to: [/*] 2020-02-24 08:43:41.435 INFO 26193 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'webMvcMetricsFilter' to: [/*] Test 执行初始化 2020-02-24 08:43:41.577 INFO 26193 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2020-02-24 08:43:41.756 INFO 26193 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@23529fee: startup date [Mon Feb 24 08:43:39 CST 2020]; root of context hierarchy ... ``` 最终,afterPropertiesSet 方法被执行并打印输出语句。 # 3、BeanPostProcessor BeanPostProcessor 和 InitializingBean 有点类似,也是可以在 Bean 的生命周期执行自定义操作,一般称之为 Bean 的后置处理器,不同的是, BeanPostProcessor 可以在 Bean 初始化前、后执行自定义操作,且针对的目标也不同,InitializingBean 针对的是实现 InitializingBean 接口的 Bean,而 BeanPostProcessor 针对的是所有的 Bean。 ```java public interface BeanPostProcessor { // Bean 初始化前调用 default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } // Bean 初始化后调用 default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } } ``` 所有的 Bean 在初始化前、后都会回调接口中的 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法,入参是当前正在初始化的 Bean 对象和 BeanName。值得注意的是 Spring 内置了非常多的 BeanPostProcessor ,以此来完善自身功能,这部分会在后面文章深入讨论。 这里通过自定义 BeanPostProcessor 来了解该接口的使用方式: ```java // 一般自定义的 BeanPostProcessor 命名格式都是以 BeanPostProcessor 为后缀。 @Component public class TestBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println(beanName + " 初始化前执行操作"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println(beanName + " 初始化后执行操作"); return bean; } } ``` 启动类: ```java @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class); } } ``` 结果: ``` ... 2020-02-24 23:37:08.949 INFO 26615 --- [ main] com.loong.diveinspringboot.test.Main : No active profile set, falling back to default profiles: default 2020-02-24 23:37:08.994 INFO 26615 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2133814f: startup date [Mon Feb 24 23:37:08 CST 2020]; root of context hierarchy 2020-02-24 23:37:09.890 INFO 26615 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode! org.springframework.context.event.internalEventListenerProcessor 初始化前执行操作 org.springframework.context.event.internalEventListenerProcessor 初始化后执行操作 org.springframework.context.event.internalEventListenerFactory 初始化前执行操作 org.springframework.context.event.internalEventListenerFactory 初始化后执行操作 main 初始化前执行操作 main 初始化后执行操作 test 初始化前执行操作 Test 执行初始化 test 初始化后执行操作 ... org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration初始化前执行操作 2020-02-24 23:37:13.097 INFO 26615 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2020-02-24 23:37:13.195 INFO 26615 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2020-02-24 23:37:13.207 INFO 26615 --- [ main] com.loong.diveinspringboot.test.Main : Started Main in 4.657 seconds (JVM running for 5.078) ... ``` 可以看到,输出的结果中不仅包括自定义的 Test,还包括 Spring 内部的 Bean 。 BeanPostProcessor 使用场景其实非常多,因为它可以获取正在初始化的 Bean 对象,然后可以依据该 Bean 对象做一些定制化的操作,如:判断该 Bean 是否为某个特定对象、获取 Bean 的注解元数据等。事实上,Spring 内部也正是这样使用的,这部分也会在后面章节详细讨论。 # 4、BeanFactoryPostProcessor BeanFactoryPostProcessor 是 Bean 工厂的后置处理器,一般用来修改上下文中的 BeanDefinition,修改 Bean 的属性值。 ``` public interface BeanFactoryPostProcessor { // 入参是一个 Bean 工厂:ConfigurableListableBeanFactory。该方法执行时,所有 BeanDefinition 都已被加载,但还未实例化 Bean。 // 可以对其进行覆盖或添加属性,甚至可以用于初始化 Bean。 void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; } ``` BeanFactoryPostProcessor 源码非常简单,其提供了一个 postProcessBeanFactory 方法,当所有的 BeanDefinition 被加载时,该方法会被回调。值得注意的是,Spring 内置了许多 BeanFactoryPostProcessor 的实现,以此来完善自身功能。 这里,我们来实现一个自定义的 BeanFactoryPostProcessor: ```java @Component public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { String beanNames[] = beanFactory.getBeanDefinitionNames(); for (String beanName : beanNames) { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); System.out.println(beanDefinition); } } } ``` 主要是通过 Bean 工厂获取所有的 BeanDefinition 。 接着启动程序: ```java @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class); } } ``` 结果: ``` 2020-02-25 21:46:00.754 INFO 28907 --- [ main] ConfigServletWebServerApplicationContext : ... 2020-02-25 21:46:01.815 INFO 28907 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : ... Root bean: class [org.springframework.context.annotation.ConfigurationClassPostProcessor]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null Root bean: class [org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null ... 2020-02-25 21:46:04.926 INFO 28907 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : ... 2020-02-25 21:46:04.989 INFO 28907 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : ... 2020-02-25 21:46:04.993 INFO 28907 --- [ main] com.loong.diveinspringboot.test.Main : ... ``` 可以看到,BeanDefinition 正确输出,里面是一些 Bean 的相关定义,如:是否懒加载、Bean 的 Class 以及 Bean 的属性等。 # 5、ImportSelector ImportSelector 是一个较为重要的扩展接口,通过该接口可动态的返回需要被容器管理的类,不过一般用来返回外部的配置类。可在标注 @Configuration 注解的类中,通过 @Import 导入 ImportSelector 来使用。 ```java public interface ImportSelector { // 方法入参是注解的元数据对象,返回值是类的全路径名数组 String[] selectImports(AnnotationMetadata importingClassMetadata); } ``` selectImports 方法返回的是类的全路径名。 自定义 ImportSelector: ```java public class TestImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { if (importingClassMetadata.hasAnnotation("")) { // 判断是否包含某个注解 } // 返回 Test 的全路径名,Test 会被放入到 Spring 容器中 return new String[]{"com.loong.diveinspringboot.test.Test"}; } } ``` selectImports 方法中可以针对通过 AnnotationMetadata 对象进行逻辑判断,AnnotationMetadata 存储的是注解元数据信息,根据这些信息可以动态的返回需要被容器管理的类名称。 定义的 Test 类: ```java public class Test { public void hello() { System.out.println("Test -- hello"); } } ``` 这里,我们没有对 Test 标注 @Component 注解,所以,Test 不会自动加入到 Spring 容器中。 ```java @SpringBootApplication @Import(TestImportSelector.class) public class Main { public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(); ConfigurableApplicationContext run = springApplication.run(Main.class); Test bean = run.getBean(Test.class); bean.hello(); } } ``` 之后通过 @Import 导入自定义的 TestImportSelector ,前面也说过,@Import 一般配合 @Configuration 使用,而 @SpringBootApplication 中包含了 @Configuration 注解。之后,通过 getBean 方法从容器中获取 Test 对象,并调用 hello 方法。 ``` 2020-02-26 08:01:41.712 INFO 29546 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2020-02-26 08:01:41.769 INFO 29546 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2020-02-26 08:01:41.773 INFO 29546 --- [ main] com.loong.diveinspringboot.test.Main : Started Main in 4.052 seconds (JVM running for 4.534) Test -- hello ``` 最终,结果正确输出。 # 6、ImportBeanDefinitionRegistrar 该接口和 ImportSelector 类似,也是配合 @Import 使用,不过 ImportBeanDefinitionRegistrar 更为直接一点,它可以直接把 Bean 注册到容器中。 ```java public interface ImportBeanDefinitionRegistrar { public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); } ``` 入参除了注解元数据对象 AnnotationMetadata 外,还多了一个 BeanDefinitionRegistry 对象,在前面的文章讲过,该对象定义了关于 BeanDefinition 的一系列的操作,如:注册、移除、查询等。 自定义 ImportBeanDefinitionRegistrar: ```java public class TestRegistrar implements ImportBeanDefinitionRegistrar { // 一般通过 AnnotationMetadata 进行业务判断,然后通过 BeanDefinitionRegistry 直接注册 Bean @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RootBeanDefinition beanDefinition = new RootBeanDefinition(Test.class); beanDefinition.setLazyInit(true); registry.registerBeanDefinition(Test.class.getName(), beanDefinition); } } ``` 这里,主要通过 BeanDefinitionRegistry 手动注册 Test 类的 BeanDefinition,并设置懒加载属性。 ImportSelector 和 ImportBeanDefinitionRegistrar 是实现 @Enable 模式注解的核心接口,而 @Enable 模式注解在 Spring、SpringBoot、SpringCloud 中被大量使用,其依靠这些注解来实现各种功能及特性,是较为重要的扩展接口,我们会在后面的文章中反复讨论,包括 ImportSelector 和 ImportBeanDefinitionRegistrar 是如何被 Spring 调用的、以及一些重要的 @Enable 注解实现。 值得注意的是,SpringBoot 外部化配置、自动装配特性就是通过 @Enable 注解配合 ImportSelector 和 ImportBeanDefinitionRegistrar 接口来实现的,这部分在前面的 SpringBoot 系列的文章中已经讨论过,感兴趣的同学可自行翻阅。 # 7、FactoryBean FactoryBean 也是一种 Bean,不同于普通的 Bean,它是用来创建 Bean 实例的,属于工厂 Bean,不过它和普通的创建不同,它提供了更为灵活的方式,其实现有点类似于设计模式中的工厂模式和修饰器模式。 Spring 框架内置了许多 FactoryBean 的实现,它们在很多应用如(Spring的AOP、ORM、事务管理)及与其它第三框架(ehCache)集成时都有体现。 ``` public interface FactoryBean

加载全部内容

相关教程
猜你喜欢
用户评论