spring's way to God chapter 33: ProxyFactoryBean creates AOP proxy

Aop related reading

Before reading this article, you need to master the contents of the following three articles, otherwise it will be difficult.

  1. Spring Series Part 15: detailed explanation of proxy (java Dynamic Proxy & cglib proxy)

  2. Spring Series Part 30: jdk dynamic proxy and cglib proxy

  3. Spring series chapter 31: Aop concept explanation

  4. Spring series Chapter 32: AOP core source code and principle explanation

This article continues Aop.

There are two main ways in which AOP creates agents

Manual mode

Also known as manual method, you need to hard code to create agents one by one.

Automated way

Also known as batch method, batch method is used in spring environment to create proxies for qualified beans through bean post processor

The manual mode is basically hard coded, which is relatively more flexible and can be used away from the spring environment, while the automatic mode is mainly used in the spring environment, which is easier and more powerful to integrate with spring.

AOP creates proxy related classes

The proxycreator support on the left and the following are manual methods. There are three classes.

Under the AbstractAutoProxyCreator on the right is the method of automatically creating agents, mainly with five implementation classes.

Manual 3 modes

ProxyFactory mode

This method is hard coded and can be used directly without spring. It is widely used. The creation of agents in the automatic method depends on ProxyFactory. Therefore, we must understand the principle of this method. It has been introduced in the previous article. If you are not clear, you can take a look at it: Spring series Chapter 32: AOP core source code and principle explanation

AspectJProxyFactory mode

AspectJ is an aspect oriented framework. It is the best and most convenient AOP framework at present. Spring integrates it. It is very convenient to realize AOP proxy through some functions provided by AspectJ. The next article will explain it in detail.

ProxyFactoryBean mode

This article mainly introduces a way to create a proxy for a specified bean in the Spring environment.

ProxyFactoryBean

This class implements an interface FactoryBean. If the FactoryBean is unclear, you can see: Spring Series Part 5: how do you know about creating bean instances?

ProxyFactoryBean creates a proxy object for the specified bean through FactoryBean.

To create an agent, there are three key messages:

  1. Functions that need to be enhanced are implemented in Advice

  2. target: indicates which object you need to enhance

  3. Proxy object: a proxy object formed by combining the enhanced functions with the target object. The target object is accessed through the proxy object to enhance the target object.

Using ProxyFactoryBean also revolves around three parts. The steps used by ProxyFactoryBean are as follows:

1.establish ProxyFactoryBean object
2.adopt ProxyFactoryBean.setTargetName Sets the of the target object bean Name, target object is spring One of the containers bean
3.adopt ProxyFactoryBean. setInterceptorNames Add notifications that need to be enhanced
4.take ProxyFactoryBean Register to Spring Container, assuming the name is proxyBean
5.from Spring Find name is proxyBean of bean,this bean Is to generate a good proxy object

Last case.

Class Service1

package com.javacode2018.aop.demo8.test1;

public class Service1 {

    public void m1() {
        System.out.println("I am m1 method");
    }

    public void m2() {
        System.out.println("I am m2 method");
    }
}

demand

Register the bean of the above class in the spring container with the name of service1. The bean is enhanced through proxy to receive two notifications

A pre notification: before calling any method in service1, output a message: ready to call xxxx method

A wrap around notification: copy counts the time-consuming of all methods.

The following is the implementation of the code

package com.javacode2018.aop.demo8.test1;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;

import java.lang.reflect.Method;

@Configuration
public class MainConfig1 {
    //Register target object
    @Bean
    public Service1 service1() {
        return new Service1();
    }

    //Register a pre notification
    @Bean
    public MethodBeforeAdvice beforeAdvice() {
        MethodBeforeAdvice advice = new MethodBeforeAdvice() {
            @Override
            public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
                System.out.println("Ready to call:" + method);
            }
        };
        return advice;
    }

    //Register a post notification
    @Bean
    public MethodInterceptor costTimeInterceptor() {
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                long starTime = System.nanoTime();
                Object result = invocation.proceed();
                long endTime = System.nanoTime();
                System.out.println(invocation.getMethod() + ",time consuming(nanosecond): " + (endTime - starTime));
                return result;
            }
        };
        return methodInterceptor;
    }

    //Register ProxyFactoryBean
    @Bean
    public ProxyFactoryBean service1Proxy() {
        //1. Create ProxyFactoryBean
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        //2. Set the bean name of the target object
        proxyFactoryBean.setTargetName("service1");
        //3. Set the bean name list of the interceptor. Here are 2 (advice1 and advice2)
        proxyFactoryBean.setInterceptorNames("beforeAdvice""costTimeInterceptor");
        return proxyFactoryBean;
    }
}

