The above two articles talk about handwritten IOC and DI. This article is about handwritten AOP. Because the code written in the above two articles is used in the content, the link is posted here:

Handwritten Spring IOC container: Click to enter

Handwriting Spring DI dependency Injection: Click to enter


AOP analysis

What is the AOP

Aspect Oriented Programming To enhance the methods of a class without changing the code of the class

So if you want to implement an AOP, all you need to do is provide AOP functionality to the users that use it, and be able to enhance the methods of the class through AOP technology implementations

Elements in AOP

  • Advice, which is enhanced functionality
  • Join points, which methods are available for enhancement
  • Pointcut pointcuts that select method points to cut into, that is, which methods to enhance
  • Aspact facets, multiple method points for selection + enhanced functionality, i.e. a combination of Advice and Pointcut
  • Introduction Adding new methods or attributes to an existing class is called Introduction
  • Weaving in means not changing the original code and adding enhanced functionality

For handwriting AOP, some of the above elements need to be provided by the user: Advice, Pointcut, Aspact, users need to provide enhanced functionality and Pointcut, aspect information; AOP frameworks need to provide Join points, Weaving

The functionality provided by AOP

  • Enhancements are required, namely Advice notifications
  • To enhance a class’s methods, you need an optional method point to enhance, the Pointcut
  • Weaving in requires enhancement of functionality without changing the original class code

The illustration of AOP

Suppose we now play a game of King of Glory and choose a hero, Lu Ban, who needs to play the entire game to the end, regardless of whether he hangs up. At the beginning, there was no equipment. Suppose that each part of Lu Ban’s body was Join points, that is, these parts needed equipment to enhance them, and then each piece of equipment was the function enhanced by Advice, such as increasing attack speed and health, etc. Now when you buy a shoe, you choose to enhance the speed, that is, wear it on your feet. So that’s a Pointcut, and then the shoe plus the foot is an Aspact cut.

Characteristics analysis

  1. Advice

To realize an Advice, we need to know the characteristics of Advice. From the above we can know that Advice is provided by users, so there are many uncontrollable factors

  • User: Enhanced functionality is provided by the user, that is, the code for enhanced functionality is written by the user
  • Variability: Since the user provides, the enhanced logic is different for different requirements
  • Optional timing: The user can choose to perform enhancements before, after, or during an exception
  • Multiplicity: The same pointcut, that is, the same method, can have multiple enhancements, more than once
  1. Pointcut

Pointcuts, which know from above that they are also user-provided, are basically similar in character to Advice

  • User-friendliness: Users specify which methods are enhanced
  • Variability: Users can specify it flexibly
  • Multipoint: Users can choose to enhance functionality at multiple method points
  1. Weaving

Weaving, the part of the code that needs to be provided by the framework, is the logic that we need to implement ourselves, through which we can add enhanced functionality to user-specified methods

  • Non-invasive: Do not change the code of the original class to achieve functional enhancements
  • We need to implement the logic ourselves in an AOP framework

Advice, Pointcut and Weaving are the three parts that need to be done. Why not implement Join points? These parts can be known during code writing


Advice to implement

Advice is implemented by the user, and this part of the logic needs to be written by the user and used when we implement AOP; We need to know the user’s stuff, the user needs to use the framework we write, and we need to insulate the user from variability

So what you need to do, you can define a standard interface, and the user can implement the interface to provide different enhancement logic, and that’s the way to deal with change, programming to the interface

Defining Advice Interface

Advice is a top-level interface that does not need to write any methods, and then implements the Advice interface according to the pre-position, post-position, surround, and exception enhancement parameters

Advice interface:

/ * * *@className: Advice
 * @description: standard interface for notifications *@author TR
 */
public interface Advice {}Copy the code

First, we know that an enhancement is an enhancement to a method, so when you use Advice, all you need to give is some information about the method

  1. Lead to enhance

If the method is enhanced before execution, you know that the pre-enhancement does not require a return value. The following parameters are required:

  • Method itself
  • Method Object[]
  • The class (Object) of the method
  1. The rear enhancement

The return value of the method is required, because if I need to do some processing on the return value, I need to use it, and post enhancement also does not need to return value, the required parameters are as follows:

  • Method itself
  • Method Object[]
  • The class (Object) of the method
  • The return value of the Object method
  1. Surrounding the enhancement

The return value of the enhanced method is the return value of the enhanced method. The parameters required are as follows:

  • Method itself
  • Method Object[]
  • The class (Object) of the method
  1. Abnormal enhancement

Capture the exception information at method execution and then enhance it, and it also needs to be enhanced by wrapping methods, which can be implemented in wrap enhancement

Through the above know, need to define three methods: enhanced front, rear enhanced, surround and abnormal enhancement method, that the three methods are defined in an interface, or three interface, according to the single responsibility principle, or three interface to achieve better, but also can according to different interface type to distinguish between what is Advice

Define front, back, surround, and exception enhancement interfaces

Define the pre-enhanced interface MethodBeforeAdvice:

/ * * *@className: MethodBeforeAdvice
 * @description: Front-end enhanced interface *@author TR
 */
