IT教程 ·

Spring(七)中心容器 – 钩子接口

高等数学——详解洛必达法则

媒介

Spring 供应了异常多的扩大接口,官方将这些接口称之为钩子,这些钩子会在特定的时候被回调,以此来加强 Spring 功用,浩瀚优异的框架也是经由过程扩大这些接口,来完成本身特定的功用,如 SpringBoot、mybatis 等。

1、Aware 系列接口

Aware 从字面意义明白就是“晓得”、“感知”的意义,是用来猎取 Spring 内部对象的接口。Aware 本身是一个顶级接口,它有一系列子接口,在一个 Bean 中完成这些子接口并重写内里的 set 要领后,Spring 容器启动时,就会回调该 set 要领,而响应的对象会经由过程要领参数通报进去。我们以个中的 ApplicationContextAware 接口为例。

ApplicationContextAware

大部份 Aware 系列接口都有一个规律,它们以对象称号为前缀,猎取的就是该对象,所以 ApplicationContextAware 猎取的对象是 ApplicationContext 。

public interface ApplicationContextAware extends Aware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

ApplicationContextAware 源码异常简朴,其继续了 Aware 接口,并定义一个 set 要领,参数就是 ApplicationContext 对象,固然,别的系列的 Aware 接口也是相似的定义。其细致运用体式格局以下:

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,在初始化阶段都能够实行自定义的操纵。

public interface InitializingBean {

    void afterPropertiesSet() throws Exception;
}

从 InitializingBean 源码中能够看出它有一个 afterPropertiesSet 要领,当一个 Bean 完成该接口时,在 Bean 的初始化阶段,会回调 afterPropertiesSet 要领,其初始化阶段细致指 Bean 设置完属性以后。

该接口运用体式格局以下:

@Component
public class Test implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Test 实行初始化");
    }
}

定义启动类:

@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。

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 来相识该接口的运用体式格局:

// 平常自定义的 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;
    }
}

启动类:

@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:

@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 。

接着启动程序:

@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 来运用。

public interface ImportSelector {

    // 要领入参是注解的元数据对象,返回值是类的全路径名数组
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

selectImports 要领返回的是类的全路径名。

自定义 ImportSelector:

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 类:

public class Test {
    public void hello() {
        System.out.println("Test -- hello");
    }
}

这里,我们没有对 Test 标注 @Component 注解,所以,Test 不会自动加入到 Spring 容器中。

@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 注册到容器中。

public interface ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

入参除了注解元数据对象 AnnotationMetadata 外,还多了一个 BeanDefinitionRegistry 对象,在前面的文章讲过,该对象定义了关于 BeanDefinition 的一系列的操纵,如:注册、移除、查询等。

自定义 ImportBeanDefinitionRegistrar:

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<T> {
    // 该要领会返回该 FactoryBean “生产”的对象实例,我们须要完成该要领以给出本身的对象实例化逻辑
    T getObject() throws Exception;

    // Bean的范例
    Class<?> getObjectType();

    // 是不是是单例
    default boolean isSingleton() {
        return true;
    }
}

自定义 FactoryBean:

@Component
public class TestFactoryBean implements FactoryBean<Test> {
    @Override
    public Test getObject() throws Exception {

        // 这里能够天真的建立 Bean,如:代办、润饰

        return new Test();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

Test 类:

public class Test {
    public void hello() {
        System.out.println("Test -- hello");
    }
}

启动类:

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication();
        ConfigurableApplicationContext run = springApplication.run(Main.class);
        Test bean = (Test) run.getBean("testFactoryBean");
        bean.hello();
    }
}

输出:

2020-02-27 23:16:00.334  INFO 32234 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-27 23:16:00.338  INFO 32234 --- [           main] com.loong.diveinspringboot.test.Main     : Started Main in 3.782 seconds (JVM running for 4.187)
Test -- hello

能够看到,启动类中 getBean 的参数是 testFactoryBean ,从这能够看出,当容器中的 Bean 完成了 FactoryBean 后,经由过程 getBean(String BeanName) 猎取到的 Bean 对象并非 FactoryBean 的完成类对象,而是这个完成类中的 getObject() 要领返回的对象。假如想猎取 FactoryBean 的完成类,需经由过程这类体式格局:getBean(&BeanName),在 BeanName 之前加上&。

8、ApplicationListener

ApplicationListener 是 Spring 完成事宜机制的中心接口,属于观察者设想形式,平常合营 ApplicationEvent 运用。在 Spring 容器启动过程当中,会在响应的阶段经由过程 ApplicationContext 宣布 ApplicationEvent 事宜,以后一切的 ApplicationListener 会被回调,依据事宜范例,实行差别的操纵。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    void onApplicationEvent(E event);
}

在 onApplicationEvent 要领中,经由过程 instanceof 推断 event 的事宜范例。

自定义 ApplicationListener:

@Component
public class TestApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof TestApplicationEvent) {
            TestApplicationEvent testApplicationEvent = (TestApplicationEvent) event;
            System.out.println(testApplicationEvent.getName());
        }
    }
}

当自定义的 TestApplicationListener 被回调时,推断当前宣布的事宜范例是不是是自定义的 TestApplicationEvent,假如是则输出事宜称号。

自定义 TestApplicationEvent:

public class TestApplicationEvent extends ApplicationEvent {

    private String name;

    public TestApplicationEvent(Object source, String name) {
        super(source);
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

启动类:

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication();
        ConfigurableApplicationContext run = springApplication.run(Main.class);
        run.publishEvent(new TestApplicationEvent(new Main(),"Test 事宜"));
    }
}

经由过程 ApplicationContext 宣布 TestApplicationEvent 事宜。固然也能够在营业代码中经由过程 ApplicationContextAware 猎取 ApplicationContext 宣布事宜。

效果:

2020-02-27 08:37:10.972  INFO 30984 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2020-02-27 08:37:11.026  INFO 30984 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-27 08:37:11.029  INFO 30984 --- [           main] com.loong.diveinspringboot.test.Main     : Started Main in 3.922 seconds (JVM running for 4.367)
Test 事宜

ApplicationListener 也被 SpringBoot 举行扩大,来完成本身特定的事宜机制。这部份也在前面的文章议论过,感兴趣的同砚可自行翻阅。

末了

Spring 的钩子接口就引见到这,值得注意的是,Spring 的许多中心功用也是经由过程其内置的钩子接口来完成的,特别是一些中心注解,如:@Component 和 @Bean 的完成,这些都邑在背面的文章逐一议论。

 

以上就是本章内容,假如文章中有毛病或许须要补充的请实时提出,本人感激涕零。

Go语言基础之接口(面向对象编程下)

参与评论