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

在 Spring 4 后才引入了 @Conditional 等条件注解,它是 Spring Boot 中实现自动配置的最大功臣!

那么问题来了:如果我们还在使用 Spring 3.x 的老版本,这时候要怎么实现一个自动配置呢?

需求和问题

核心的诉求

  1. 现存系统,不打算重构
  2. Spring 版本为 3.x,也不打算升级版本和引入 Spring Boot
  3. 期望能够在少改代码的前提下实现功能增强

比如说:

  1. 希望能够给全站统一添加上日志记录(如:RPC 框架 Web 调用的摘要信息、数据库访问层的摘要信息),这个其实是个通用的功能。
  2. 我们引用了一些基础设施,并想对这些基础设施的功能作进一步的增强,这时候就应该从框架的层面来解决这个问题。

面临的问题

  • 3.x 的 Spring 没有条件注解

因为没有条件注解,所以我们不清楚在什么时候 需要/不需要 配置这些东西

  • 无法自动定位需要加载的自动配置

此时我们没有办法像 Spring Boot 的自动配置那样让框架自动加载我们的配置,我们要使用一些别的手段让 Spring 可以加载到我们定制的这些功能。

核心解决思路

条件判断

  • 通过 BeanFactoryPostProcessor 进行判断

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

配置加载

  • 编写 Java Config 类
  • 引入配置类
    • 通过 component-scan
    • 通过 XML 文件 import

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

Spring 提供的两个扩展点

BeanPostProcessor

  • 针对 Bean 实例
  • 在 Bean 创建后提供定制逻辑回调

BeanFactoryPostProcessor

  • 针对 Bean 定义
  • 在容器创建 Bean 前获取配置元数据
  • Java Config 中需要定义为 static 方法(如果不定义,Spring 在启动时会报一个 warning,你可尝试一下)

关于 Bean 的一些定制

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

Lifecycle Callback

InitializingBean / @PostConstruct / init-method

这部分是关于初始化的,可以在 Bean 的初始化之后做一些定制,这里有三种方式:

  • 实现 InitializingBean 接口
  • 使用 @PostConstruct 注解
  • 在 Bean 定义的 XML 文件里给它指定一个 init-method;亦或者在使用 @Bean 注解时指定 init-method

这些都可以让我们这个 Bean 在创建之后去调用特定的方法。

DisposableBean / @PreDestroy / destroy-method

这部分是在 Bean 回收的时候,我们该做的一些操作。可以指定这个 Bean 在销毁的时候,如果:

  • 它实现了 DisposableBean 这个接口,那么 Spring 会去调用它相应的方法
  • 也可以将 @PreDestroy 注解加在某个方法上,那么会在销毁时调用这个方法
  • 在 Bean 定义的 XML 文件里给它指定一个 destroy-method;亦或者在使用 @Bean 注解时指定 destroy-method,那么会在销毁时调用这个方法

XxxAware 接口

  • ApplicationContextAware

可以把整个 ApplicationContext 通过接口进行注入,在这个 Bean 里我们就可以获得一个完整的 ApplicationContext。

  • BeanFactoryAware

与 ApplicationContextAware 类似。

  • BeanNameAware

可以把 Bean 的名字注入到这个实例中来。

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

一些常用操作

判断类是否存在

  • ClassUitls.isPresent()

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

判断 Bean 是否已定义

  • ListableBeanFactory.containsBeanDefinition():判断 Bean 是否已定义。
  • ListableBeanFactory.getBeanNamesForType():可以查看某些类型的 Bean 都有哪些名字已经被定义了。

注册 Bean 定义

  • BeanDefinitionRegistry.registerBeanDefinition()

    • GenericBeanDefinition
  • BeanFactory.registerSingleton()

撸起袖子加油干

理论就科普完了,下面就开始实践。

在当前的例子中,我们假定一下当前环境为:没有使用 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),如下有几点是需要注意的:

  • 这里的方法定义为 static
  • 使用时,如果两项目不是在同个包下,需要主动将当前类加入到项目的 component-scan 里
@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

代码托管于 GitHub,欢迎 Star

参考链接

