Spring源码解析-BeanPostProcessor

原创
2019/03/08 15:25
阅读数 7K

BeanPostProcessor的定义和作用

首先,我们先提出一个问题,假设我需要在Spring初始化完Bean之后做一些定制化逻辑,简单的可以是记录下初始化过的bean,又或者是针对某类策略需要执行一些具体的业务逻辑,比如Spring里面提供的Bean的校验逻辑之类的,我们该怎么做比较好呢?

其实,Spring在初始化Bean之后已经加入了一些钩子方法,方便我们去做些后置处理的业务逻辑,这个后置处理就是通过接下来要说的类BeanPostProcessor来实现的。BeanPostProcessor接口定义了2个回调方法,你可以实现这些方法来提供自己的实例化逻辑、依赖解析逻辑,以及一些定制化业务逻辑等等。如果你希望在Spring容器完成实例化、配置和初始化bean之后实现一些定制逻辑,可以插入一个或多个BeanPostProcessor实现。

这个BeanPostProcessor的实现类允许自定义修改新的Bean实例,例如检查标记接口或用代理包装它们实例。applicationcontext可以在bean定义中自动检测BeanPostProcessor Bean实例,并将它们注册到容器里面,在稍后的处理流程中将它们应用于随后创建的任何bean。

当然,我们还可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例执行的顺序,只要将我们的自定义的BeanPostProcessor类实现Order接口并设置相应的优先级即可实现。BeanPostProcessor实例在bean对象实例上执行的。换句话说,Spring IoC容器实例化一个bean实例后再通过BeanPostProcessor实例执行后置处理业务。所以BeanPostProcessor是针对已经实例化好的对象做处理的,如果你要改变未处理的bean即bean definition的话则要使用BeanFactoryPostProcessor这个接口。后续讨论。

BeanPostProcessor方法讲解

BeanPostProcessor实际上只有2个回调方法,我们来看下具体代码:

    /**
     * 这个方法会在InitializingBean的方法afterPropertiesSet以及自定义了 init-method方法执行之前被回调,返回值可能是个原始对象的包装类
	 */
	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

	/**
     * 这个方法会在InitializingBean的方法afterPropertiesSet以及自定义了 init-method方法执行之后被回调,返回值可能是个原始对象的包装类
	 */
	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

我们可以看出上面两个方法的处理都是在bean已经实例化后执行的,看方法名称分别是在初始化之前和之后,但是这个初始化并非对象的实例化,而是指的是实现了InitializingBean接口的bean执行里面的afterPropertiesSet方法和为bean指定了init-method方法执行的前后去回调这两个方法,根据具体需求,我们可以添加自己的业务逻辑。

BeanPostProcessor 实战

对于BeanPostProcessor 的运用,在Spring里面随处可见,比如我们经常用的bean的字段校验,也用到了这个后置处理接口来执行校验,我们来看下自定义BeanPostProcessor 来实现具体业务逻辑的代码是怎么写,首先,我们看下校验后置处理的实现,代码如下:

public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean {

	private Validator validator;

	private boolean afterInitialization = false;


	/**
	 * 可以指定校验器,默认会初始化一个
	 */
	public void setValidator(Validator validator) {
		this.validator = validator;
	}

	/**
	 * 指定校验器工厂类
	 */
	public void setValidatorFactory(ValidatorFactory validatorFactory) {
		this.validator = validatorFactory.getValidator();
	}

	/**
	 * 选择是在初始化之前调用校验还是初始化之后调用校验
	 */
	public void setAfterInitialization(boolean afterInitialization) {
		this.afterInitialization = afterInitialization;
	}

	/**
	 * 这个校验BeanPostProcessor初始化完后执行该方法,去初始化默认校验器
	 */
	@Override
	public void afterPropertiesSet() {
		if (this.validator == null) {
			this.validator = Validation.buildDefaultValidatorFactory().getValidator();
		}
	}


	/**
	 * 初始化前校验对象
	 */
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (!this.afterInitialization) {
			doValidate(bean);
		}
		return bean;
	}

	/**
	 * 初始化后校验对象
	 */
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (this.afterInitialization) {
			doValidate(bean);
		}
		return bean;
	}


	/**
	 *校验业务逻辑
	 */
	protected void doValidate(Object bean) {
		Set<ConstraintViolation<Object>> result = this.validator.validate(bean);
		if (!result.isEmpty()) {
			StringBuilder sb = new StringBuilder("Bean state is invalid: ");
			for (Iterator<ConstraintViolation<Object>> it = result.iterator(); it.hasNext();) {
				ConstraintViolation<Object> violation = it.next();
				sb.append(violation.getPropertyPath()).append(" - ").append(violation.getMessage());
				if (it.hasNext()) {
					sb.append("; ");
				}
			}
			throw new BeanInitializationException(sb.toString());
		}
	}

}