Now start the spring container and get the proxy object

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);
//Get the proxy object. The name of the proxy object bean is the name of the registered ProxyFactoryBean, that is, service1Proxy
Service1 bean = context.getBean("service1Proxy", Service1.class);
System.out.println("----------------------");
//Method of calling proxy
bean.m1();
System.out.println("----------------------");
//Method of calling proxy
bean.m2();

Run output

----------------------
Ready to call:public void com.javacode2018.aop.demo8.test1.Service1.m1()
I am m1 method
public void com.javacode2018.aop.demo8.test1.Service1.m1(),time consuming(nanosecond): 8680400
----------------------
Ready to call:public void com.javacode2018.aop.demo8.test1.Service1.m2()
I am m2 method
public void com.javacode2018.aop.demo8.test1.Service1.m2(),time consuming(nanosecond): 82400

As you can see from the output, the target object service1 has been enhanced.

interceptorNames in ProxyFactoryBean

interceptorNames is used to specify the bean name list of interceptors. There are two common methods.

  • Batch mode

  • Non batch mode

Batch mode

usage method

proxyFactoryBean.setInterceptorNames("Need matching bean name*");

The bean names that need to be matched are followed by a *, which can be used for batch matching, such as interceptor *. At this time, spring will find all beans of the following 2 types from the container, and those whose bean names start with interceptor will be used as enhancers

org.springframework.aop.Advisor
org.aopalliance.intercept.Interceptor

When using this place, it should be noted that when registering in batch mode, if the type of enhancer is not the above two types, such as the following three types of notifications, we need to package it as Advisor, and MethodInterceptor is of Interceptor type, so we don't need to package it as Advisor type.

MethodBeforeAdvice(Method (pre notification)
AfterReturningAdvice(Method (post notification)
ThrowsAdvice(Exception notification)

Let's take a case and feel it.

case

Two enhancers are registered in batch below.

package com.javacode2018.aop.demo8.test2;

import com.javacode2018.aop.demo8.test1.Service1;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.lang.Nullable;

import java.lang.reflect.Method;

public class MainConfig2 {
    //Register target object
    @Bean
    public Service1 service1() {
        return new Service1();
    }

    //Define an intensifier: interceptor1, which is a pre notification inside and needs to be packaged as an Advisor type
    @Bean
    public Advisor interceptor1() {
        MethodBeforeAdvice advice = new MethodBeforeAdvice() {
            @Override
            public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
                System.out.println("Ready to call:" + method);
            }
        };
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setAdvice(advice);
        return advisor;
    }

    //Define an intensifier: interceptor2
    @Bean
    public MethodInterceptor interceptor2() {
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                long starTime = System.nanoTime();
                Object result = invocation.proceed();
                long endTime = System.nanoTime();
                System.out.println(invocation.getMethod() + ",time consuming(nanosecond): " + (endTime - starTime));
                return result;
            }
        };
        return methodInterceptor;
    }

    //Register ProxyFactoryBean
    @Bean
    public ProxyFactoryBean service1Proxy() {
        //1. Create ProxyFactoryBean
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        //2. Set the bean name of the target object
        proxyFactoryBean.setTargetName("service1");
        //3. Set the bean name list of the interceptor. Batch register here
        proxyFactoryBean.setInterceptorNames("interceptor*"); //@1
        return proxyFactoryBean;
    }
}

Two intensifiers are defined above:

interceptor1: pre notification. The package is of Advisor type

interceptor2: surround notification, of type MethodInterceptor

Test code

@Test
public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
    //Get the proxy object. The name of the proxy object bean is the name of the registered ProxyFactoryBean, that is, service1Proxy
    Service1 bean = context.getBean("service1Proxy", Service1.class);
    System.out.println("----------------------");
    //Method of calling proxy
    bean.m1();
    System.out.println("----------------------");
    //Method of calling proxy
    bean.m2();
}

Run output

----------------------
Ready to call:public void com.javacode2018.aop.demo8.test1.Service1.m1()
I am m1 method
public void com.javacode2018.aop.demo8.test1.Service1.m1(),time consuming(nanosecond): 10326200
----------------------
Ready to call:public void com.javacode2018.aop.demo8.test1.Service1.m2()
I am m2 method
public void com.javacode2018.aop.demo8.test1.Service1.m2(),time consuming(nanosecond): 52000

Non batch mode

usage

In the non batch mode, multiple enhancers need to be registered, and the bean names of multiple enhancers need to be clearly specified. Multiple enhancers are executed in the order specified in the parameters, such as

proxyFactoryBean.setInterceptorNames("advice1","advice2");

The bean types corresponding to advice1 and advice2 must be the types specified in the following list. The type range is wider than the matching method

MethodBeforeAdvice(Method (pre notification)
AfterReturningAdvice(Method (post notification)
ThrowsAdvice(Exception notification)
org.aopalliance.intercept.MethodInterceptor((surround notification)
org.springframework.aop.Advisor((consultant)

Let's take a case.

case

This time, three notifications are given to service1: pre, surround and post

package com.javacode2018.aop.demo8.test3;

import com.javacode2018.aop.demo8.test1.Service1;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.lang.Nullable;

import java.lang.reflect.Method;

public class MainConfig3 {
    //Register target object
    @Bean
    public Service1 service1() {
        return new Service1();
    }

    //Define a pre notification
    @Bean
    public MethodBeforeAdvice methodBeforeAdvice() {
        MethodBeforeAdvice methodBeforeAdvice = new MethodBeforeAdvice() {
            @Override
            public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
                System.out.println("Ready to call:" + method);
            }
        };
        return methodBeforeAdvice;
    }

    //Define a surround notification
    @Bean
    public MethodInterceptor methodInterceptor() {
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                long starTime = System.nanoTime();
                Object result = invocation.proceed();
                long endTime = System.nanoTime();
                System.out.println(invocation.getMethod() + ",time consuming(nanosecond): " + (endTime - starTime));
                return result;
            }
        };
        return methodInterceptor;
    }

    //Define a post notification
    @Bean
    public AfterReturningAdvice afterReturningAdvice() {
        AfterReturningAdvice afterReturningAdvice = new AfterReturningAdvice() {
            @Override
            public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
                System.out.println(method + ",completion of enforcement!");
            }
        };
        return afterReturningAdvice;
    }

    //Register ProxyFactoryBean
    @Bean
    public ProxyFactoryBean service1Proxy() {
        //1. Create ProxyFactoryBean
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        //2. Set the bean name of the target object
        proxyFactoryBean.setTargetName("service1");
        //3. Set the bean name list of the interceptor. Batch register here
        proxyFactoryBean.setInterceptorNames("methodBeforeAdvice""methodInterceptor""afterReturningAdvice");
        return proxyFactoryBean;
    }
}

Test code

@Test
public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
    //Get the proxy object. The name of the proxy object bean is the name of the registered ProxyFactoryBean, that is, service1Proxy
    Service1 bean = context.getBean("service1Proxy", Service1.class);
    System.out.println("----------------------");
    //Method of calling proxy
    bean.m1();
    System.out.println("----------------------");
    //Method of calling proxy
    bean.m2();
}

Run output

----------------------
Ready to call:public void com.javacode2018.aop.demo8.test1.Service1.m1()
I am m1 method
public void com.javacode2018.aop.demo8.test1.Service1.m1(),completion of enforcement!
public void com.javacode2018.aop.demo8.test1.Service1.m1(),time consuming(nanosecond): 12724100
----------------------
Ready to call:public void com.javacode2018.aop.demo8.test1.Service1.m2()
I am m2 method
public void com.javacode2018.aop.demo8.test1.Service1.m2(),completion of enforcement!
public void com.javacode2018.aop.demo8.test1.Service1.m2(),time consuming(nanosecond): 76700

Source code analysis

The focus is on the following method

org.springframework.aop.framework.ProxyFactoryBean#getObject

Source code:

public Object getObject() throws BeansException {
    //Initialize the advisor chain
    initializeAdvisorChain();
    //Is it a single instance
    if (isSingleton()) {
        //Create a singleton proxy object
        return getSingletonInstance();
    }
    else {
        //Create multiple proxy objects
        return newPrototypeInstance();
    }
}

The initializeAdvisorChain method is used to initialize the advisor (interceptor) chain. According to the interceptorNames configuration, find the qualified interceptors in the spring container and put them into the configuration for creating aop agents

