当前位置: 移动技术网 > IT编程>开发语言>Java > 详解如何在低版本的Spring中快速实现类似自动配置的功能

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

2019年07月19日  | 移动技术网IT编程  | 我要评论

在 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

参考链接



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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网