public interface MethodBeforeAdvice extends Advice {

    /** ** pre-enhanced method *@paramMethod: The method to be executed *@paramTarget: the target object on which the method is executed@paramArgs: arguments to the execution method *@return: void
     **/
    void before(Method method, Object target, Object[] args);
}
Copy the code

Define AfterReturningAdvice:

/ * * *@className: AfterReturningAdvice
 * @description: Rear enhanced interface *@author TR
 */
public interface AfterReturningAdvice extends Advice {
    
    /** * post-enhancement method *@paramMethod: The method to be executed *@paramTarget: the target object on which the method is executed@paramArgs: arguments to the execution method *@paramReturnValue: the returnValue * of the execution method@return: void
     **/
    void after(Method method, Object target, Object[] args, Object returnValue);
}
Copy the code

Define the surround, exception-enhanced interface MethodInterceptor:

/ * * *@className: MethodInterceptor
 * @description: Surround, exception enhancement interface *@author TR
 */
public interface MethodInterceptor extends Advice {

    /** * surround, exception-enhanced methods, in the method implementation need to call the target method *@paramMethod: The method to be executed *@paramTarget: the target object on which the method is executed@paramArgs: arguments to the execution method *@return: Java.lang. Object The return value of the execution method *@throws Throwable
     **/
    Object invoke(Method method, Object target, Object[] args) throws Throwable;
}
Copy the code

The class diagram is as follows:


Pointcut implementation

Pointcut features:

  • User-friendliness: Users specify which methods are enhanced
  • Variability: Users can specify it flexibly
  • Multipoint: Users can choose to enhance functionality at multiple method points

We need to give the user something that gives them the flexibility to specify method points, and when we get it, we know which method points they specified

Specify which methods to enhance, what information to specify, which is actually one or more methods, and if there’s an overload, so this thing that you specify, is a complete method signature, right

The specified information can describe a class of methods, such as:

  • A method of a class in a package
  • All methods of a class under a package
  • Methods that start with GET in all classes under a package
  • Methods that start with get and end with sevice in all classes under a package
  • Methods that start with GET and end with sevice in all classes under a package and its subpackages

You can then define an expression to describe the above information, which is the package name. Class name. Method name (parameter type)

How does each of these pieces require it?

  • Package name: must have parent-child characteristics, can be fuzzy matching
  • Class name: Can be fuzzy match
  • Method name: Fuzzy matching is possible
  • Parameter types: There can be multiple parameters

The expression defined is needed to determine whether a method of a class needs to be enhanced, so it needs to match the class and the matching method

Matching expressions:

  • Regular expression
  • AspactJ expression

AspactJ expression learning

/ * * *@className: AspectJTest
 * @description: AspectJ test class */
public class AspectJTest {

    public static void main(String[] args) throws NoSuchMethodException {
        // Get the pointcut resolver
        PointcutParser pp = PointcutParser
                .getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
        // The pointcut parser resolves a type matcher according to the rules
        TypePatternMatcher tp = pp.parseTypePattern("di.*");
        // Generate a pointcut expression from the expression
        PointcutExpression pe = pp.parsePointcutExpression("execution(* demo.beans.BeanFactory.get*(..) )");

        // Matches MagicGril's getName methodClass<? > cl = MagicGril.class; Method method = cl.getMethod("getName".null);
        // Match method execution
        ShadowMatch sm = pe.matchesMethodExecution(method);
        System.out.println("Match method:" + sm.alwaysMatches());
        System.out.println(cl.getName() + ", match expression:" + pe.couldMatchJoinPointsInType(cl));
        System.out.println(DefaultBeanFactory.class + ", match expression:" + pe.couldMatchJoinPointsInType(DefaultBeanFactory.class));

        System.out.println(cl.getName() +"All methods under class:");
        for(Method method1 : cl.getMethods()) { System.out.println(method1.getName()); }}}Copy the code

Output result:

From the above description, we can know, how to design the tangent point

The properties that pointcuts need are pointcuts expressions, and the functions that need to be provided are matching classes and matching methods

And we could define a top-level interface like Advice, because if there are other expressions that are more useful in the future and need to be extended, then we just inherit the top-level interface and implement the behavior of the matching classes and methods we define, regardless of how it is implemented internally. Right

Define the PointCut interface

/ * * *@className: PointCut
 * @description: The pointcut matches the interface *@author: TR
 */
public interface PointCut {

    /** * Match class *@author: jinpeng.sun
     * @date: 2021/4/19 13:46
     * @paramTargetClass: matched targetClass *@return: boolean
     **/
     boolean matchClass(Class
        targetClass);

     /** * match method *@author: jinpeng.sun
      * @date: 2021/4/19 13:46
      * @paramMethod: matching target method *@paramTargetClass: matched targetClass *@return: boolean
      **/
     boolean matchMethod(Method method, Class
        targetClass);
}
Copy the code

An implementation class that defines regular expressions: RegExpressionPointcut

This is not implemented here, mainly using AspactJ

/ * * *@className: RegExpressionPointcut
 * @description: regular expression implementation class *@author: TR
 */
public class RegExpressionPointcut implements PointCut {