private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
    if (!ObjectUtils.isEmpty(this.interceptorNames)) {
        //  polling   interceptorNames
        for (String name : this.interceptorNames) {
            //Batch registration method: judge whether name ends with *
            if (name.endsWith(GLOBAL_SUFFIX)) {
                //@1: Find a matching enhancer from the container and add it to the aop configuration
                addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
                                 name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
            }
            else {
                //Non matching method: find the bean by name, wrap it as Advisor and throw it into the aop configuration
                Object advice;
                //Find bean s from container
                advice = this.beanFactory.getBean(name);
                //@2: Add advice to the interceptor list
                addAdvisorOnChainCreation(advice, name);
            }
        }
    }
}

@1: addGlobalAdvisor batch mode. Take a look at the source code. It's relatively simple

/**
 * Add all global interceptors and pointcuts,
 * For all beans of type Advisor/Interceptor in the container, the bean name beginning with prefix will be added to the interceptor chain
 */

private void addGlobalAdvisor(ListableBeanFactory beanFactory, String prefix) {
    //Get all bean s of type Advisor in the container
    String[] globalAdvisorNames =
        BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Advisor.class);
    //Gets all bean s of type Interceptor in the container
    String[] globalInterceptorNames =
        BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Interceptor.class);
    List<Object> beans = new ArrayList<>(globalAdvisorNames.length + globalInterceptorNames.length);
    Map<Object, String> names = new HashMap<>(beans.size());
    for (String name : globalAdvisorNames) {
        Object bean = beanFactory.getBean(name);
        beans.add(bean);
        names.put(bean, name);
    }
    for (String name : globalInterceptorNames) {
        Object bean = beanFactory.getBean(name);
        beans.add(bean);
        names.put(bean, name);
    }
    //To sort beans, you can implement the Ordered interface. The sorting rule is order asc
    AnnotationAwareOrderComparator.sort(beans);
    for (Object bean : beans) {
        String name = names.get(bean);
        //Determine whether the bean starts with prefix
        if (name.startsWith(prefix)) {
            //Add it to the interceptor chain
            addAdvisorOnChainCreation(bean, name);
        }
    }
}

@2: addAdvisorOnChainCreation

private void addAdvisorOnChainCreation(Object next, String name) {
    //namedBeanToAdvisor is used to convert a bean to an advisor
    Advisor advisor = namedBeanToAdvisor(next);
    //Add advisor to interceptor chain
    addAdvisor(advisor);
}

namedBeanToAdvisor method

private AdvisorAdapterRegistry advisorAdapterRegistry = new DefaultAdvisorAdapterRegistry();

private Advisor namedBeanToAdvisor(Object next) {
    //Wrap objects as Advisor objects
    return this.advisorAdapterRegistry.wrap(next);
}

For those unclear about advisor adapter registry, please refer to the previous article: Spring series Chapter 32: AOP core source code and principle explanation

The advisorAdapterRegistry#wrap method wraps the adviceObject as an Advisor object. The code is as follows, which is relatively simple

public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    if (adviceObject instanceof Advisor) {
        return (Advisor) adviceObject;
    }
    if (!(adviceObject instanceof Advice)) {
        throw new UnknownAdviceTypeException(adviceObject);
    }
    Advice advice = (Advice) adviceObject;
    if (advice instanceof MethodInterceptor) {
        return new DefaultPointcutAdvisor(advice);
    }
    for (AdvisorAdapter adapter : this.adapters) {
        if (adapter.supportsAdvice(advice)) {
            return new DefaultPointcutAdvisor(advice);
        }
    }
    throw new UnknownAdviceTypeException(advice);
}

summary

  1. There are two main ways to create proxies in Spring: manual and automatic

  2. The manual method is hard coded. You can only create proxy objects for one target object at a time. It is relatively flexible and more flexible for developers. It can usually be used independently of the spring environment; The automation method is mainly used in the spring environment. It is usually a matching method to create proxies for qualified target bean s, which is easier to use

  3. The ProxyFactoryBean introduced in this article is used to create proxy objects for specified beans in the spring environment. It is not used too much. You can learn about it

Case source code

https://gitee.com/javacode2018/spring-series

All the case codes of passerby a java will be put on this in the future. Let's watch it and continue to pay attention to the dynamics.

Source: https://mp.weixin.qq.com/s?__ biz=MzA5MTkxMDQ4MQ==&mid=2648934977&idx=1&sn=8e4caf6a17bf5e123884df81a6382214&chksm=8862127fbf159b699c4456afe35a17f0d7bed119a635b11c154751dd95f59917487c895ccb84&scene=21#wechat_ redirect

Posted by unify34 on Mon, 01 Nov 2021 10:08:16 -0700