如何写好一个 Spring 组件

背景
Spring 框架提供了许多接口,可以使用这些接口来定制化 bean ,而非简单的 getter/setter 或者构造器注入。细翻 Spring Cloud Netflix、Spring Cloud Alibaba 等这些构建在 Spring Framework 的成熟框架源码,你会发现大量的扩展 bean 例如
- Eureka 健康检查
package org.springframework.cloud.netflix.eureka;
public class EurekaHealthCheckHandler implements InitializingBean {}
- Seata Feign 配置
package com.alibaba.cloud.seata.feign;
public class SeataContextBeanPostProcessor implements BeanPostProcessor {}
代码示例

- DemoBean
@Slf4j
public class DemoBean implements InitializingBean {
	public DemoBean() {
		log.info("--> instantiate ");
	}
	@PostConstruct
	public void postConstruct() {
		log.info("--> @PostConstruct ");
	}
	@Override
	public void afterPropertiesSet() throws Exception {
		log.info("--> InitializingBean.afterPropertiesSet ");
	}
	public void initMethod() {
		log.info("--> custom initMehotd");
	}
}
- DemoBeanPostProcessor
@Configuration
public class DemoBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if ("demoBean".equals(beanName)){
			log.info("--> BeanPostProcessor.postProcessBeforeInitialization ");
		}
		return bean;
	}
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if ("demoBean".equals(beanName)){
			log.info("--> BeanPostProcessor.postProcessAfterInitialization ");
		}
		return bean;
	}
}
- DemoConfig
@Configuration
public class DemoConfig {
	@Bean(initMethod = "initMethod")
	public DemoBean demoBean() {
		return new DemoBean();
	}
}
运行输出日志
- 整个 bean 的创建过程日志输出如下和文首图片横线以上 bean 创建周期一致
DemoBean             : --> instantiate
DemoBeanPostProcessor: --> BeanPostProcessor.postProcessBeforeInitialization
DemoBean             : --> @PostConstruct
DemoBean             : --> InitializingBean.afterPropertiesSet
DemoBean             : --> custom initMehotd
DemoBeanPostProcessor: --> BeanPostProcessor.postProcessAfterInitialization
执行过程核心源码
- AbstractAutowireCapableBeanFactory.initializeBean
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
  // 执行BeanPostProcessor.postProcessBeforeInitialization
  Object wrappedBean = wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
      ...
      // 执行用户自定义初始化and JSR 250 定义的方法
  invokeInitMethods(beanName, wrappedBean, mbd);
      ...
  // 执行执行BeanPostProcessor.postProcessAfterInitialization
  wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  return wrappedBean;
}
- 下文就详细说明一下每个 bean 的扩展周期的最佳使用场景
BeanPostProcessor

BeanPostProcessor 是一个可以自定义实现回调方法接口,来实现自己的实例化逻辑、依赖解决逻辑等,如果想要在 Spring 完成对象实例化、配置、初始化之后实现自己的业务逻辑,可以通过扩展实现一个或多个 BeanPostProcessor 处理。
- 多用于适配器模式,可以在实现同一接口 bean 创建前后进行包装转换
// seata 上下文转换,将其他类型 wrap 成 SeataFeignContext
public class SeataContextBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName){
		if (bean instanceof FeignContext && !(bean instanceof SeataFeignContext)) {
			return new SeataFeignContext(getSeataFeignObjectWrapper(),
					(FeignContext) bean);
		}
		return bean;
	}
}
- 自定义 注解查找扩展
net.dreamlu.mica.redisson.stream.RStreamListenerDetector 查找自定义 @RStreamListener 实现 基于 Redisson 的 pub/sub
public class RStreamListenerDetector implements BeanPostProcessor {
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		Class<?> userClass = ClassUtils.getUserClass(bean);
		ReflectionUtils.doWithMethods(userClass, method -> {
			RStreamListener listener = AnnotationUtils.findAnnotation(method, RStreamListener.class);
			.... do something
		}, ReflectionUtils.USER_DECLARED_METHODS);
		return bean;
	}
}
PostConstruct
JavaEE5 引入了@PostConstruct 作用于 Servlet 生命周期的注解,实现 Bean 初始化之前的自定义操作。
- 只能有一个非静态方法使用此注解
- 被注解的方法不能有返回值和方法参数
- 被注解的方法不得抛出异常
这里需要注意的 这个注解不是 Spring 定义的,而是属于 JavaEE JSR-250 规范定义的注解,当你在使用 Java11 的时候要手动引入相关 jar(因为 Java11 移除了)
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
</dependency>
使用场景: 在之前的版本,我们可以在启动后通过 @PostConstruct 注解的方法执行初始化数据。但由于 Java 高版本已经移除相关 API ,我们不推荐使用此 注解,可以通过 Spring 相关 Event 回调事件处理
@PostConstruct 注解的方法在项目启动的时候执行这个方法,也可以理解为在 spring 容器启动的时候执行,可作为一些数据的常规化加载,比如数据字典之类的。
InitializingBean

