BeanPostProcessor and Spring noninvasive extension

@TOC

I. BeanPostProcessor

The BeanPostProcessor interface has two methods:

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

It seems that initialization is misleading. Initialization here does not refer to class initialization or instance initialization.

It refers to calling the init method initialization method, which is similar to the method specified in init method below, that is, executing postProcessBeforeInitialization before calling init and postProcessAfterInitialization after calling init.

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

It makes sense to think about it. All post class instances have been initialized for a long time. Then init method is the only initialization.

The init method method is executed after the afterpropertieset method of the InitializingBean interface, so the properties of the bean have been set.

II. User transparent and non intrusive expansion function

Through BeanPostProcessor, we can realize transparent proxy to users. When we introduced dynamic proxy, we had to create proxy classes by ourselves, which felt very unfriendly.

Here we introduce a function of transparent printing method execution time to users through BeanPostProcessor combined with annotation and dynamic agent to experience the power of BeanPostProcessor.

2.1 notes

First, we create an annotation. When users want to print the execution time of a method, they just need to add the annotation.

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;
}

In order to simplify the logic of dynamic agent, we do not use method annotation here, but use class annotation. That is, as long as logexecuration is added to the class, the execution time of the method in the class will be printed.

The Class attribute used here is the log Class, which is where the log is printed. By default, it is the logexecuration invocationhandler. I haven't seen it before. It doesn't matter. Come right away.

2.2 dynamic agent

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 our old friend, once we see it, we can confirm that it is the implementation logic part of JDK dynamic agent, focusing on the invoke method. We see that the logic is very simple, that is, we record the time before and after calling the target method, and print it.

Static method getProxy is a factory method that is convenient to get proxy classes.

2.3 create agent by beanpostprocessor

Next, let's go to 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;
    }
}

The logic is very simple. After the bean is created, check whether the bean has a logexecuration annotation. If so, replace the bean with a proxy class.

2.4 business

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 configuration startup class

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);
    }
}

Three, summary

It has basically become a standard way to extend Spring through bean processor, annotation and dynamic proxy.

For example, @ Transactional annotation implements transaction transparent extension, and @ Cache implements Cache transparent extension.

The user is also straightforward to use, just need to add the corresponding annotation on the corresponding method and class, which is very friendly to the user, so many people use @ transactional annotation for a long time without knowing what is going on, of course, including me.

Tags: Programming Java JDK Attribute Spring

Posted on Sun, 10 Nov 2019 05:55:02 -0500 by mdaoust