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