这五种方式拓展Bean的生命周期,你必须记住
引言
大家好,我是有清。
又快到了吃西瓜的季节,不知道大家有没有吃过无籽西瓜,西瓜从它的幼苗到结果需要经过很漫长的一段过程,在其幼苗的时候,我们利用秋水仙素对其进行处理,然后再进行杂交,就可以得到无籽西瓜
那么,如果我想打破 Bean 传统的生命周期,在其创建到销毁的过程中,去指定一些操作,有没有什么开箱即用的手段呢?学会这些对我们的日常开发又有什么帮助呢?接下来,让我们一起学习一下
拓展方式
使用 Spring 的接口
我们可以实现 InitializingBean 接口,重写 afterPropertiesSet 方法,在这里你就可以在 Bean 实例化之后自定义一些操作
代码示例
@Component
public class ImproveBeanTest1 implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("我进来了。。。");
}
}
既然在实例化之后有这样的接口,那么有没有类似的接口可以自定义我们的销毁 Bean 的时候做点拓展?那当然有,就是 DisposableBean 接口
代码示例
@Component
public class ImproveBeanTest2 implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("糟糕。我被销毁了");
}
}
可能一下子你看了这两个示例,你会觉得这有个球用,但是格局打开,一旦我们在框架级别上去思考这个问题,我们是不是可以借助这个特性,进行一些预热操作、以及下线的时候进行优雅停机呢?
使用JSR-250注解
其实 JSR-250 的注解和上面提供的 Spring 的接口的功能很类似,别因为人家是 250 就瞧不起人家,它的作用一般就是使我们的框架解耦与 Spring 框架,尽管我们目前国内大部分用的都是 Spring 框架,不过,技多不压身,我们来看一下
250 给我们提供了了两个注解,分别是 @PostConstruct、@PreDestroy
有清老师小课堂上线,Post 表示后置、Construct 表示构造,连起来就是后置构造的时候搞事情,就是在 Bean 初始化完成后对 Bean 进行处理
Pre 表示提前,Destroy 表示消毁,连起来就是前置销毁的时候搞事情,就是在 Bean 销毁阶段前做一些处理 代码示例
@Component
public class ImproveBeanTest3{
@PostConstruct
public void postConstruct() {
System.out.println("我是构造后置");
}
@PreDestroy
public void preDestroy() {
System.out.println("我是销毁前置");
}
}
使用 @Bean 的属性
临时抽查:如何使一个类交由 Spring 进行 Bean 管理,除了 @Component、@Controller、@Service、xml 配置,还有什么方法? 举手回答🙋:“还可以使用 @Configuration 搭配 @Bean 注解”
对,此种方式也可以生成我们的 Bean,当然也提供了“前置、后置”方法,show me code
@Configuration
public class ImproveBeanTest4 {
@Bean(initMethod = "onInitialize", destroyMethod = "onDestroy")
public ImproveBeanTest4 mySpringBean() {
return new ImproveBeanTest4();
}
public void onInitialize() {
System.out.println("我我我我进来");
}
public void onDestroy() {
System.out.println("我我我我出去了");
}
}
这边还有一点需要注意的是,如果我们的 Bean 中含有 public 修饰的 close() 或者 shutdown() 方法,那么默认情况是会自动触发回调的,如果你不希望多此一举,你可以使用 destroyMethod="" 去禁止这个行为
@Bean(destroyMethod = "")
public ImproveBeanTest2 mySpringBean2() {
return new ImproveBeanTest2();
}
public void close() {
System.out.println("我不能被执行了");
}
xml 配置文件中同样可以支持这些骚操作,但是基于 xml 配置文件用的比较少,我就不展开说明了,感兴趣的小伙伴可以 官网走起:docs.spring.io/spring-fram…
使用 BeanPostProcessor
BeanPostProcessor 在整个 Bean 的周期应该是比较男一号的角色了,被用到的地方也很多,
在 Bean 初始化之前或之后,去自定义操作,甚至去狸猫换太子,怎么理解狸猫换太子,本来我准备生成 ABean,然后直接给他替换成 BBean,你或许会有疑惑,这是什么骚操作,一会我们的最佳实践会带你解开疑惑,暂且先插个眼在这里
我们需要实现 BeanPostProcessor 接口,并且需要主动重写方法,上个代码,look 一下
@Configuration
public class ImproveBeanTest5 implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
这边我们需要注意的是 BeanPostProcessor 的杀伤力非常广,对所有的 Bean 都会进行这个 processor 处理,所以一般我们需要对 Bean 进行判断处理一下,不然我就想换一个太子,结果整个皇氏家族都被你换了
使用 Aware 接口
aware 接口,也可以拓展 Bean 的生命周期,但是我这边认为一般我们去实现 Aware 相关接口,最好去取东西而不是塞东西,为什么这么说?
我们来翻译一下 aware 的意思,这个英文的意思是可感知、察觉的,如果 bean 实现了一些 aware 方法,可以说是上级方法调用到我们这里来,把变量传递给了我们,这个时候我们尽可能去取东西,而不是写,想写操作或者更改属性,建议使用 BeanPostProcessors
就好比我们业务开发的时候,别人调用我们 的方法,给了我们一个 model 对象,我们去取其中一两个属性,完成我们的业务逻辑,有必要的话,再分装一个新的 model 回去,如果我们把这个 model 对象改了,一旦出问题了,就很有可能被 diss 说,你为啥改了我给你的对象,日志打的都不对了, 这问题都没法排查了
当然使用 aware 去写当然没问题哈,只是一个编程习惯的问题,来上代码
@Component
public class ImproveBeanTest6 implements BeanNameAware {
@Override
public void setBeanName(String name) {
System.out.println("-------" + name);
}
}
Spring 给我们提供了非常多的 Aware接口,可以按需使用:docs.spring.io/spring-fram…
最佳实践
借助 InitializingBean 实现工厂模式
我们知道工厂模式,其实在我们日常的开发过程中出镜率还是比较高的,那么我们如何使用 InitializingBean 这个接口,去优雅实现我们的工厂模式呢
我们这边举一个🌰,我们知道我们房贷可以正常还款,和排个半年的队提前还款,或者刺激的逾期不还,我们就借助这样的一个小 case,去展开一下我们的代码
闲话少说,直接看代码
@Component
public class CommonRepayFactory {
private static final Map<String, IRepayFactory> REPAY_FACTORY_MAP = new ConcurrentHashMap<>();
public static IRepayFactory getRepayFactory(String type) throws Exception {
IRepayFactory repayFactory = REPAY_FACTORY_MAP.get(type);
if (Objects.isNull(repayFactory)) {
throw new Exception("搞的啥,没有这个工厂");
}
return repayFactory;
}
public static void register(String factoryType, IRepayFactory repayFactory) {
REPAY_FACTORY_MAP.put(factoryType, repayFactory);
}
}
// 提前还款工厂
@Component
@Slf4j
public class AdvanceRepayFactory implements IRepayFactory, InitializingBean {
@Override
public void repay(RepayRequest request) {
// 还款操作
}
@Override
public void afterPropertiesSet() {
CommonRepayFactory.register("提前还款", this);
}
}
// 调用处直接 CommonRepayFactory.getRepayFactory(request.getRepayBizType()).repay(request)
这一个工厂模式你改吧改吧,就能直接搬到你的项目中使用了,不用谢,我叫雷锋,具体代码地址见文章尾部
借助 ApplicationContextAware 获取所需的 Bean
我们在实际开发的过程中可能会碰到这样的问题,我在某个工具类的静态方法中想注入了某个 Bean,但是不能直接 @Autworied 这个 Bean,这个时候我们有取巧注入的方法,但是当你这个 Bean 是 jar 包引入的并且是多态的你可以直接 @Autworied 么?所以我们通常需要一个工具类去获取 Bean
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
private ApplicationContextUtils() { }
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
applicationContext = context;
}
public static <T> T getBeanByType(Class<T> clazz) throws Exception {
if (applicationContext == null) {
throw new Exception("搞什么,瞎拿");
}
return applicationContext.getBean(clazz);
}
public static <T> T getBeanByName(String beanName) throws Exception {
if (applicationContext == null) {
throw new Exception("搞什么,瞎拿");
}
return (T) applicationContext.getBean(beanName);
}
}
借助 BeanPostProcessor 实现狸猫换太子
先看个代码示例,应该是比较简单易懂的
@Component
public class ImproveBeanTest7 implements ApplicationContextAware, BeanPostProcessor {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("defaultConfig")) {
// 如果遇到需要替换的Bean,我们直接换成自己实现的Bean即可(这里可以把就得removeBeanDefinition,然后注册新的registerBeanDefinition)
// 这里的myConfig要继承自defaultConfig,否则引用的地方会报错 return applicationContext.getBean("myConfig");
}
return bean;
}
}
这时候可能会有同学有疑问了,如果我有多个实现了 BeanPostProcessor 的接口这么办,其实这个问题,代码里给了我们答案,我们会进行循环处理,一旦 postProcessBeforeInitialization 返回值不为空,我们就直接取当前的返回值了,不再进行循环处理
@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessBeforeInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
总结
通过上面的讲解,其实我们知道了如何对 Bean 进行拓展,以及了解了部分的实现场景,可能还收获了两个小轮子,但是可能现在你暂时还用不上,你只需收藏,其余的交给收藏夹处理
当然如果这篇文章真的对你有所帮助,希望你点赞分享,你的支持是我写作最大的动力,有什么问题都欢迎私信进行沟通交流
文章中的代码地址:github.com/isysc1/Issu…
闲言碎语
上周二,是情人节,大家都在讨论关于爱的命题,而我在公司苦苦加班思考这行代码怎么写
后来下班了,路上熙熙攘攘,有出售爱情的商贩、有对浪漫过敏的城管、有相拥而笑的恋人、还有拉着行李箱匆匆而过的他和她,似乎大家都在为这个命题写上自己认为的答案
而这时,突然间一个小女孩跑过来对我说:“哥哥,你要买花吗?”
我问她:“你的花呢”?
小女孩说:“人要先感到幸福,才能看到玫瑰。”