Spring提供了BeanPostProcessor接口可以在bean创建之前和之后做一些操作,最典型的就是给@RequestMapping添加切面,记录请求参数和返回结果到日志中方便后续问题排查.

简单实例

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!--    <context:property-placeholder location="classpath*:*.properties"/>-->
    <context:component-scan base-package="li.dongpo.tc" />

    <bean id="sampleServiceImpl" class="li.dongpo.tc.service.SampleServiceImpl" />

    <bean id="sampleBeanPostProcessor" class="li.dongpo.tc.service.SampleBeanPostProcessor" />
</beans>
public class Application {
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        SampleService sampleService0 = (SampleService) context.getBean("sampleServiceImpl");
        sampleService0.hello();

        SampleService sampleService1 = context.getBean(SampleService.class);
        sampleService1.hello();
    }
}
package li.dongpo.tc.service;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * dongpo.li
 * 2020/5/18
 */
public class SampleBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName);
        Object proxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
            public Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {
                long start = System.currentTimeMillis();
                Object result = method.invoke(bean, args);
                System.out.println(bean.getClass().getName() + "#" + method.getName() + "执行时间" + (System.currentTimeMillis() - start));
                return result;
            }
        });
        return proxy;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
package li.dongpo.tc.service;

/**
 * dongpo.li
 * 2020/5/18
 */
public interface SampleService {
    void hello();
}
package li.dongpo.tc.service;

/**
 * User: dongpo.li
 * Date: 2020-05-14
 */
public class SampleServiceImpl implements SampleService {
    public void hello() {
        System.out.println("hello world");
    }
}

其中SampleBeanPostProcessor就是我们自定义的BeanPostProcessor,它使用Spring的后置处理器,在实例创建结束之后,将该bean的动态代理作为bean的实例存入IoC容器中,这样,之后调用这个bean的所有方法都会打印执行时间.

实例的代码执行结果:

sampleServiceImpl
hello world
li.dongpo.tc.service.SampleServiceImpl#hello执行时间0
hello world
li.dongpo.tc.service.SampleServiceImpl#hello执行时间0

注意有坑

这样处理之后,IoC容器中的bean是SampleServiceImpl的一个代理类,由于使用JDK动态代理,所以它是接口SampleService的另一个实现,此时,如果还是直接使用SampleServiceImpl接收的话,是会有问题的.

// 抛ClassCastException
SampleServiceImpl sampleService0 = (SampleServiceImpl) context.getBean("sampleServiceImpl");
// 抛NoSuchBeanDefinitionException
SampleServiceImpl sampleService1 = context.getBean(SampleServiceImpl.class);

感兴趣的话可以试一下.

这样本来是没有问题的,依赖反转要求就是依赖抽象而不是依赖具体实现,但是在感性上,IoC容器里应该是SampleServiceImpl的一个实例,因为xml中就是这样配置的,而且目前笔者很少见到有项目严格遵守这个规范.所以不明白其中的原理或者不知道有这个后置处理器的时候,问题就很难排查了,需要注意一下.