InitializingBean 接口方法会在 容器初始化(getter/setter/构造器)完成 bean 的属性注入后执行。
应用场景: 动态修改容器注入的 Bean 参数
- 正常用户配置参数注入到 bean
security:
  oauth2:
      ignore-urls:
        - '/ws/**'
@ConfigurationProperties(prefix = "security.oauth2")
public class PermitAllUrlProperties {
	@Getter
	@Setter
	private List<String> ignoreUrls = new ArrayList<>();
}
- 我们发现此时用户配置并不完整,还有一些通用不需要用户维护,可通过实现 InitializingBean 接口回调扩展
@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class PermitAllUrlProperties implements InitializingBean {
	@Getter
	@Setter
	private List<String> urls = new ArrayList<>();
	@Override
	public void afterPropertiesSet() {
		urls.add("/common/*");
	}
}
initMethod
上文 @PostConstruct 已经不推荐大家使用,可以使用 Bean(initMethod = 'initMehotd') 替代,相关的限制如上。
@Bean(initMethod = "initMethod")
public DemoBean demoBean() {
  return new DemoBean();
}
public void initMethod() {
  log.info("--> custom initMehotd");
}
总结
- 参考 https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#beans-factory-nature
- mica : https://github.com/lets-mica/mica
- pig: https://github.com/lltx/pig
如何写好一个 Spring 组件的更多相关文章
- 如何写好一个vue组件,老夫的一年经验全在这了【转】 v-bind="$attrs" 和 v-on="$listeners"
		如何写好一个vue组件,老夫的一年经验全在这了 一个适用性良好的组件,一种是可配置项很多,另一种就是容易覆写,从而扩展功能 Vue 组件的 API 来自三部分——prop.事件和插槽: prop 允许 ... 
- 初学ReactJS,写了一个RadioButtonList组件
		1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>React Demo</title> ... 
- 教你写Spring组件
		前言 原文地址:https://www.cnblogs.com/qnlcy/p/15905682.html 一.宗旨 在如日中天的 Spring 架构体系下,不管是什么样的组件,不管它采用的接入方式如 ... 
- 写一个vue组件
		写一个vue组件 我下面写的是以.vue结尾的单文件组件的写法,是基于webpack构建的项目.如果还不知道怎么用webpack构建一个vue的工程的,可以移步到vue-cli. 一个完整的vue组件 ... 
- 干净win7要做几步才能运行第一个Spring MVC 写的动态web程序
		干净win7要做几步才能运行第一个Spring MVC 写的动态web程序: 1. 下载安装jdk 2. 配置Java环境变量 3. 测试一下第1,2两步是否完全成功:http://jingyan.b ... 
- js单行写一个评级组件
		单行写一个评级组件:"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate); -----------------------------------分隔符- ... 
