[Spring] principle and implementation of AOP

1, Gossip

I'm a little sleepy today, but I still insist on learning for a while. Today, let's take a look at another major feature of Spring, AOP

2, Basic points

1. Basic concepts
AOP means aspect oriented programming. It is a technology to realize the unified maintenance of program functions through precompiled mode and dynamic agent during operation
You can understand that we can enhance the functions without changing the original business logic

2. Before learning AOP, we also need to understand the agent pattern
You can refer to my previous blog [structural design mode] agent mode

3. Key concepts of AOP

  • Aspect: a class whose crosscutting concerns are modularized. For example, we will write the logging function as a LogUtils class
  • Crosscutting concerns: it has nothing to do with our business logic, but we need to pay attention to the parts. For example, we want to add log printing function or security verification before the implementation of business code. The enhancement of these functions is the crosscutting concerns
  • Advice: the function that the aspect must implement can be understood as a method in the aspect class
  • Target: the notified object, that is, which object you want to enhance
  • Proxy: the object created after notification is applied to the target object, that is, our proxy class
  • Pointcut (entry point): inform the execution place of the aspect, that is, where you want to enhance the business logic function
  • Jointpoint: the execution point that matches the pointcut

3, Three implementation methods of AOP

1. Using Spring's API interface

First, we need to import AOP related dependencies

<dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.8</version>
        </dependency>
    </dependencies>

Then we write an interface and corresponding implementation class that supports addition, deletion, query and modification

package com.decade.service;
public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}

import com.decade.service.UserService;

public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("User increase");
    }

    @Override
    public void delete() {
        System.out.println("User delete");
    }

    @Override
    public void update() {
        System.out.println("User update");
    }

    @Override
    public void query() {
        System.out.println("User query");
    }
}

Suppose we want to add a log output before and after executing the business logic code
Without changing the original code logic, we need to use AOP
We create two new aspects, which are respectively responsible for log output before and after business execution
Note: the post enhancement has one more parameter, which means the return value of the execution target object method

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class BeforeLogUtil implements MethodBeforeAdvice {

    /**
     * Pre enhancement
     * @param method Method of the target object to execute
     * @param args parameter
     * @param target Target object
     * @throws Throwable Throw exception
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "of" + method.getName() + "Executed");
    }
}

import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class AfterLogUtil implements AfterReturningAdvice {

    /**
     * Post enhancement
     * @param returnValue The return value of the execution target object method
     * @param method Method of the target object to execute
     * @param args parameter
     * @param target Target object
     * @throws Throwable Throw exception
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("Yes" + method.getName() + "The returned result is" + returnValue);
    }
}

In addition, we also need to make relevant configuration. We create a new ApplicationContext in the resource folder XML file

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- register bean -->
    <bean id="userService" class="com.decade.service.impl.UserServiceImpl"/>
    <bean id="beforeLog" class="com.decade.util.BeforeLogUtil"/>
    <bean id="afterLog" class="com.decade.util.AfterLogUtil"/>

    <!-- Method 1: use native spring API Interface -->
    <!-- to configure AOP:Import required AOP Constraints of -->
    <aop:config>
        <!-- Pointcut settings, here execution The purpose of an expression is to indicate where to execute
         execution(<Modifier mode>?<Return type mode><Method name pattern>(<Parameter mode>)<Abnormal mode>?)
         Except for return type mode, method name mode and parameter mode, other items are optional.
         Here, the meaning of the parameter is the return value type method name parameter (the two ellipsis here indicate that it can be any parameter)
         com.decade.service.impl.UserServiceImpl.*express UserServiceImpl All methods under
         -->
        <aop:pointcut id="point" expression="execution(* com.decade.service.impl.UserServiceImpl.*(..))"/>
        <!-- Execute surround increase -->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="point"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="point"/>
    </aop:config>
</beans>

Finally, we write a test class to test

import com.decade.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // Because the dynamic proxy mode proxies the interface, we need to use the proxy object, that is, the interface
        UserService userService = context.getBean("userService", UserService.class);

        userService.add();
    }
}

The operation results are as follows

2. Use custom classes to implement

Different from method 1, we don't need to inherit the API interface of Spring. We can enhance the function through a custom aspect
We will create a new custom class to replace the BeforeLogUtil and AfterLogUtil above

package com.decade.util;

// Customize an aspect and put the notification in it
public class LogUtils {

    public void before() {
        System.out.println("=========Before method execution=============");
    }

    public void after() {
        System.out.println("=========After method execution=============");
    }
}

Then adjust the configuration in xml.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- register bean -->
    <bean id="userService" class="com.decade.service.impl.UserServiceImpl"/>
    
    <!-- Method 2: user defined class -->
    <bean id="logUtil" class="com.decade.util.LogUtils"/>
    <aop:config>
        <!-- Custom aspect, that is, the class that stores notifications -->
        <aop:aspect ref="logUtil">
            <!-- Configure the entry point, that is, where you want to enhance the business logic function -->
            <aop:pointcut id="point" expression="execution(* com.decade.service.impl.UserServiceImpl.*(..))"/>
            <!-- Notification, that is, the function that the aspect must implement, can be understood as a method in the aspect class -->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
</beans>

Run the test class in mode 1, and the results are as follows

3. Implementation using annotations

Key notes: @ Aspect, @ Before, @ After, @ Around

package com.decade.util;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

// This annotation indicates that this class is a facet
@Aspect
public class AnnotationPointCut {

    // Note that after the @ Before annotation, the value in parentheses is filled in the execution expression to indicate the position to be executed
    @Before("execution(* com.decade.service.impl.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("=========Before method execution=============");
    }

    // @The value in parentheses of the After annotation is also an execution expression
    @After("execution(* com.decade.service.impl.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("=========After method execution=============");
    }

    /**
     * Around Advice 
     * @param joinPoint Execution point representing the entry point
     * @throws Throwable Throw exception
     */
    @Around("execution(* com.decade.service.impl.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("=========Surround front=============");
        // Method execution is very important. This is the method of aop proxy chain execution
        Object proceed = joinPoint.proceed();
        System.out.println("=========After surround=============");

        // Print signature, modifier + package name + component name (class name) + method name
        System.out.println("Signature:" + joinPoint.getSignature());
        System.out.println("proceed:" + proceed);
    }
}

Then we open the annotation scanning related configuration in the xml file

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- register bean -->
    <bean id="userService" class="com.decade.service.impl.UserServiceImpl"/>
    <bean id="annotationPoint" class="com.decade.util.AnnotationPointCut"/>
    <!-- Open annotation, automatically spring Which configurations are in the container@aspectJ Tangential bean Create proxy, weave in cut
     It has one proxy-target-class Property, default to false,Indicates use jdk Dynamic proxy weaving enhancement
     When matched as true When, it means to use CGLib Dynamic agent technology weaving enhancement.
     But even if proxy-target-class Set to false,If the target class does not declare an interface, then spring Will be used automatically CGLib Dynamic agent
     -->
    <aop:aspectj-autoproxy/>
</beans>

The operation results are as follows

If there is any mistake, please correct it

Keywords: Java Spring Back-end

Added by washbucket on Fri, 25 Feb 2022 14:33:29 +0200