如何在低版本的 Spring 中快速实现类似自动配置的功能

2019-05-03  本文已影响0人  y0ngb1n

感谢您的阅读,本文由 杨斌的博客 版权所有。
如若转载,请注明出处:杨斌的博客(https://y0ngb1n.github.io/a/coustomize-your-own-autoconfigure-whit-spring-less-than-version-4.html


在 Spring 4 后才引入了 @Conditional 等条件注解,它是 Spring Boot 中实现自动配置的最大功臣!
那么问题来了:如果我们还在使用 Spring 3.x 的老版本,这时候要怎么实现一个自动配置呢?

代码托管于 GitHub,欢迎 Star :kissing_heart:

需求和问题

核心的诉求

比如说:

面临的问题

核心解决思路

条件判断

Spring 为我们提供了一个扩展点,我们可以通过 BeanFactoryPostProcessor 来解决条件判断的问题,它可以让我们在 BeanFactory 定义完之后、Bean 的初始化之前对我们这些 Bean 的定义做一些后置的处理。可以在这个时候对我们的 Bean 定义做判断,看看当前 存在/缺少 哪些 Bean 的定义,还可以增加一些 Bean 的定义 —— 加入一些自己定制的 Bean。

配置加载

可以考虑编写自己的 Java Config 类,并把它加到 component-scan 里面,然后想办法让现在系统的 component-scan 包含我们编写的 Java Config 类;也可以编写 XML 文件,如果当前系统使用 XML 的方式,那么它加载的路径上是否可以加载我们的 XML 文件,如果不行就可以使用手动 import 这个文件。

Spring 提供的两个扩展点

BeanPostProcessor

BeanFactoryPostProcessor

关于 Bean 的一些定制

既然上面提到了 Spring 的两个扩展点,这里就延展一下关于 Bean 的一些定制的方式。

Lifecycle Callback

XxxAware 接口

如果对源码感兴趣,可见:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean
如果当前 Bean 存在 closeshutdown 方法名的方法时,会被 Spring 视为 destroy-method,在销毁时会进行调用。

一些常用操作

判断类是否存在

调用 Spring 提供的 ClassUitls.isPresent() 来判断一个类是否存在当前 Class Path 下。

判断 Bean 是否已定义

注册 Bean 定义

撸起袖子加油干

理论就科普完了,下面就开始实践。
在当前的例子中,我们假定一下当前环境为:没有使用 Spring Boot 以及高版本的 Spring

Step 1:模拟低版本的 Spring 环境

这里只是简单地引入了 spring-context 依赖,并没有真正的使用 Spring 3.x 的版本,但也没有使用 Spring 4 以上的一些特性。

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
  </dependency>
  <dependency>
    <groupId>io.github.y0ngb1n.samples</groupId>
    <artifactId>custom-starter-core</artifactId>
    <scope>provided</scope>
  </dependency>
</dependencies>

Step 2:以实现 BeanFactoryPostProcessor 接口为例

@Slf4j
public class GreetingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
    throws BeansException {

    // 判断当前 Class Path 下是否存在所需要的 GreetingApplicationRunner 这么一个类
    boolean hasClass = ClassUtils
      .isPresent("io.github.y0ngb1n.samples.greeting.GreetingApplicationRunner",
        GreetingBeanFactoryPostProcessor.class.getClassLoader());

    if (!hasClass) {
      // 类不存在
      log.info("GreetingApplicationRunner is NOT present in CLASSPATH.");
      return;
    }

    // 是否存在 id 为 greetingApplicationRunner 的 Bean 定义
    boolean hasDefinition = beanFactory.containsBeanDefinition("greetingApplicationRunner");
    if (hasDefinition) {
      // 当前上下文已存在 greetingApplicationRunner
      log.info("We already have a greetingApplicationRunner bean registered.");
      return;
    }

    register(beanFactory);
  }

  private void register(ConfigurableListableBeanFactory beanFactory) {

    if (beanFactory instanceof BeanDefinitionRegistry) {
      GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
      beanDefinition.setBeanClass(GreetingApplicationRunner.class);

      ((BeanDefinitionRegistry) beanFactory)
        .registerBeanDefinition("greetingApplicationRunner", beanDefinition);
    } else {

      beanFactory.registerSingleton("greetingApplicationRunner", new GreetingApplicationRunner());
    }
  }
}

注册我们的 Bean(见 CustomStarterAutoConfiguration),如下有几点是需要注意的:

@Configuration
public class CustomStarterAutoConfiguration {

  @Bean
  public static GreetingBeanFactoryPostProcessor greetingBeanFactoryPostProcessor() {
    return new GreetingBeanFactoryPostProcessor();
  }
}

Step 3:验证该自动配置是否生效

在其他项目中添加依赖:

<dependencies>
  ...
  <dependency>
    <groupId>io.github.y0ngb1n.samples</groupId>
    <artifactId>custom-starter-spring-lt4-autoconfigure</artifactId>
  </dependency>
  <dependency>
    <groupId>io.github.y0ngb1n.samples</groupId>
    <artifactId>custom-starter-core</artifactId>
  </dependency>
  ...
</dependencies>

启动项目并观察日志(见 custom-starter-examples),验证自动配置是否生效了:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.0.RELEASE)

2019-05-02 20:47:27.692  INFO 11460 --- [           main] i.g.y.s.d.AutoconfigureDemoApplication   : Starting AutoconfigureDemoApplication on HP with PID 11460 ...
2019-05-02 20:47:27.704  INFO 11460 --- [           main] i.g.y.s.d.AutoconfigureDemoApplication   : No active profile set, falling back to default profiles: default
2019-05-02 20:47:29.558  INFO 11460 --- [           main] i.g.y.s.g.GreetingApplicationRunner      : Initializing GreetingApplicationRunner.
2019-05-02 20:47:29.577  INFO 11460 --- [           main] i.g.y.s.d.AutoconfigureDemoApplication   : Started AutoconfigureDemoApplication in 3.951 seconds (JVM running for 14.351)
2019-05-02 20:47:29.578  INFO 11460 --- [           main] i.g.y.s.g.GreetingApplicationRunner      : Hello everyone! We all like Spring!

到这里,已成功在低版本的 Spring 中实现了类似自动配置的功能。:clap:


参考链接

上一篇 下一篇

猜你喜欢

热点阅读