- 写一个Spring Boot的Hello World
		尽管这个demo也就hello world水平,但我还是要记录一下(总算能动了QAQ),毕竟老是看文章不动手不行啊 上次写Servlet的CRUD项目还是2月份,虽然代码忘的差不多了,但我就记得JDB ... 
- vue validate多表单验证思考 之前写过一个里外层,现在觉得不合适,应该平行的写,然后都给ret,最后判断ret 再做出反应,这样整体表单的所有验证就都报验证,然后最后提交的时候把组件内的对象合并到总的对象,再提交
		vue validate多表单验证思考 之前写过一个里外层,现在觉得不合适,应该平行的写,然后都给ret,最后判断ret 再做出反应,这样整体表单的所有验证就都报验证,然后最后提交的时候把组件内的对象 ... 
- 用vue.js写的一个瀑布流的组件
		用vue.js写的一个瀑布流的组件:https://segmentfault.com/a/1190000010741319 https://www.jianshu.com/p/db3cadc03402 
随机推荐
- 谷歌地球服务器"失联"的替代方案
			2020年11月下旬,谷歌地球开始无法连接.作为谷歌地球的替代方案,推荐使用国产软件"图新地球LSV".网址 http://www.tuxingis.com 下载"图新地 ... 
- 经典面试题:在浏览器地址栏输入一个 URL 后回车,背后发生了什么
			尽人事,听天命.博主东南大学硕士在读,热爱健身和篮球,乐于分享技术相关的所见所得,关注公众号 @ 飞天小牛肉,第一时间获取文章更新,成长的路上我们一起进步 本文已收录于 CS-Wiki(Gitee 官 ... 
- Vue3组件(九)Vue + element-Plus + json = 动态渲染的表单控件
			一个成熟的表单 表单表单,你已经长大了,你要学会: 动态渲染 支持单列.双列.多列 支持调整布局 支持表单验证 支持调整排列(显示)顺序 依据组件值显示需要的组件 支持 item 扩展组件 可以自动创 ... 
- Flutter 中不得不会的 mixin
			mixin 是 Dart 中非常重要的概念,对于未接触过此概念的Coder来说尤其重要,最近看源码的时候,由于对 mixin 不熟悉导致理解出现偏差,走了很多弯路,所以这篇文章介绍一下 mixin 概 ... 
- CVer想知道的都在这里了,一起分析下《中国计算机视觉人才调研报告》吧!
			最近闲来无事,老潘以一名普通算法工程师的角度,结合自身以及周围人的情况,理性也感性地分析一下极市平台前些天发布的2020年度中国计算机视觉人才调研报告. 以下的"计算机视觉人才"简 ... 
- sql注入和union all关联查询的学习总结
			1.后台从页面取值进行sql查询时最好不要直接拼,如下代码: String sql = "SELECT wo.* " + " from push_command pu & ... 
- 微信小程序:数据绑定
			data中的数据不仅仅可以当成文本来显示,还可以当成属性来显示. 注意:属性值要用单引号或双引号引起来. 在微信开发者工具的控制台中点击Wxml会看到 使用Boolean类型充当属性的时候,字符串和花 ... 
- 1.3 PHP+MYSQL+APACHE配置(序)
			本节对服务器端web服务进行配置.事实上,对于配置这个环境(WAMP)网上还是有很多教程的,大家可以通过网上的教程完成配置,也不必拘泥于本文.甚至网上有免费的服务器端软件可以选择,比如著名的phpst ... 
- Serverless 2.0,鸡蛋还是银弹?
			简介: 本篇旨在介绍 Serverless 如今应用到应用(非病句)的各种困境,以及帮助用户如何去规避一些问题,提前了解方向. 浪潮 从 2014 年 Serverless 冒头至今,已经有无数的勇士 ... 
- c++中深层复制(浅层复制运行错误)成功运行-----sample
			下面随笔给出c++中深层复制(浅层复制运行错误)成功运行------sample. 浅层复制与深层复制 浅层复制 实现对象间数据元素的一一对应复制. 深层复制 当被复制的对象数据成员是指针类型时,不是 ... 