    @Override
    public boolean matchClass(Class
        targetClass) {
        return false;
    }

    @Override
    public boolean matchMethod(Method method, Class
        targetClass) {
        return false; }}Copy the code

Define AspectJ tangent point expression of implementation class: AspectJExpressionPointcut

/ * * *@className: AspectJExpressionPointcut
 * @description: AspectJ pointcut expression implementation class *@author: TR
 */
public class AspectJExpressionPointcut implements PointCut {

    /** get the pointcut parser */
    PointcutParser pp = PointcutParser
            .getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();

    /** The string form of the pointcut expression */
    private String expression;

    /** Pointcut expressions in AspectJ */
    private PointcutExpression pe;

    public AspectJExpressionPointcut(String expression) {
        super(a);this.expression = expression;
        pe = pp.parsePointcutExpression(expression);
    }


    @Override
    public boolean matchClass(Class
        targetClass) {
        return pe.couldMatchJoinPointsInType(targetClass);
    }

    @Override
    public boolean matchMethod(Method method, Class
        targetClass) {
        ShadowMatch sm = pe.matchesMethodExecution(method);
        return sm.alwaysMatches();
    }

    public String getExpression(a) {
        returnexpression; }}Copy the code

Implementation steps:

  1. Maven introduces Aspectj jars
 <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.96.</version>
 </dependency>
Copy the code
  1. Get the pointcut resolver
 PointcutParser pp = PointcutParser
        .getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
Copy the code
  1. Parses the PointcutExpression to obtain the PointcutExpression
PointcutExpression pe = pp.parsePointcutExpression(
				"execution(* edu.dongnao.courseware.beans.BeanFactory.get*(..) )");
Copy the code
  1. Using PointcutExpression to match classes is unreliable
pe.couldMatchJoinPointsInType(targetClass)
Copy the code
  1. The PointcutExpression matching method is reliable
ShadowMatch sm = pe.matchesMethodExecution(method);
return sm.alwaysMatches();
Copy the code

The class diagram is as follows:


Aspact implementation

Advice and Pointcut are implemented, so how do users use them?

Advice is a user-provided enhanced implementation that requires interface inheritance, so you can configure Advice as a bean. Pointcut is a Pointcut expression that matches classes and methods. You do not need to configure Advice as a bean. Then adviceBeanName+expression forms the cut surface

Let’s implement the aspect through the appearance pattern, combining Advice and Pointcut

Defining the Advisor interface

/ * * *@className: Advisor
 * @description: Build the interface to the section, combining advice and pointcut *@author: TR
 */
public interface Advisor {

    /** * Get the name of the notification bean *@return: java.lang.String
     **/
    String getAdviceBeanName(a);

    /** * get the pointcut expression *@return: java.lang.String
     **/
    String getExpression(a);
}
Copy the code

Define the PointcutAdvisor interface

/ * * *@className: PointcutAdvisor
 * @descriptionPointcut notifier, inherited from Advisor, extends pointcut *@author: TR
 */
public interface PointcutAdvisor extends Advisor {

    /** * get the pointcut *@return: PointCut
     **/
    PointCut getPointCut(a);
}
Copy the code

Define the AspectJPointcutAdvisor implementation class

/ * * *@className: AspectJPointcutAdvisor
 * @description: notifier implementation class * for AspectJ pointcut expressions@author: TR
 */
public class AspectJPointcutAdvisor implements PointcutAdvisor {

    /** The name of the notification bean */
    private String adviceBeanName;

    /** expression */
    private String expression;

    Tangent point / * * * /
    private PointCut pointCut;

    public AspectJPointcutAdvisor(String adviceBeanName, String expression) {
        this.adviceBeanName = adviceBeanName;
        this.expression = expression;
        this.pointCut = new AspectJExpressionPointcut(expression);
    }

    @Override
    public PointCut getPointCut(a) {
        return pointCut;
    }

    @Override
    public String getAdviceBeanName(a) {
        return adviceBeanName;
    }

    @Override
    public String getExpression(a) {
        returnexpression; }}Copy the code

The following classes are not implemented and use the form AspactJ

/ * * *@className: RegPointcutAdvisor
 * @description: notifier implementation class * for regular pointcut expressions@author: TR
 */
public class RegPointcutAdvisor implements PointcutAdvisor {

    /** The name of the notification bean */
    private String adviceBeanName;

    /** expression */
    private String expression;

    Tangent point / * * * /
    private PointCut pointCut;

    public RegPointcutAdvisor(String adviceBeanName, String expression) {
        this.adviceBeanName = adviceBeanName;
        this.expression = expression;
        this.pointCut = new RegExpressionPointcut();
    }

    @Override
    public PointCut getPointCut(a) {
        return null;
    }

    @Override
    public String getAdviceBeanName(a) {
        return null;
    }

    @Override
    public String getExpression(a) {
        return null; }}Copy the code

The class diagram is as follows:

Figure seen from above, if we extend other the tangent point of expression, so in the implementation class, you can see, there are a lot of duplicate code, if so can also optimize the, can be on the upper deck of the implementation classes, and an abstract class, to inherit PointcutAdvisor, then the main point of tangency implementation inheritance this abstract class


