BeanPostProcessor與Spring無侵入擴展

@TOCjava

1、BeanPostProcessor

BeanPostProcessor接口有2個方法:spring

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

感受Initialization頗有誤導性,這裏的Initialization並非指類的初始化,也不是指實例的初始化。緩存

而是指調用init-method這個初始化方法,就是相似於下面init-method指定的方法init調用前執行postProcessBeforeInitialization,調用以後執行postProcessAfterInitialization。ide

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

想想也有道理,都post類實例了,不是早就執行玩類初始化和類實例初始化了麼,那麼就只有init-method這個初始化了。post

init-method方法是在InitializingBean接口的afterPropertiesSet方法以後執行,因此bean的屬性已經設置完成了。this

2、用戶透明無侵入擴展功能

經過BeanPostProcessor咱們能夠實現對用戶透明的代理,以前咱們介紹動態代理的時候,要本身建立代理類,感受很是不友好。代理

這裏咱們介紹一個經過BeanPostProcessor結合註解與動態代理來實現對用戶透明的打印方法執行時間的功能,來感覺一下BeanPostProcessor的強大。日誌

2.1 註解

首先咱們建立一個註解,用戶想要打印一個方法的執行時間的時候,只須要添加該註解就能夠了。code

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,就打印這個類中方法執行時間。component

這裏使用的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);
    }
}

輸出

3、總結

經過BeanPostProcessor、註解、動態代理來基本已經成了擴展Spring的標準套路了。

例如@Transactional註解實現事務透明擴展,經過@Cache來實現緩存透明擴展。

用戶使用起來也是爽歪歪,只須要在對應的方法、類上添加相應的註解就能夠了,這就對用戶很是友好,友好到不少人使用了好久的@Transational註解都不知道究竟是怎麼回事,固然這其中也包括我。

相關文章
相關標籤/搜索