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

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

需求和问题

核心的诉求

现存系统,不打算重构

Spring 版本为 3.x,也不打算升级版本和引入 Spring Boot

期望能够在少改代码的前提下实现功能增强

比如说:

希望能够给全站统一添加上日志记录(如:RPC 框架 Web 调用的摘要信息、数据库访问层的摘要信息),这个其实是个通用的功能。

我们引用了一些基础设施,并想对这些基础设施的功能作进一步的增强,这时候就应该从框架的层面来解决这个问题。

面临的问题

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 以上的一些特性。

org.springframework

spring-context

org.projectlombok

lombok

io.github.y0ngb1n.samples

custom-starter-core

provided

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:验证该自动配置是否生效

在其他项目中添加依赖:

...

io.github.y0ngb1n.samples

custom-starter-spring-lt4-autoconfigure

io.github.y0ngb1n.samples

custom-starter-core

...

启动项目并观察日志(见 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

参考链接

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

java快速注释怎么配置_详解如何在低版本的Spring中快速实现类似自动配置的功能...相关推荐

  1. java批量执行查询sql语句_详解MyBatis直接执行SQL查询及数据批量插入

    一.直接执行SQL查询: 1.mappers文件节选 ${paramSQL} 2.DAO类节选 public interface SomeDAO{ List getInstanceModel(@Par ...

  2. java sort 第二个参数_详解java Collections.sort的两种用法

    Collections是一个工具类,sort是其中的静态方法,是用来对List类型进行排序的,它有两种参数形式: public static > void sort(List list) { l ...

  3. java list for循环遍历_详解Java中list,set,map的遍历与增强for循环

    详解Java中list,set,map的遍历与增强for循环 Java集合类可分为三大块,分别是从Collection接口延伸出的List.Set和以键值对形式作存储的Map类型集合. 关于增强for ...

  4. linux系统rc路由配置_详解CentOS 6.4 添加永久静态路由所有方法汇总

    CentOS添加永久静态路由,具体如下: 在使用双网卡,同时使用2个网关的时候就需要加一条静态路由了.当然还有很多时候会需要加路由. 操作系统版本centos-6.4 64bit 一:使用route ...

  5. backlog配置_详解socket中的backlog 参数

    backlog: an accumulation of uncompleted work or matters needing to be dealt with. (累计未完成的待处理事件) sock ...

  6. Java扫描配置文件的注解_详解Spring框架注解扫描开启之配置细节

    前言 Spring框架对Bean进行装配提供了很灵活的方式,下面归纳一下主要的方式: 在XML中进行显示配置 在Java中进行显示配置 隐式的bean发现机制和自动装配 而自动装配实现就需要注解扫描, ...

  7. jre环境变量配置_详解java环境变量意思-用于解决环境搭建问题

    这一期教程主要针对于百度了一大圈也没有解决java环境变量配置问题的人,本期教程看完你多多少少都该有所学会. 环境变量配置你看着很复杂,其实它很简单. java环境配置好了后用cmd试下命令看出不出数 ...

  8. Java 多线程断点下载文件_详解

    本文转载于:http://blog.csdn.net/ibm_hoojo/article/details/6838222 基本原理:利用URLConnection获取要下载文件的长度.头部等相关信息, ...

  9. mysql验证配置_详解MySQL|教你一招如何自动验证 MySQL 配置正确性

    升级 MySQL 版本之后,许多用户在启动时并未更改配置文件,但发现新的的版本不再支持某些已弃用的选项,这会导致升级的 MySQL 服务关闭.在其他情况下,修改配置文件时错误输入无效的配置项会使得服务 ...

最新文章

  1. Hibernate 4.2.8,javassist 3.18.1和ClassCastExceptions –注意您的类路径
  2. 44response对象
  3. 使用detours实现劫持
  4. 执行计划中cpu耗时_面试被问怎么排查遇到的系统CPU飙高和频繁GC,到底该怎么回答?...
  5. 【BZOJ4034】T2,树链剖分练习
  6. Leetcode Insertion Sort List
  7. 凸优化第二章凸集 2.4 广义不等式
  8. java面试宝典2019
  9. H3CIE网络工程师成长之路
  10. 万字Spring框架学习总结(附核心代码详细注释)
  11. pr字幕一个一个出现的笨方法
  12. 【论文笔记】covid-19肺部感染区域分割基准
  13. 6096. 咒语和药水的成功对数
  14. Mac设置Office的语言问题
  15. 检索 COM 类工厂中 CLSID 为 {00024500-0000-0000-C000-000000000046} 的组件时失败,原因是出现以下错误: 80070005
  16. java编写活期储蓄帐目管理_活期储蓄账目管理系统
  17. 第五人格服务器维护中怎么办6,第五人格新联动刚来就出问题,紧急停服维护,这得补偿多少?...
  18. 逐行拆解Guava限流器RateLimiter
  19. Android 第三方库--2017年Android开源项目及库汇总
  20. Qt 之 QLCDNumber

热门文章

  1. 阅读笔记11-孤独后厂村:30万互联网人跳不出的中国硅谷
  2. CSS之Bootstrap(快速布局)
  3. TypeError: softmax() got an unexpected keyword argument 'axis'
  4. 制作PC端的安装程序
  5. python实现以及所有排序大总结【转】
  6. C++ – 第一次作业
  7. 提升自己逼格的编程之美之代码规范
  8. php 判断字符串中是否包含另一个字符串 strpos
  9. 【转】Java垃圾收集器
  10. 错误 C2628 “Wall”后面接“int”是非法的(是否忘记了“;”?)