https://github.com/y0ngb1n/spring-boot-samples
https://github.com/digitalsonic/geektime-spring-family

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot自动配置的实现原理

    一.运行原理 Spring Boot的运行是由注解@EnableAutoConfiguration提供的. @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAuto

  • Spring Boot 自动配置的实现

    Spring Boot 自动配置 来看下 spring boot中自动配置的注解 @SuppressWarnings("deprecation") @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public

  • 全面解析SpringBoot自动配置的实现原理

    之前一直在用SpringBoot框架,一直感觉SpringBoot框架自动配置的功能很强大,但是并没有明白它是怎么实现自动配置的,现在有空研究了一下,大概明白了SpringBoot框架是怎么实现自动配置的功能,我们编写一个最简单的自动配置功能,大概的总结一下. 一,配置属性类 其实就是值对象注入的方式去配置一些Spring常用的配置,我们编写一个最简单的配置对象. @ConfigurationProperties(prefix = "hello") //@Component //如果这

  • 初识Spring Boot框架之Spring Boot的自动配置

    在上篇博客初识Spring Boot框架中我们初步见识了SpringBoot的方便之处,很多小伙伴可能也会好奇这个spring Boot是怎么实现自动配置的,那么今天我就带小伙伴我们自己来实现一个简单的Spring Boot 自动配置的案例,看看这一切到底是怎么发生的. 假设我的需求是这样的:当我的项目中存在某个类的时候,系统自动为我配置该类的Bean,同时,我这个Bean的属性还可以在application.properties中进行配置,OK,就这样一个需求,我们来看看怎么实现. 1.新建s

  • Spring Boot实战教程之自动配置详解

    前言 大家应该都有所了解,随着Ruby.Groovy等动态语言的流行,相比较之下Java的开发显得格外笨重.繁多的配置.低下的开发效率.复杂的部署流程以及第三方技术集成难度大等问题一直被人们所诟病.随着Spring家族中的新星Spring Boot的诞生,这些问题都在逐渐被解决. 个人觉得Spring Boot中最重要的两个优势就是可以使用starter简化依赖配置和Spring的自动配置.下面这篇文章将给大家详细介绍Spring Boot自动配置的相关内容,话不多说,来一起看看详细的介绍. 使

  • Spring boot中@Conditional和spring boot的自动配置实例详解

    我们知道,spring boot自动配置功能可以根据不同情况来决定spring配置应该用哪个,不应该用哪个,举个例子: Spring的JdbcTemplate是不是在Classpath里面?如果是,并且DataSource也存在,就自动配置一个JdbcTemplate的Bean Thymeleaf是不是在Classpath里面?如果是,则自动配置Thymeleaf的模板解析器.视图解析器.模板引擎 那个这个是怎么实现的呢?原因就在于它利用了Spring的条件化配置,条件化配置允许配置存在于应用中

  • Spring Boot 自动配置之条件注解浅析

    Spring Boot 神奇的自动配置,主要依靠大量的条件注解来使用配置自动化. 根据满足某一个特定条件创建一个特定的Bean.比如说,在某些系统变量下创建Bean,或者只有在某个Bean创建后才去创建另外一个Bean. 就是根据条件来控制Bean的创建行为,可以利用该特性来进行一些自动配置. 一.常用的条件注解 @Conditional 依赖的条件 @ConditionalOnBean  在某个Bean存在的条件下 @ConditionalOnMissingBean 在某个Bean不存在的条件

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

    在 Spring 4 后才引入了 @Conditional 等条件注解,它是 Spring Boot 中实现自动配置的最大功臣! 那么问题来了:如果我们还在使用 Spring 3.x 的老版本,这时候要怎么实现一个自动配置呢? 需求和问题 核心的诉求 现存系统,不打算重构 Spring 版本为 3.x,也不打算升级版本和引入 Spring Boot 期望能够在少改代码的前提下实现功能增强 比如说: 希望能够给全站统一添加上日志记录(如:RPC 框架 Web 调用的摘要信息.数据库访问层的摘要信息

  • 详解Android studio 3+版本apk安装失败问题

    studio2.3升级到3.1之后将apk发给别人下载到手机上安装,华为提示安装包无效或与操作系统不兼容,魅族提示apk仅为测试版,要求下载正式版安装. 在网上找了一下,发现是studio3.0之后的instant run功能引起的,直接点击绿色箭头按钮烧出来的apk都是不完整的,也就是魅族指的测试版,并且这个apk的路径在app\build\intermediates\instant-run-apk\debug下,而原来的app\build\outputs\apk\debug路径下已经没有ap

  • IDEA 单元测试创建方法详解(2020.03版本亲测)

    IntelliJ IDEA创建单元测试 在 Android 中,单元测试基于 JUnit,JUnit 的平面使用足以测试完全基于 Java 代码的功能. 但是,为了测试 Android 特定的功能,您需要一堆构建在 JUnit 之上的包装类.IntelliJ IDEA 简化了 Android 测试项目构建过程中的大部分任务. 1.确保你的代码是可测试的 单元测试要求源代码的编写方式使得模块之间的依赖关系可以很容易地通过模仿来消除.另外,单元测试要求功能相互隔离. 因为这个 HelloDroid

  • 详解解决IDEA2020.1版本的lombok插件问题

    相信大家更新idea2020.1版本出现之后,发现很多插件无法使用的问题,比如常用的lombok插件无法使用,这里将提供解决方案 一. 官网插件地址 在idea插件中心找到lombok,右边会显示官网插件地址,点进去即可 二. 官网插件下载 2.1 右边选择安装到IDE,选择 2020.1版本 2.2 菜单切换到Versions,选择我们想要的版本 查看所有,找到EAP的版本 下载完成后要在IDEA进行本地安装覆盖历史的版本,就可以正常使用了 三. 安装本地插件 安装本地插件方法如下: 找 到下

  • 通过Spring Boot整合Mybatis分析自动配置详解

    前言 SpringBoot凭借"约定大于配置"的理念,已经成为最流行的web开发框架,所以有必须对其进行深入的了解:本文通过整合Mybatis类来分析SpringBoot提供的自动配置(AutoConfigure)功能,在此之前首先看一个整合Mybatis的实例. SpringBoot整合Mybatis 提供SpringBoot整合Mybatis的实例,通过Mybatis实现简单的增删改查功能: 1.表数据 CREATE TABLE `role` ( `note` varchar(25

  • 如何在Spring中使用编码方式动态配置Bean详解

    bean与spring容器的关系 Bean配置信息定义了Bean的实现及依赖关系,Spring容器根据各种形式的Bean配置信息在容器内部建立Bean定义注册表,然后根据注册表加载.实例化Bean,并建立Bean和Bean的依赖关系,最后将这些准备就绪的Bean放到Bean缓存池中,以供外层的应用程序进行调用. 本文将给大家详细介绍关于在Spring中使用编码方式动态配置Bean的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 1 DefaultListableBea

  • Spring中基于XML的AOP配置详解

    1. 准备工作 1.1 创建工程 day03_eesy_03SpringAOP 1.2 在配置文件pom.xml中添加依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  • 详解SpringBoot如何删除引用jar包中的无用bean

    目录 前言 代码示例 实现代码示例 前言 公司有个项目,时间比较赶,而且项目的部分需求,和之前做的项目部分功能一样,为了赶速度和直接将之前多模块的maven项目中的部分模块,直接以jar包的形式引入到新项目中了,虽然省去了不少开发时间,但是造成项目需要导入引入项目jar的相关依赖,导致项目臃肿,启动很慢.有没有办法让项目只加载自己需要的bean呢? 当然我们可以直接修改源代码重新打包引入去解决,但是这个办法太多麻烦. 通过百度的手段,查询可以在springboot启动类上用@ComponentS

  • 详解如何在Vue3+TS的项目中使用NProgress进度条

    目录 写在前面 在项目中安装 简单的封装 在Vue切换路由时展示进度条 写在前面 NProgress是一个轻量级的进度条组件,在Github上已经2.4万star数了,虽然这个组件已经好久没有更新了,最近一次更新是20年4月份,改了jQuery的版本,但是该组件的使用频率还是高的. 在项目中安装 这里的包管理工具使用的npm,如果你使用的是yarn或者pnpm,请自行更改安装命令,安装命令如下: npm i nprogress -S 因为我们是TS的项目,还需要安装其类型声明文件,命令如下: n

随机推荐