这五种方式拓展Bean的生命周期,你必须记住


theme: condensed-night-purple

引言

大家好,我是有清。

又快到了吃西瓜的季节,不知道大家有没有吃过无籽西瓜,西瓜从它的幼苗到结果需要经过很漫长的一段过程,在其幼苗的时候,我们利用秋水仙素对其进行处理,然后再进行杂交,就可以得到无籽西瓜

那么,如果我想打破 Bean 传统的生命周期,在其创建到销毁的过程中,去指定一些操作,有没有什么开箱即用的手段呢?学会这些对我们的日常开发又有什么帮助呢?接下来,让我们一起学习一下

kshZLp

拓展方式

使用 Spring 的接口

我们可以实现 InitializingBean 接口,重写 afterPropertiesSet 方法,在这里你就可以在 Bean 实例化之后自定义一些操作

代码示例 ```Java @Component
public class ImproveBeanTest1 implements InitializingBean {

@Override  
public void afterPropertiesSet() throws Exception {  
    System.out.println("我进来了。。。");  
}

} ``` InitializingBean1

既然在实例化之后有这样的接口,那么有没有类似的接口可以自定义我们的销毁 Bean 的时候做点拓展?那当然有,就是 DisposableBean 接口

代码示例 ```Java @Component
public class ImproveBeanTest2 implements DisposableBean {

@Override  
public void destroy() throws Exception {  
    System.out.println("糟糕。我被销毁了");  
}

} ``` ImproveBeanTest2

可能一下子你看了这两个示例,你会觉得这有个球用,但是格局打开,一旦我们在框架级别上去思考这个问题,我们是不是可以借助这个特性,进行一些预热操作、以及下线的时候进行优雅停机呢?

6djTe4

使用JSR-250注解

其实 JSR-250 的注解和上面提供的 Spring 的接口的功能很类似,别因为人家是 250 就瞧不起人家,它的作用一般就是使我们的框架解耦与 Spring 框架,尽管我们目前国内大部分用的都是 Spring 框架,不过,技多不压身,我们来看一下

6b1gzz

250 给我们提供了了两个注解,分别是 @PostConstruct、@PreDestroy

有清老师小课堂上线,Post 表示后置、Construct 表示构造,连起来就是后置构造的时候搞事情,就是在 Bean 初始化完成后对 Bean 进行处理

Pre 表示提前,Destroy 表示消毁,连起来就是前置销毁的时候搞事情,就是在 Bean 销毁阶段前做一些处理 代码示例 ```Java @Component
public class ImproveBeanTest3{

@PostConstruct  
public void postConstruct() {  
    System.out.println("我是构造后置");  
}  

@PreDestroy  
public void preDestroy() {  
    System.out.println("我是销毁前置");  
}

} ```

bean3

使用 @Bean 的属性

临时抽查:如何使一个类交由 Spring 进行 Bean 管理,除了 @Component、@Controller、@Service、xml 配置,还有什么方法? 举手回答🙋:“还可以使用 @Configuration 搭配 @Bean 注解”

对,此种方式也可以生成我们的 Bean,当然也提供了“前置、后置”方法,show me code

```Java @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("我我我我出去了");  
}

} ``` 图4

这边还有一点需要注意的是,如果我们的 Bean 中含有 public 修饰的 close() 或者 shutdown() 方法,那么默认情况是会自动触发回调的,如果你不希望多此一举,你可以使用 destroyMethod="" 去禁止这个行为 ```Java @Bean(destroyMethod = "")
public ImproveBeanTest2 mySpringBean2() {
return new ImproveBeanTest2();
}

public void close() {
System.out.println("我不能被执行了");
} ```

xml 配置文件中同样可以支持这些骚操作,但是基于 xml 配置文件用的比较少,我就不展开说明了,感兴趣的小伙伴可以 官网走起:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-initializingbean

使用 BeanPostProcessor

BeanPostProcessor 在整个 Bean 的周期应该是比较男一号的角色了,被用到的地方也很多,

在 Bean 初始化之前或之后,去自定义操作,甚至去狸猫换太子,怎么理解狸猫换太子,本来我准备生成 ABean,然后直接给他替换成 BBean,你或许会有疑惑,这是什么骚操作,一会我们的最佳实践会带你解开疑惑,暂且先插个眼在这里

image.png

我们需要实现 BeanPostProcessor 接口,并且需要主动重写方法,上个代码,look 一下 ```Java @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 说,你为啥改了我给你的对象,日志打的都不对了, 这问题都没法排查了

wKpwF5

当然使用 aware 去写当然没问题哈,只是一个编程习惯的问题,来上代码

```Java @Component
public class ImproveBeanTest6 implements BeanNameAware {

@Override  
public void setBeanName(String name) {  
    System.out.println("-------" + name);  
}

} ```

图6

Spring 给我们提供了非常多的 Aware接口,可以按需使用:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aware-list

最佳实践

借助 InitializingBean 实现工厂模式

我们知道工厂模式,其实在我们日常的开发过程中出镜率还是比较高的,那么我们如何使用 InitializingBean 这个接口,去优雅实现我们的工厂模式呢

我们这边举一个🌰,我们知道我们房贷可以正常还款,和排个半年的队提前还款,或者刺激的逾期不还,我们就借助这样的一个小 case,去展开一下我们的代码

闲话少说,直接看代码

```Java @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

```Java @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 实现狸猫换太子

先看个代码示例,应该是比较简单易懂的

```Java @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 返回值不为空,我们就直接取当前的返回值了,不再进行循环处理

```Java

@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 进行拓展,以及了解了部分的实现场景,可能还收获了两个小轮子,但是可能现在你暂时还用不上,你只需收藏,其余的交给收藏夹处理

当然如果这篇文章真的对你有所帮助,希望你点赞分享,你的支持是我写作最大的动力,有什么问题都欢迎私信进行沟通交流

文章中的代码地址:https://github.com/isysc1/Issues/tree/master/code/src/main/java/com/example/bean

闲言碎语

上周二,是情人节,大家都在讨论关于爱的命题,而我在公司苦苦加班思考这行代码怎么写

后来下班了,路上熙熙攘攘,有出售爱情的商贩、有对浪漫过敏的城管、有相拥而笑的恋人、还有拉着行李箱匆匆而过的他和她,似乎大家都在为这个命题写上自己认为的答案

而这时,突然间一个小女孩跑过来对我说:“哥哥,你要买花吗?”

我问她:“你的花呢”?

小女孩说:“人要先感到幸福,才能看到玫瑰。”

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值