Has implemented

Things to get done

It is up to us to add user-provided enhancements to the specified methods

When do I weave

When a Bean instance is created, weaving takes place after the Bean has been initialized

How do YOU know if the Bean is being enhanced

You need to traverse the Bean class and all of its methods, and then match the user-specified facets in turn. If there are matching facets, you need to enhance them

How to weave in, is need to use proxy!

The user needs to register the section, we also need to implement the logic to judge the match, weave, how to write this part of the code, need to write where

All you need to do now is add a handler to the Bean creation process. Later, more handlers may be added to the Bean creation process. If this part of the code is written in the BeanFactory, then this class will have a lot of code and will not be easy to extend later

Take a look at the Bean creation process:

The above diagram, each arrow is to add the extension points, there may be behind, need to add more processing logic on these points, you will need to design a kind of way, in the case of not changing the BeanFactory, flexible extension, you can use the observer pattern, there are several extension point, is there are several topics, six observer

Define the observer interface BeanPostProcessor

/ * * *@className: BeanPostProcessor
 * @descriptionAfter the Bean is instantiated and dependency injection is completed@author: TR
 */
public interface BeanPostProcessor {

    /** * The bean is processed before initialization *@author: TR
     * @paramBean: Bean instance *@paramBeanName: bean name *@return: java.lang.Object
     **/
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {
       return bean;
    }

    /** * Processing of bean after initialization *@author: TR
     * @paramBean: Bean instance *@paramBeanName: bean name *@return: java.lang.Object
     **/
    default Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
        returnbean; }}Copy the code

Define the notification interface Aware

/ * * *@className: Aware
 * @description: Build notification interface *@date: 2021/4/19 thus *@author: jinpeng.sun
 */
public interface Aware {}Copy the code
/ * * *@className: BeanFactoryAware
 * @description: Bean factory builds notification interface *@author: TR
 */
public interface BeanFactoryAware extends Aware {

    /** * The interface implementer gets the bean factory method *@author: TR
     * @param bf:
     * @return: void
     **/
    void setBeanFactory(BeanFactory bf);
}
Copy the code

The above interface is mainly used to get Bean factory information

Define AdvisorRegistry interface for notifier registration

This interface is mainly used to register the Advisor into the implementation class of the enhancement

/ * * *@className: AdvisorRegistry
 * @description: notifier registration interface *@author: TR
 */
public interface AdvisorRegistry {

    /** * Registered notifier *@author: TR
     * @param advisor:
     * @return: void
     **/
    public void registerAdvisor(Advisor advisor);

    /** * get the notifier list *@author: TR
     * @date: 2021/4/19 16:45
     *
     * @return: java.util.List<demo.aop.advisor.Advisor>
     **/
    public List<Advisor> getAdvisors(a);
}
Copy the code

The observer implementation class that defines enhanced processing AdvisorAutoProxyCreator

/ * * *@className: AdvisorAutoProxyCreator
 * @description: The user provides facets through the Advisor to inject DefaultBeanFactory into the implementation * inside the framework: DefaultBeanFactory injects the IOC container * DefaultBeanFactory calls the BeanPostProcessor interface related methods for enhancements *@author: TR
 */
public class AdvisorAutoProxyCreator implements BeanPostProcessor.BeanFactoryAware.AdvisorRegistry {

    /** List of notifiers */
    private List<Advisor> advisors;

    /** The current bean */
    private BeanFactory beanFactory;

    public AdvisorAutoProxyCreator(a) {
        this.advisors = new ArrayList<>();
    }

    @Override
    public void registerAdvisor(Advisor advisor) {
        this.advisors.add(advisor);
    }

    @Override
    public List<Advisor> getAdvisors(a) {
        return this.advisors;
    }

    @Override
    public void setBeanFactory(BeanFactory bf) {
        this.beanFactory = bf;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
        return null; }}Copy the code

Registered BeanPostProcessor

Added a method to register BeanPostProcessor in the BeanFactory interface

    /** Register BeanPostProcessor */
    void registerBeanPostProcessor(BeanPostProcessor beanPostProcessor);
Copy the code

Add the following methods to the DefaultBeanFactory implementation class:

    private List<BeanPostProcessor> postProcessors = Collections.synchronizedList(new ArrayList<>());

    @Override
    public void registerBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
        postProcessors.add(beanPostProcessor);
        if (beanPostProcessor instanceof BeanFactoryAware) {
            ((BeanFactoryAware) beanPostProcessor).setBeanFactory(this); }}Copy the code