像上面这样的业务逻辑,我们自己也可以模仿的写出符合自己业务的逻辑,是不是很简单方便,看了Spring里自定义的BeanPostProcessor,接下来我们自己来实现一个简单的BeanPostProcessor,只是简单打印创建的对象和时间,具体实现如下:

 @Component
public class TestBeanPostProcessor implements BeanPostProcessor {

    protected static final Logger logger = LoggerFactory.getLogger(TestBeanPostProcessor.class);
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        logger.info("BeforeInitialization:"+ beanName + " process time:" + new Date());
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        logger.info("AfterInitialization:"+ beanName + " process time:" + new Date());
        return bean;
    }
}

上面的钩子处理类,用注解的方式让applicationContext自动检测到,也可以使用下面的xml配置让applicationContext自动检测:

<bean class="com.fcbox.pangu.manage.web.TestBeanPostProcessor" />

这样子,applicationContext就能检测到我们的TestBeanPostProcessor,然后执行里面2个回调方法,启动项目看输出的日志是这样的:

2018-12-21 14:26:47.850 [RMI TCP Connection(2)-127.0.0.1] com.fcbox.pangu.manage.web.TestBeanPostProcessor - BeforeInitialization:mvcContentNegotiationManager process time:Fri Dec 21 14:26:47 CST 2018
2018-12-21 14:26:47.891 [RMI TCP Connection(2)-127.0.0.1] com.fcbox.pangu.manage.web.TestBeanPostProcessor - AfterInitialization:mvcContentNegotiationManager process time:Fri Dec 21 14:26:47 CST 2018

可以很清楚的看出这2个方法的执行顺序,而且也生效了,所以我们可以通过这种方式,通过热插拔的形式去实现这些钩子方法,可以很方便的去修改我们的对象。 接下来,要是我们自定义的BeanPostProcessor需要有先后的执行顺序,我们可以让我们自定义的BeanPostProcessor去实现Ordered接口,并设置优先级,让我们来新建一个实现Ordered的BeanPostProcessor,很简单代码如下:

@Component
public class TestOrderBeanPostProcessor implements BeanPostProcessor, Ordered {

    protected static final Logger logger = LoggerFactory.getLogger(TestOrderBeanPostProcessor.class);
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        logger.info("hight order BeforeInitialization:"+ beanName + " process time:" + new Date());
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        logger.info("hight order AfterInitialization:"+ beanName + " process time:" + new Date());
        return bean;
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

同时并改造TestBeanPostProcessor类让其实现ordered接口,并实现getOrder方法让其返回2,然后启动项目发现日志是这样:

2018-12-21 14:32:03.744 [RMI TCP Connection(3)-127.0.0.1] com.fcbox.pangu.manage.web.TestOrderBeanPostProcessor - hight order BeforeInitialization:localCache process time:Fri Dec 21 14:32:03 CST 2018
2018-12-21 14:32:17.261 [RMI TCP Connection(3)-127.0.0.1] com.fcbox.pangu.manage.web.TestBeanPostProcessor - low order BeforeInitialization:localCache process time:Fri Dec 21 14:32:17 CST 2018

很明显高优先级的BeanPostProcessor会先被执行,但是这种优先级是针对同一个回调方法来说的,对于postProcessBeforeInitialization永远会在postProcessAfterInitialization之前执行的。这一点是要注意的。至此BeanPostProcessor介绍完毕。

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部