Spring BPP How to Elegantly Create Dynamic Proxy Bean Detailed Explanation

  • 2021-07-01 07:24:27
  • OfStack

v1. Preface

This article is not based on Aspectj, but directly through Cglib and ProxyFactoryBean to create proxy Bean. From the following example, you can see the difference between the proxy Bean created by Cglib and the proxy Bean created by ProxyFactoryBean.

v2. Basic test code

Test the entity class and create a proxy Bean of type BppTestDepBean in BPP.


@Component
public static class BppTestBean {
 @Autowired
 private BppTestDepBean depBean;

 public void test1() {
  depBean.testDep();
 }

 public void test2() {
  depBean.testDep();
 }

 @TestMethod
 public void test3() {
  depBean.testDep();
 }
}

@Component
public static class BppTestDepBean {
 public void testDep() {
  System.out.println("HEHE");
 }
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestMethod {
}

Test class


@RunWith(SpringRunner.class)
@SpringBootTest
public class BppTest {

 @Autowired
 private BppTestBean bppTestBean;

 @Test
 public void test() {
  bppTestBean.test1();
  bppTestBean.test2();
  bppTestBean.test3();
 }
}

v3. Creating Proxy Bean with Cglib


public class ProxyBpp1 implements BeanPostProcessor {
 private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBpp1.class);

 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  if (bean instanceof BppTestBean) {
   Enhancer enhancer = new Enhancer();
   enhancer.setSuperclass(bean.getClass());
   // Identification Spring-generated proxies
   enhancer.setInterfaces(new Class[]{SpringProxy.class});
   // Setting enhancements 
   enhancer.setCallback((MethodInterceptor) (target, method, args, methodProxy) -> {
    if ("test1".equals(method.getName())) {
     LOGGER.info("ProxyBpp1  Start execution ...");
     Object result = methodProxy.invokeSuper(target, args);
     LOGGER.info("ProxyBpp1  End execution ...");
     return result;
    }
    return method.invoke(target, args);
   });

   return enhancer.create();
  }
  return bean;
 }
}

Mainly the test1 method that proxies BppTestBean. In fact, the proxy Bean created in this way uses the problem, and the @ Autowired field is not injected, so NPE will appear. methodProxy.invokeSuper(target, args) This 1 line of code is problematic, targe is a proxy class object, and the real object is postProcessBeforeInitialization(Object bean, String beanName) The bean object in, the @ Autowired field of the bean object has been injected. So you can put methodProxy.invokeSuper(target, args) Modify to method.invoke(bean, args) Resolves the problem that the @ Autowired field cannot be injected.

v4. Creating Proxy Bean with ProxyFactoryBean


public class ProxyBpp2 implements BeanPostProcessor {
 private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBpp2.class);

 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  if (bean instanceof BppTestBean) {
   ProxyFactoryBean pfb = new ProxyFactoryBean();
   pfb.setTarget(bean);
   pfb.setAutodetectInterfaces(false);
   NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
   advisor.addMethodName("test1");
   advisor.setAdvice((MethodInterceptor) invocation -> {
    LOGGER.info("ProxyBpp2  Start execution ...");
    Object result = invocation.getMethod().invoke(invocation.getThis(), invocation.getArguments());
    LOGGER.info("ProxyBpp2  End execution ...");
    return result;
   });
   pfb.addAdvisor(advisor);

   return pfb.getObject();
  }
  return bean;
 }
}

When using ProxyFactoryBean to create a proxy Bean, 1 must have an targe object. When Advisor cuts in, it executes Advice one by one. invocation.getThis() Is the target object passed in when the proxy Bean is created through ProxyFactoryBean. Since the target object is postProcessBeforeInitialization(Object bean, String beanName) So the @ Autowired field has also been injected.

v 5. @ Autowired when annotations are processed

Everyone must know that the @ Autowired field is also processed through an BPP, but this BPP is one more advanced than what we usually use, and it is InstantiationAwareBeanPostProcessor. This BPP can realize the creation of Bean, the injection and analysis of attributes (such as @ Autowired, @ Value, @ Resource, etc.). You can refer to CommonAnnotationBeanPostProcessor (handling JSR-250 related annotations) and AutowiredAnnotationBeanPostProcessor (handling @ Autowired, @ Value, @ Inject related annotations) under 1.

InstantiationAwareBeanPostProcessor has one of the following methods, and AutowiredAnnotationBeanPostProcessor overrides this method to implement automatic injection with relevant annotation attributes.


@Nullable
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
  throws BeansException {

 return null;
}

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
 InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
 try {
  metadata.inject(bean, beanName, pvs);
 }
 catch (BeanCreationException ex) {
  throw ex;
 }
 catch (Throwable ex) {
  throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
 }
 return pvs;
}

The postProcessProperties method of InstantiationAwareBeanPostProcessor is called in the populateBean method of Spring AbstractAutowireCapableBeanFactory. There is the following code in doCreateBan of AbstractAutowireCapableBeanFactory.


// Initialize the bean instance.
Object exposedObject = bean;#
try {
 populateBean(beanName, mbd, instanceWrapper);
 exposedObject = initializeBean(beanName, exposedObject, mbd);
}