Add handler before and after bean initialization to the getBean method:

 @Override
    public Object getBean(String beanName) throws Exception {
        // Get the bean definition
        BeanDefinition bd = beanDefinitionMap.get(beanName);
        Object bean = doGetBean(beanName);

        // Attribute injection
        setPropertyDIValues(bd, bean);

        // Bean processing before initialization
        bean = this.applyPostProcessBeforeInitialization(bean, beanName);

        // The bean's life cycle
        if (StringUtils.isNotBlank(bd.getInitMethodName())) {
            doInitMethod(bean,bd);
        }

        // Bean initialization after processing
        bean = this.applyPostProcessAfterInitialization(bean, beanName);

        return bean;
    }
    
    /** * Processing of bean after initialization *@author: TR 
     * @param bean: 
     * @param beanName: 
     * @return: java.lang.Object
     **/
    private Object applyPostProcessAfterInitialization(Object bean, String beanName) throws Exception {
        for (BeanPostProcessor bp : this.postProcessors) {
            bean = bp.postProcessBeforeInitialization(bean, beanName);
        }
        return bean;
    }

    /** * The bean is processed before initialization *@author: TR 
     * @param bean: 
     * @param beanName: 
     * @return: java.lang.Object
     **/
    private Object applyPostProcessBeforeInitialization(Object bean, String beanName) throws Exception {
        for (BeanPostProcessor bp : this.postProcessors) {
            bean = bp.postProcessAfterInitialization(bean, beanName);
        }
        return bean;
    }
Copy the code

The next thing you need to do is determine if the bean needs to be enhanced, so you need to get all the methods of the bean, and then walk through it according to the registered advisors, and then get the cut point to match the class and method

Modify AdvisorAutoProxyCreator postProcessAfterInitialization method in the class:

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
        // Determine whether section enhancement is required and get notification implementation of the enhancement
        List<Advisor> advisors = getMatchedAdvisors(bean, beanName);
        // Return the enhanced object if it needs to be enhanced
        if (CollectionUtils.isNotEmpty(advisors)) {
            // Use proxy mode for feature enhancement
        }
        return bean;
    }
Copy the code

Here is the method code to get a match:

    /** * Get the matching method *@author: TR
     * @paramBean: Bean instance *@paramBeanName: bean name *@return: void
     **/
    private List<Advisor> getMatchedAdvisors(Object bean, String beanName) {
        if (CollectionUtils.isEmpty(advisors)) {
            return null;
        }
        / / classClass<? > beanClass = bean.getClass();// Get all the methods
        List<Method> allMethods = getAllMethodForClass(beanClass);

        // Store the matching Advisor
        List<Advisor> advisors = new ArrayList<>();
        for (Advisor ad : this.advisors) {
            if (ad instanceof PointcutAdvisor) {
                // Check whether a match is found
                if(isPointcutMatchBean((PointcutAdvisor) ad, beanClass, allMethods)) { advisors.add(ad); }}}return advisors;
    }
Copy the code

Get all methods, using a utility class provided by the Spring Framework, to find all methods under the class:

    /** * get all methods *@author: TR
     * @param beanClass:
     * @return: void
     **/
    private List<Method> getAllMethodForClass(Class
        beanClass) {
        List<Method> allMethods = newLinkedList<>(); Set<Class<? >> classes =new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(beanClass));
        classes.add(beanClass);
        for(Class<? > cc : classes) {// Get all methods according to the utility class
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(cc);
            for(Method m : methods) { allMethods.add(m); }}return  allMethods;
    }
Copy the code

Here is a way to determine if there is a matching PointCut rule, using the method defined in PointCut:

   /** * a method to determine if there is a matching pointcut rule *@author: TR
     * @paramAD: section *@paramBeanClass: specifies the class *@paramAllMethods: allMethods under the specified class *@return: void
     **/
    private boolean isPointcutMatchBean(PointcutAdvisor ad, Class
        beanClass, List
       
         allMethods)
        {
        PointCut p = ad.getPointCut();

        / / match
        if(! p.matchClass(beanClass)) {return false;
        }
        for (Method m : allMethods) {
            // Match method
            if (p.matchMethod(m, beanClass)) {
                return true; }}return false;
    }
Copy the code

After deciding whether to enhance, it is necessary to implement proxy enhancement, so what is the implementation logic here

As you can see from the figure above, if you need to decide whether to use JDK dynamic proxies or CGLIB dynamic code, you can also abstract out an interface here. Then different proxy methods are implemented separately

Define the AopProxy proxy interface

/ * * *@className: AopProxy
 * @description: AOP proxy interface for creating and getting proxy objects *@author: TR
 */
public interface AopProxy {

    Object getProxy(a);

    Object getProxy(ClassLoader classLoader);
}
Copy the code

JDK dynamic proxy implementation

We know that JDK dynamic proxies work on interfaces, so what data is needed in the implementation?

  • The interface to implement
  • The target object
  • Match the Advisors,
  • BeanFactory

Required parameters:

  • The interface to implement
  • InvocationHandler
/ * * *@className: JdkDynamicAopProxy
 * @description: JDK dynamic proxy implementation *@author: TR
 */
public class JdkDynamicAopProxy implements AopProxy.InvocationHandler {
    private static final Logger logger = LoggerFactory.getLogger(JdkDynamicAopProxy.class);

    / / the name of the bean
    private String beanName;
    // The bean object that needs to be proxied
    private Object target;
    // Notification list, which needs to be enhanced
    private List<Advisor> advisors;
    // The current bean
    private BeanFactory beanFactory;

    public JdkDynamicAopProxy(String beanName, Object target, List<Advisor> advisors, BeanFactory beanFactory) {
        this.beanName = beanName;
        this.target = target;
        this.advisors = advisors;
        this.beanFactory = beanFactory;
    }

    @Override
    public Object getProxy(a) {
        return this.getProxy(target.getClass().getClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("For" + target + "Create a JDK agent.");
        }
        return Proxy.newProxyInstance(classLoader, target.getClass().getInterfaces(), this);
    }

    /** * The implementation of the InvocationHandler interface is used to perform proxy enhancement and return the actual result */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        returnAopProxyUtils.applyAdvices(target, method, args, advisors, proxy, beanFactory); }}Copy the code

CGLIB dynamic proxy implementation

The CGLIB dynamic proxy can perform operations on interfaces and classes, so it requires the following data:

  • The class to inherit from
  • The interface to implement
  • The target object
  • Match the Advisors,
  • BeanFactory
  • Construction parameter class
  • Structural parameters

Required parameters:

  • Inheritance of class
  • The interface to implement
  • Callback
  • Construction parameter type
  • Structural parameters

The construct parameter type and construct parameter are present when creating the instance

/ * * *@className: CglibDynamicAopProxy
 * @description: Cglib dynamic proxy *@author: TR
 */
public class CglibDynamicAopProxy implements AopProxy.MethodInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(CglibDynamicAopProxy.class);
    private static Enhancer enhancer = new Enhancer();

    / / the name of the bean
    private String beanName;
    // The bean object that needs to be proxied
    private Object target;
    // Notification list, which needs to be enhanced
    private List<Advisor> advisors;
    // The current bean
    private BeanFactory beanFactory;

    public CglibDynamicAopProxy(String beanName, Object target, List<Advisor> advisors, BeanFactory beanFactory) {
        this.beanName = beanName;
        this.target = target;
        this.advisors = advisors;
        this.beanFactory = beanFactory;
    }

    @Override
    public Object getProxy(a) {
        return this.getProxy(target.getClass().getClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("For" + target + "Create the Cglib proxy."); } Class<? > superClass =this.target.getClass();
        enhancer.setSuperclass(superClass);
        enhancer.setInterfaces(this.getClass().getInterfaces());
        enhancer.setCallback(this); Constructor<? > constructor =null;

        try {
            constructor = superClass.getConstructor(newClass<? > [] {}); }catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        if(constructor ! =null) {
            return enhancer.create();
        } else {
            BeanDefinition bd = ((DefaultBeanFactory)beanFactory).getBeanDefinition(beanName);
            returnenhancer.create(bd.getConstructor().getParameterTypes(), bd.getConstructorArgumentRealValues()); }}@Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        returnAopProxyUtils.applyAdvices(target, method, objects, advisors, o, beanFactory); }}Copy the code

AopProxyUtils what does it do, and as you can see, this part of the processing, which is used for enhancement, is done using Advice, so the logic for enhancement is the same, right

/ * * *@className: AopProxyUtils
 * @description: AOP proxy tool classes *@author: TR
 */
public class AopProxyUtils {


    /** * Apply advice to the method to get the final result *@author: TR
     * @paramTarget: the object to be enhanced *@paramMethod: The method that needs to be enhanced *@paramArgs: arguments to the enhanced method *@paramAdvisors: Matched slice *@paramProxy: the enhanced proxy object * of the bean object@paramBeanFactory: Bean factory *@return: java.lang.Object
     **/
    public static Object applyAdvices(Object target, Method method, Object[] args, List
       
         advisors, Object proxy, BeanFactory beanFactory)
        throws Throwable {
        // Get the enhanced advices for the current method
        List<Object> advices = AopProxyUtils.getShouldApplyAdvices(target.getClass(), method, advisors, beanFactory);
        if (CollectionUtils.isEmpty(advices)) {
            return method.invoke(target, args);
        } else {
            // Use chain of responsibility for enhancement
            AopAdviceChainInvocation ac = new AopAdviceChainInvocation(proxy, target, method, args, advices);
            returnac.invoke(); }}/** * Get a list of Advice facets that match the method *@author: TR
     * @param aClass:
     * @param method:
     * @param advisors:
     * @param beanFactory:
     * @return: java.util.List<demo.aop.advice.Advice>
     **/
    private static List<Object> getShouldApplyAdvices(Class
        aClass, Method method, List
       
         advisors, BeanFactory beanFactory)
        throws Exception {
        if (CollectionUtils.isEmpty(advisors)) {
            return null;
        }
        List<Object> advices = new ArrayList<>();
        for (Advisor advisor : advisors) {
            if (advisor instanceof PointcutAdvisor) {
                if(((PointcutAdvisor)advisor).getPointCut().matchMethod(method, aClass)) { advices.add(beanFactory.getBean(advisor.getAdviceBeanName())); }}}returnadvices; }}Copy the code

How do I pass the data acquired when creating the bean instance into the initialized Aop, using ThreadLocal to hold parameter values in the BeanDefinition

BeanDefinition adds the following method:

   /** Gets the construction parameter value */
    public Object[] getConstructorArgumentRealValues();

    /** Sets the construction parameter value */
    public void setConstructorArgumentRealValues(Object[] values);
Copy the code

Corresponding implementation in the GeneralBeanDefinition class:

   private ThreadLocal<Object[]> realConstructorArgumentValues = new ThreadLocal<>();

    @Override
    public Object[] getConstructorArgumentRealValues() {
        return realConstructorArgumentValues.get();
    }

    @Override
    public void setConstructorArgumentRealValues(Object[] values) {
       this.realConstructorArgumentValues.set(values);
    }
Copy the code

AopAdviceChainInvocation class

/ * * *@className: AopAdviceChainInvocation
 * @description: AOP responsibility chain calls class *@author: TR
 */
public class AopAdviceChainInvocation {

    /** AOP responsibility chain execution method */
    private static Method invokeMethod;

    static {
        try {
            invokeMethod = AopAdviceChainInvocation.class.getMethod("invoke".null);
        } catch(NoSuchMethodException e) { e.printStackTrace(); }}// Proxy class object
    private Object proxy;
    // Target class object
    private Object target;
    // Invoke the executed object method
    private Method method;
    // Arguments to the execution method
    private Object[] args;
    // Method enhanced functionality: notification list
    private List<Object> advices;

    public AopAdviceChainInvocation(Object proxy, Object target, Method method, Object[] args, List<Object> advices) {
          this.proxy = proxy;
          this.target = target;
          this.method = method;
          this.args = args;
          this.advices = advices;
    }

    // The index number of the chain of responsibility execution record
    private int i = 0;

    public Object invoke(a) throws Throwable {
        if (i < this.advices.size()) {
            Object advice = this.advices.get(i++);
            if (advice instanceof MethodBeforeAdvice) {
                // Perform pre-enhancement
                ((MethodBeforeAdvice)advice).before(method, target, args);
            } else if (advice instanceof MethodInterceptor) {
                // Perform surround enhancement and exception enhancement. The method and object given here are the Invoke method and chain object
                ((MethodInterceptor)advice).invoke(invokeMethod,this.null);
            } else if (advice instanceof AfterReturningAdvice) {
                // After the enhancement, get the result first, before the enhancement
                Object returnValue = this.invoke();
                ((AfterReturningAdvice)advice).after(method, target, args, returnValue);
                return returnValue;
            }
            / / callback
            return this.invoke();
        } else {
            returnmethod.invoke(target, args); }}}Copy the code

Define the AopProxyFactory interface

After the proxy approach is implemented, we need to use it. Here we use the factory pattern implementation:

/ * * *@className: AopProxyFactory
 * @description: the AOP proxy's factory interface *@author: TR
 */
public interface AopProxyFactory {

    /** * Get the implementation of the AOP proxy interface based on the argument *@param* bean: instance@paramBeanName: bean name *@paramAdvisors: List of advisors *@paramBeanFactory: Bean factory *@return: AopProxyFactory
     **/
    AopProxy createAopProxy(Object bean, String beanName, List<Advisor> advisors, BeanFactory beanFactory);

    /** * get the default AopProxyFactory *@return: AopProxyFactory
     **/
    static AopProxyFactory getDefaultAopProxyFactory(a) {
        return newDefaultAopProxyFactory(); }}Copy the code

Implementation class:

/ * * *@className: DefaultAopProxyFactory
 * @description: Agent factory implementation class *@author: TR
 */
public class DefaultAopProxyFactory implements AopProxyFactory {

    @Override
    public AopProxy createAopProxy(Object bean, String beanName, List<Advisor> advisors, BeanFactory beanFactory) {
        // Use JDK dynamic proxy or CGLIB dynamic proxy
        if (shouldUseJDKDynamicProxy(bean, beanName)) {
            return new JdkDynamicAopProxy(beanName, bean, advisors, beanFactory);
        } else {
            return newCglibDynamicAopProxy(beanName, bean, advisors, beanFactory); }}private boolean shouldUseJDKDynamicProxy(Object bean, String beanName) {
        return false; }}Copy the code

Here need to modify the AdvisorAutoProxyCreator postProcessAfterInitialization method in the class:

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
        // Determine whether section enhancement is required and get notification implementation of the enhancement
        List<Advisor> advisors = getMatchedAdvisors(bean, beanName);
        // Return the enhanced object if it needs to be enhanced
        if (CollectionUtils.isNotEmpty(advisors)) {
            // Use proxy mode for feature enhancement
            bean = this.createProxy(bean, beanName, advisors);
        }
        return bean;
    }
    
    private Object createProxy(Object bean, String beanName, List<Advisor> advisors) {
        return AopProxyFactory.getDefaultAopProxyFactory()
                .createAopProxy(bean, beanName, advisors, beanFactory)
                .getProxy();
    }
Copy the code

The way the constructor is cached in the DefaultBeanFactory class needs to be changed

The code cached in the determineConstructor method is commented out:

if(ct ! =null) {
// // For the prototype bean, cache it
// if (bd.isProtoType()) {
// bd.setConstructor(ct);
/ /}
            return ct;
        } else {
            throw new Exception("No constructor found:" + bd);
        }
Copy the code

