一、关于BeanPostProcessor
1.1:它是什么?
首先它是一个接口,定义了两个方法:
代码块11 2 3 4 5 6 7 8 9 10 11
| public interface BeanPostProcessor { @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; }
@Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
|
它定义了两个方法,分别是:
postProcessBeforeInitialization
:bean初始化前置处理
postProcessAfterInitialization
:bean初始化后置处理
⚙️ 注:这里的初始化是指一个被实例化后的bean的完成其一些初始化方法的调用(最基本的就是通过@PostConstruct
预设的初始化方法),上面两个方法的before
和after
就是针对这个状态来区分触发时机的。
我们可以定义一个实现了该接口的bean,来达到对其他bean做一些初始化前后要做的事情。
1.2:什么时候触发?
首先看下spring beans的生命周期
(图片来源于网络):
上图中标红的位置就是BeanPostProcessor
两个方法的触发点,可以看到这些方法的触发是在初始化阶段。
那么,如何定义一个类似的bean的初始化阶段的后置处理器呢?很简单,让一个bean实现BeanPostProcessor接口
并重写其before
、after
方法即可,可以搞很多个这样的bean,触发过程就是,容器里的任何bean在实例化后初始化前,都会触发一次所有实现了BeanPostProcessor接口
的bean的before方法
,初始化以后都会触发一次所有实现了BeanPostProcessor接口
的bean的after方法
,也就是说,spring在启动时,会预先加载实现了该接口的对象(通过registerBeanPostProcessors方法
注册这类bean),这样,其他任何bean在初始化时,都可以通过之前已经加载好的逻辑,逐个触发一遍(当然如果想要保证实现顺序,还可以通过实现Order接口
,来定义触发顺序)。
1.3:可以用来做什么?
了解了它的触发时机,那么它通常可以用来做哪些事情呢?一般来说,可以利用其做一些通用性的bean属性注入,下面通过一个实例来说下其应用方式和场景。
二、使用方式
实战一下,给目前项目内所有的SqlSessionFactory
对象都加一个拦截器
。
2.1:定义一个Mybatis拦截器
现在来定义一个Mybatis
里的拦截器
,它的作用就是简单拿到sql,然后打印出该sql执行耗时:
代码块21 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Slf4j @Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})}) public class SqlInterceptor implements Interceptor {
@Override public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); StatementHandler statementHandler = (StatementHandler) target; BoundSql boundSql = statementHandler.getBoundSql(); String sql = boundSql.getSql(); long start = System.currentTimeMillis(); try { return invocation.proceed(); } catch (Throwable t) { System.out.println(String.format("错误SQL=%s", sql)); throw t; } finally { System.out.println(String.format("耗时%s ms, SQL=%s", (System.currentTimeMillis() - start), sql)); }
}
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); }
@Override public void setProperties(Properties properties) {
} }
|
Mybatis的拦截器需要预先往SqlSessionFactory
设置:
代码块31 2 3 4 5 6 7 8
| @Bean(name = "sqlSession") public SqlSessionFactory sqlSession(@Qualifier("dataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setVfs(SpringBootVFS.class); bean.getObject().getConfiguration().addInterceptor(new SqlInterceptor()); return bean.getObject(); }
|
2.2:借助BeanPostProcessor操作相关Bean
这时项目模块如果很多,但是这个拦截器又要求对所有项目所有的SqlSessionFactory
都生效,一个个去改每个项目里的SqlSessionFactory
类型的bean太过繁琐,这个时候就可以在公共模块里定义一个BeanPostProcessor
去干这件事,比如可以定义成下面这样:
代码块41 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Slf4j public class SqlSessionFactoryBeanPostProcessor implements BeanPostProcessor {
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof SqlSessionFactory) { SqlSessionFactory nowBean = (SqlSessionFactory) bean; nowBean.getConfiguration().addInterceptor(new SqlInterceptor(nowBean .getConfiguration() .getEnvironment() .getDataSource())); } return bean; } }
|
然后再把它也定义成一个bean,其本身也是一个bean,才能被spring扫到去装载,否则只是实现BeanPostProcessor
接口spring是没办法察觉做管理的:
代码块51 2 3 4 5 6 7
| @ConditionalOnClass({SqlSessionFactory.class}) public class MysqlAutoConfiguration { @Bean public SqlSessionFactoryBeanPostProcessor sqlSessionFactoryBeanPostProcessor() { return new SqlSessionFactoryBeanPostProcessor(); } }
|
这样写完,就不用去一个个的改SqlSessionFactory
对象了,只要引入该公共模块,那么在bean初始化完成后,就会走这段逻辑,然后滤出自己需要的类型,对其进行修改就好,这样,所有SqlSessionFactory
就在不修改别的地方初始化SqlSessionFactory
代码的情况下,全局生效了。