That is to say, the attribute of Bean is filled first, and then the initialization of Bean is carried out. There are four main things done in the initializeBean method.

1. invokeAwareMethods

2. applyBeanPostProcessorsBeforeInitialization

3. invokeInitMethods

4. applyBeanPostProcessorsAfterInitialization

Where 2 and 4 are the postProcessBeforeInitialization and postProcessAfterInitialization methods in the normal BPP that are called, respectively.

This is why the @ Autowired field associated with the corresponding target Bean was injected when the proxy Bean was created in BPP.

v6. InstantiationAwareBeanPostProcessor to create dynamic proxy Bean

The InstantiationAwareBeanPostProcessor interface has an postProcessBeforeInstantiation method that allows us to instantiate Bean ourselves. By looking at AbstractAutowireCapableBeanFactory, the method calls: createBean method- > resolveBeforeInstantiation method- > applyBeanPostProcessorsBeforeInstantiation method- > The InstantiationAwareBeanPostProcessor # postProcessBeforeInstantiation method, if a non-null instance is returned, the doCreateBean method is no longer executed. This means that there will be no process for populating and initializing Bean properties, but AbstractAutowireCapableBeanFactory can help us do so.


public <T> T postProcess(T object) {
 if (object == null) {
  return null;
 }
 T result;
 try {
  //  Using containers autowireBeanFactory Standard dependency injection method autowireBean() Deal with  object Dependency injection of objects 
  this.autowireBeanFactory.autowireBean(object);
  //  Using containers autowireBeanFactory Standard initialization method initializeBean() Initialize object  object
  result = (T) this.autowireBeanFactory.initializeBean(object,
    object.toString());
 } catch (RuntimeException e) {
  Class<?> type = object.getClass();
  throw new RuntimeException(
    "Could not postProcess " + object + " of type " + type, e);
 }
 return result;
}

The above code can help us realize the function of automatic injection and initialization of Bean in non-Spring container. As Spring security students know, this is also used to solve the problem of attribute injection in objects. If you read the source code of Spring security, you will find many objects, such as WebSecurity, ProviderManager, various security Filter, etc. The creation of these objects is not discovered and registered into spring container by bean definition, but directly from new. The AutowireBeanFactoryObjectPostProcessor tool class provided by Spring security enables these objects to have the same lifecycle as container bean, and also injects corresponding dependencies, thus entering a state ready for use.

Use Cglib to create a dynamic proxy Bean in InstantiationAwareBeanPostProcessor.


public class ProxyBpp3 implements InstantiationAwareBeanPostProcessor {
 private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBpp3.class);

 private final AutowireCapableBeanFactory autowireBeanFactory;

 ProxyBpp3(AutowireCapableBeanFactory autowireBeanFactory) {
  this.autowireBeanFactory = autowireBeanFactory;
 }

 @Override
 public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
  if (beanClass.equals(BppConfig.BppTestBean.class)) {
   Enhancer enhancer = new Enhancer();
   enhancer.setSuperclass(beanClass);
   // Identification Spring-generated proxies
   enhancer.setInterfaces(new Class[]{SpringProxy.class});
   // Setting enhancements 
   enhancer.setCallback((MethodInterceptor) (target, method, args, methodProxy) -> {
    if ("test1".equals(method.getName())) {
     LOGGER.info("ProxyBpp3  Start execution ...");
     Object result = methodProxy.invokeSuper(target, args);
     LOGGER.info("ProxyBpp3  End execution ...");
     return result;
    }
    return methodProxy.invokeSuper(target, args);
   });

   return this.postProcess(enhancer.create());
  }
  return null;
 }

 ...
}

Use ProxyFactoryBean to create a dynamic proxy Bean in InstantiationAwareBeanPostProcessor.


public class ProxyBpp4 implements InstantiationAwareBeanPostProcessor {
 private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBpp4.class);

 private final AutowireCapableBeanFactory autowireBeanFactory;

 ProxyBpp4(AutowireCapableBeanFactory autowireBeanFactory) {
  this.autowireBeanFactory = autowireBeanFactory;
 }

 @Override
 public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
  if (beanClass.equals(BppConfig.BppTestBean.class)) {
   ProxyFactoryBean pfb = new ProxyFactoryBean();
   pfb.setTarget(this.postProcess(BeanUtils.instantiateClass(beanClass)));
   pfb.setAutodetectInterfaces(false);
   NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
   advisor.addMethodName("test1");
   advisor.setAdvice((MethodInterceptor) invocation -> {
    LOGGER.info("ProxyBpp4  Start execution ...");
    Object result = invocation.getMethod().invoke(invocation.getThis(), invocation.getArguments());
    LOGGER.info("ProxyBpp4  End execution ...");
    return result;
   });
   pfb.addAdvisor(advisor);

   return pfb.getObject();
  }
  return null;
 }
 ...
}

In the above two ways, note that after instantiating bean, the injection of object-related attributes and the initialization process of objects are actively completed by postProcess method and AbstractAutowireCapableBeanFactory.

v7. Source sharing

Click me to view the source code. If you have any questions, please pay attention to WeChat official account for consultation.

Summarize


Related articles: