zoukankan      html  css  js  c++  java
  • 【框架】利用Spring的BeanPostProcessor来修改bean属性

    一、关于BeanPostProcessor

    1.1:它是什么?

    首先它是一个接口,定义了两个方法:

    
    public interface BeanPostProcessor {
    	@Nullable //所有bean初始化之前触发该方法
    	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
    
    	@Nullable //所有bean初始化之后触发该方法
    	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的生命周期(图片来源于网络):

     

    图1

    上图中标红的位置就是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执行耗时:

    
    @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 { //拦截每次的sql执行
            Object target = invocation.getTarget();
            StatementHandler statementHandler = (StatementHandler) target;
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql(); //获取sql
            long start = System.currentTimeMillis();
            try {
                return invocation.proceed(); //sql运行
            } 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设置:

    
    @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去干这件事,比如可以定义成下面这样:

    
    @Slf4j
    public class SqlSessionFactoryBeanPostProcessor implements BeanPostProcessor {
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof SqlSessionFactory) { //所有bean初始化之后都会进入这个方法,这个时候需要滤出需要的类型,比如这次就只需要拿到SqlSessionFactory类型的对象对其设置拦截器就行了
                SqlSessionFactory nowBean = (SqlSessionFactory) bean;
                nowBean.getConfiguration().addInterceptor(new SqlInterceptor(nowBean //设置拦截器
                    .getConfiguration()
                    .getEnvironment()
                    .getDataSource()));
            }
            return bean; //完成后返回出去,可能直接进入容器,也可能会去执行其他的BeanPostProcessor
        }
    }

    然后再把它也定义成一个bean,其本身也是一个bean,才能被spring扫到去装载,否则只是实现BeanPostProcessor接口spring是没办法察觉做管理的:

    
    @ConditionalOnClass({SqlSessionFactory.class}) //存在SqlSessionFactory类型时,才会触发下面bean的装载
    public class MysqlAutoConfiguration {
        @Bean
        public SqlSessionFactoryBeanPostProcessor sqlSessionFactoryBeanPostProcessor() {
            return new SqlSessionFactoryBeanPostProcessor();
        }
    }
    

    这样写完,就不用去一个个的改SqlSessionFactory对象了,只要引入该公共模块,那么在bean初始化完成后,就会走这段逻辑,然后滤出自己需要的类型,对其进行修改就好,这样,所有SqlSessionFactory就在不修改别的地方初始化SqlSessionFactory代码的情况下,全局生效了。

  • 相关阅读:
    置换及Polya定理
    题解 UVa10943
    Error applying site theme: A theme with the name "Jet 1011" and version already exists on the server.
    用shtml来include网页文件
    SQL 2005 附加数据库出错"解决方法
    SVN 配置 入门教程
    Oracle .Net Develoer
    JdbcTemplate完全学习
    SVD外积展开式
    初识 Nslookup 命令
  • 原文地址:https://www.cnblogs.com/hama1993/p/11198310.html
Copyright © 2011-2022 走看看