Add the code to createBeanByConstructor

 /** Build bean */ with constructor
    private Object createBeanByConstructor(BeanDefinition bd) throws Exception {
        Object object = null;
        if (CollectionUtils.isEmpty(bd.getConstructorArgumentValues())) {
            // If the constructor parameter value is empty, no arguments are passed
            object = bd.getBeanClass().newInstance();
        } else {
            // If the obtained parameter value is empty, no parameter is passed
            Object[] args = getConstructorArgumentValues(bd);
            if (args == null) {
                object = bd.getBeanClass().newInstance();
            } else {
                // Determine the constructor based on the parameter matching, and then instantiate the callbd.setConstructorArgumentRealValues(args); Constructor<? > ct =this.determineConstructor(bd, args);
                // The cache constructor was moved here from determineConstructor, caching whether the prototype is cached or not, because AOP needs it later
                bd.setConstructor(ct);
                returnct.newInstance(args); }}return object;
    }
Copy the code

Test the

Pre-enhanced implementation:

public class MyBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object target, Object[] args) {
        System.out.println(this + "To" + target + "Pre-enhanced!"); }}Copy the code

Post-enhancement implementation:

public class MyAfterReturningAdvice implements AfterReturningAdvice {

    @Override
    public void after(Method method, Object target, Object[] args, Object returnValue) {
        System.out.println(this + "To" + target + "Post-enhanced, return value ="+ returnValue); }}Copy the code

Surround enhancement implementation:

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(Method method, Object target, Object[] args) throws Throwable {
        System.out.println(this + "对 " + target + "Circled forward enhancement.");
        Object ret = method.invoke(target, args);
        System.out.println(this + "对 " + target + "After the surround - enhanced. The return value of the method is: + ret);
        returnret; }}Copy the code

The test class:

public class AopTest {
    static PreBuildBeanFactory bf = new PreBuildBeanFactory();

    @Test
    public void testCirculationDI(a) throws Throwable {

        GeneralBeanDefinition bd = new GeneralBeanDefinition();
        bd.setBeanClass(Lad.class);
        List<Object> args = new ArrayList<>();
        args.add("Sun Wukong");
        args.add(new BeanReference("baigujing"));
        bd.setConstructorArgumentValues(args);
        bf.registerBeanDefinition("swk", bd);

        bd = new GeneralBeanDefinition();
        bd.setBeanClass(MagicGirl.class);
        args = new ArrayList<>();
        args.add(Bone fairy);
        bd.setConstructorArgumentValues(args);
        bf.registerBeanDefinition("baigujing", bd);

        bd = new GeneralBeanDefinition();
        bd.setBeanClass(Renminbi.class);
        bf.registerBeanDefinition("renminbi", bd);

        // Pre-enhance advice bean registration
        bd = new GeneralBeanDefinition();
        bd.setBeanClass(MyBeforeAdvice.class);
        bf.registerBeanDefinition("myBeforeAdvice", bd);

        // Surround enhanced advice bean registration
        bd = new GeneralBeanDefinition();
        bd.setBeanClass(MyMethodInterceptor.class);
        bf.registerBeanDefinition("myMethodInterceptor", bd);

        // Post-enhanced advice bean registration
        bd = new GeneralBeanDefinition();
        bd.setBeanClass(MyAfterReturningAdvice.class);
        bf.registerBeanDefinition("myAfterReturningAdvice", bd);

        // Register AOP's BeanPostProcessor in BeanFactory
        AdvisorAutoProxyCreator aapc = new AdvisorAutoProxyCreator();
        bf.registerBeanPostProcessor(aapc);
        // register Advisor with AdvisorAutoProxyCreator
        aapc.registerAdvisor(
                new AspectJPointcutAdvisor("myBeforeAdvice"."execution(* demo.di.MagicGirl.*(..) )"));
        // register Advisor with AdvisorAutoProxyCreator
        aapc.registerAdvisor(
                new AspectJPointcutAdvisor("myMethodInterceptor"."execution(* demo.di.Lad.say*(..) )"));
        // register Advisor with AdvisorAutoProxyCreator
        aapc.registerAdvisor(new AspectJPointcutAdvisor("myAfterReturningAdvice"."execution(* demo.di.Renminbi.*(..) )"));

        bf.preInstantiateSingletons();

        System.out.println("-----------------myBeforeAdvice---------------");
        MagicGirl gril = (MagicGirl) bf.getBean("baigujing");
        gril.getFriend();
        gril.getName();

        System.out.println("----------------myMethodInterceptor----------------");
        Boy boy = (Boy) bf.getBean("swk");
        boy.sayLove();

        System.out.println("-----------------myAfterReturningAdvice---------------");
        Renminbi rmb = (Renminbi) bf.getBean("renminbi"); rmb.pay(); }}Copy the code

Running results:

Other points in this article:

  • Java design pattern triggered by a security guard: appearance pattern
  • What? Java design Mode: Observer Mode
  • Drumming is reminiscent of the Java design pattern: the Chain of Responsibility pattern
  • Nuwa creates a Thought-provoking Java design pattern: Factory Pattern
  • Tony: Java Design pattern: Proxy pattern