@TOC

一、BeanPostProcessor

BeanPostProcessor接口有2个方法:

Object postProcessBeforeInitialization(Object bean, String beanName)
Object postProcessAfterInitialization(Object bean, String beanName)

感觉Initialization很有误导性,这里的Initialization并不是指类的初始化,也不是指实例的初始化。

而是指调用init-method这个初始化方法,就是类似于下面init-method指定的方法init调用前执行postProcessBeforeInitialization,调用之后执行postProcessAfterInitialization。

@Bean(initMethod = "init")
<bean id="id" class="Class" init-method="init"></bean>

想一想也有道理,都post类实例了,不是早就执行玩类初始化和类实例初始化了么,那么就只有init-method这个初始化了。

init-method方法是在InitializingBean接口的afterPropertiesSet方法之后执行,所以bean的属性已经设置完成了。

二、用户透明无侵入扩展功能

通过BeanPostProcessor我们可以实现对用户透明的代理,之前我们介绍动态代理的时候,要自己创建代理类,感觉非常不友好。

这里我们介绍一个通过BeanPostProcessor结合注解与动态代理来实现对用户透明的打印方法执行时间的功能,来感受一下BeanPostProcessor的强大。

2.1 注解

首先我们创建一个注解,用户想要打印一个方法的执行时间的时候,只需要添加该注解就可以了。

import org.curitis.jdk.LogExeDurationInvacationHandler;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExeDuration {
    Class value() default LogExeDurationInvacationHandler.class;
}

为了简化一点动态代理的逻辑,我们这里没有使用方法注解,而是使用了类注解,就是只要类上添加了LogExeDuration,就打印这个类中方法执行时间。

这里使用的Class属性是日志类,就是日志打印到哪里,默认是LogExeDurationInvacationHandler,还没见过,没关系,马上来。

2.2 动态代理

import org.curitis.annotation.LogExeDuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.Duration;
import java.time.Instant;

public class LogExeDurationInvacationHandler implements InvocationHandler {

    private Object target;

    private Logger logger;

    public LogExeDurationInvacationHandler(Object target,LogExeDuration logExeDuration) {
        this.target = target;
        this.logger = LoggerFactory.getLogger(logExeDuration.value());
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Instant start = Instant.now();
        Object result = method.invoke(target, args);
        Instant end = Instant.now();
        logger.info(String.format("%s execution cost %d ms",method.getName(), Duration.between(start,end).toMillis()));
        return result;
    }

    public static Object getProxy(Object target, LogExeDuration annotation){
        Class<?> clazz = target.getClass();
        ClassLoader classLoader = clazz.getClassLoader();
        Class<?>[] interfaces = clazz.getInterfaces();
        LogExeDurationInvacationHandler h = new LogExeDurationInvacationHandler(target,annotation);
        return Proxy.newProxyInstance(classLoader, interfaces, h);
    }
}

InvocationHandler我们的老朋友了,一看到基本就可以确定是JDK动态代理的实现逻辑部分,重点关注invoke方法,我们看到逻辑非常简单就是在调用目标方法前后记录了一下时间,并打印。

静态方法getProxy是一个方便获取代理类的工厂方法。

2.3 BeanPostProcessor创建代理

接下来,请出大佬BeanPostProcessor:

import org.curitis.annotation.LogExeDuration;
import org.curitis.jdk.LogExeDurationInvacationHandler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class LogExeDurationBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> clazz = bean.getClass();
        LogExeDuration annotation = clazz.getAnnotation(LogExeDuration.class);
        if(annotation != null){
            bean = LogExeDurationInvacationHandler.getProxy(bean,annotation);
        }
        return bean;
    }
}

逻辑很简单,就是如果bean创建之后看这个bean有没有LogExeDuration注解,如果有,就把这个bean替换为一个代理类。

2.4 业务类

public interface BusinessService {
    String doSomething(Integer id,String name);
}
import org.curitis.annotation.LogExeDuration;
import org.curitis.service.BusinessService;
import org.springframework.stereotype.Service;

@Service("businessService")
@LogExeDuration
public class BusinessServiceImpl implements BusinessService{

    @Override
    public String doSomething(Integer id, String name) {
        return id + " " + name;
    }
}

2.5 配置启动类

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({
        "org.curitis.component",
        "org.curitis.service"
})
public class ApplicationConfig {
}
import org.curitis.config.ApplicationConfig;
import org.curitis.service.BusinessService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Start {

    private static final Logger logger = LoggerFactory.getLogger(Start.class);

    public static void main(String[] args) {
        logger.info("start......");
        ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        BusinessService service = context.getBean(BusinessService.class);
        String result = service.doSomething(1, "curitis");
        System.out.println(result);
    }
}

BeanPostProcessor与Spring无侵入扩展-LMLPHP

三、总结

通过BeanPostProcessor、注解、动态代理来基本已经成了扩展Spring的标准套路了。

例如@Transactional注解实现事务透明扩展,通过@Cache来实现缓存透明扩展。

用户使用起来也是爽歪歪,只需要在对应的方法、类上添加相应的注解就可以了,这就对用户非常友好,友好到很多人使用了很久的@Transational注解都不知道到底是怎么回事,当然这其中也包括我。

11-10 20:19