spring6-AOP aspect-oriented programming

1. Scene simulation

Build submodules: spring6-aop

1.1. Declare the interface

Declare the calculator interface Calculator, which contains abstract methods for addition, subtraction, multiplication and division.

public interface Calculator {
    
    
    
    int add(int i, int j);
    
    int sub(int i, int j);
    
    int mul(int i, int j);
    
    int div(int i, int j);
    
}
1.2. Create implementation class

Insert image description here

public class CalculatorImpl implements Calculator {
    
    
    
    @Override
    public int add(int i, int j) {
    
    
    
        int result = i + j;
    
        System.out.println("方法内部 result = " + result);
    
        return result;
    }
    
    @Override
    public int sub(int i, int j) {
    
    
    
        int result = i - j;
    
        System.out.println("方法内部 result = " + result);
    
        return result;
    }
    
    @Override
    public int mul(int i, int j) {
    
    
    
        int result = i * j;
    
        System.out.println("方法内部 result = " + result);
    
        return result;
    }
    
    @Override
    public int div(int i, int j) {
    
    
    
        int result = i / j;
    
        System.out.println("方法内部 result = " + result);
    
        return result;
    }
}
1.3. Create an implementation class with logging function

Insert image description here

public class CalculatorLogImpl implements Calculator {
    
    
    
    @Override
    public int add(int i, int j) {
    
    
    
        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
    
        int result = i + j;
    
        System.out.println("方法内部 result = " + result);
    
        System.out.println("[日志] add 方法结束了,结果是:" + result);
    
        return result;
    }
    
    @Override
    public int sub(int i, int j) {
    
    
    
        System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
    
        int result = i - j;
    
        System.out.println("方法内部 result = " + result);
    
        System.out.println("[日志] sub 方法结束了,结果是:" + result);
    
        return result;
    }
    
    @Override
    public int mul(int i, int j) {
    
    
    
        System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
    
        int result = i * j;
    
        System.out.println("方法内部 result = " + result);
    
        System.out.println("[日志] mul 方法结束了,结果是:" + result);
    
        return result;
    }
    
    @Override
    public int div(int i, int j) {
    
    
    
        System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
    
        int result = i / j;
    
        System.out.println("方法内部 result = " + result);
    
        System.out.println("[日志] div 方法结束了,结果是:" + result);
    
        return result;
    }
}
1.4. Ask questions

① Existing code defects

Regarding the implementation class with logging function, we found the following defects:

  • It interferes with core business functions, causing programmers to distract themselves from developing core business functions.
  • Additional functions are scattered in various business function methods, which is not conducive to unified maintenance

②Solution ideas

To solve these two problems, the core is: decoupling. We need to extract additional functionality from the business functionality code.

③Difficulty

Difficulty in solving the problem: the code to be extracted is inside the method, and it cannot be solved by extracting the repeated code in the subclass to the parent class. Therefore, new technologies need to be introduced.

2. Agent mode

2.1. Concept

①Introduction

One of the twenty-three design patterns, belonging to the structural pattern. Its function is to provide a proxy class, so that when we call the target method, we no longer call the target method directly, but indirectly through the proxy classCall. Separate code that is not part of the core logic of the target method from the target method - Decouple. When calling the target method, first call the method of the proxy object to reduce calls and interruptions to the target method. At the same time, allowing additional functions to be centralized together is also conducive to unified maintenance. ②Agent in life
Insert image description here
Insert image description here

  • Advertisers need to go through an agent when looking for big stars to shoot ads.
  • When a partner talks to the big boss about a cooperation offer, the meeting time needs to go through the secretary
  • Real estate agents act as agents for buyers and sellers

③Related terms

  • Proxy: After stripping out the non-core logic, encapsulate the classes, objects, and methods of these non-core logic.
  • Target: Classes, objects, and methods that have non-core logic code "applied" by the proxy.
2.2. Static proxy

Create a static proxy class:

public class CalculatorStaticProxy implements Calculator {
    
    
    
    // 将被代理的目标对象声明为成员变量
    private Calculator target;
    
    public CalculatorStaticProxy(Calculator target) {
    
    
        this.target = target;
    }
    
    @Override
    public int add(int i, int j) {
    
    
    
        // 附加功能由代理类中的代理方法来实现
        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
    
        // 通过目标对象来实现核心业务逻辑
        int addResult = target.add(i, j);
    
        System.out.println("[日志] add 方法结束了,结果是:" + addResult);
    
        return addResult;
    }
}

Static proxy does achieve decoupling, but because the code is hard-coded, it does not have any flexibility at all. Take the logging function as an example. If additional logs are needed in other places in the future, more static proxy classes will have to be declared, which will generate a lot of duplicate code. The logging function is still scattered and there is no unified management.

Put forward further requirements: Centralize the logging function into a proxy class. Any logging requirements in the future will be implemented through this proxy class. This requires the use of dynamic proxy technology.

2.3. Dynamic proxy

Insert image description here
Factory class that produces proxy objects:

public class ProxyFactory {
    
    

    private Object target;

    public ProxyFactory(Object target) {
    
    
        this.target = target;
    }

    public Object getProxy(){
    
    

        /**
         * newProxyInstance():创建一个代理实例
         * 其中有三个参数:
         * 1、classLoader:加载动态生成的代理类的类加载器
         * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
         * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
         */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
    
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                /**
                 * proxy:代理对象
                 * method:代理对象需要实现的方法,即其中需要重写的方法
                 * args:method所对应方法的参数
                 */
                Object result = null;
                try {
    
    
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                    System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
                } finally {
    
    
                    System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
                }
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}
2.4. Testing
@Test
public void testDynamicProxy(){
    
    
    ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());
    Calculator proxy = (Calculator) factory.getProxy();
    proxy.div(1,0);
    //proxy.div(1,1);
}

3. AOP concepts and related terms

3.1. Overview

AOP (Aspect Oriented Programming) is a design idea, which is aspect-oriented programming in the field of software design. It is a supplement and improvement of object-oriented programming. It is implemented through pre-compilation and runtime dynamic proxy. A technology that dynamically adds additional functionality to a program by modifying the source code. AOP can be used to isolate various parts of the business logic, thereby reducing the coupling between the various parts of the business logic, improving the reusability of the program, and improving the efficiency of development.

3.2. Related terms
①Crosscutting concerns

Solving the same problems scattered in each module, such as user authentication, log management, transaction processing, and data caching are all cross-cutting concerns.

The same type of non-core business extracted from each method. Within the same project, we can use multiple cross-cutting concerns to enhance related methods in many different ways.

This concept is not grammatical, but based on the logical needs of additional functions: there are ten additional functions, and there are ten cross-cutting concerns.

Insert image description here

②Notification (enhanced)

Enhancement, in layman's terms, refers to the functions you want to enhance, such as security, transactions, logs, etc.

Each cross-cutting concern needs to write a method to implement it. Such a method is called a notification method.

  • Pre-notification: Executed before the proxied target method
  • Return notification: Executed after the delegated target methodcompletes successfully(ends of life)
  • Exception notification: Executed after the proxy target methodends abnormally (died unexpectedly)
  • Post notification: Executed after the delegated target methodfinally ends ( final conclusion )
  • Surround notification: Use the try...catch...finally structure to surround the entire proxied target method, including all locations corresponding to the above four notifications
    Insert image description here
③Section

A class that encapsulates notification methods.
Insert image description here

④Goal

The target object being proxied.

⑤Agent

A proxy object created after applying notifications to the target object.

⑥Connection point

This is also a purely logical concept, not defined by grammar.

Arrange the methods in a row, each cross-cutting position is regarded as the x-axis direction, and the order of method execution from top to bottom is regarded as the y-axis. The intersection of the x-axis and the y-axis is the connection point. In layman’s terms, this is where spring allows you to use notifications
Insert image description here

⑦Entry point

How to position connection points.

Each class method contains multiple connection points, so connection points are things that exist objectively in the class (logically speaking).

If you think of a join point as a record in the database, then the entry point is the SQL statement that queries the record.

Spring's AOP technology can locate specific connection points through pointcuts. In layman's terms, it is necessary to actually enhance the method

Pointcuts are described through the org.springframework.aop.Pointcut interface, which uses classes and methods as query conditions for join points.

3.3. Function
  • Simplify the code: Extract the repeated code out of the method, so that the extracted method can focus more on its core functions , improve cohesion.

  • Code enhancement: Encapsulate specific functions into aspect classes. Wherever there is a need, just apply it and it will be applied to the aspect class. The logical method is enhanced by aspects.

4. Annotation-based AOP

4.1. Technical description

Insert image description here
Insert image description here

  • Dynamic proxies are divided into JDK dynamic proxies and cglib dynamic proxies
  • When the target class has an interface, JDK dynamic proxy and cglib dynamic proxy are used. When there is no interface, only cglib dynamic proxy can be used.
  • The proxy class dynamically generated by JDK dynamic proxy will be under the com.sun.proxy package. The class name is $proxy1 and implements the same interface as the target class.
  • The proxy class dynamically generated by cglib dynamic proxy will be in the same package as the target and will inherit the target class.
  • Dynamic proxy (InvocationHandler): JDK’s native implementation, the target class that needs to be proxied must implement the interface. Because this technical requirementthe proxy object and the target object implement the same interface (two brothers worship the pattern).
  • cglib: Implement proxy by inheriting the proxied target class (recognize godfather mode), so the target class does not need to implement the interface.
  • AspectJ: It is an implementation of AOP thinking. It is essentially a static proxy. The proxy logic is "woven into" the bytecode file compiled by the proxy target class, so the final effect is dynamic. . A weaver is a weaver. Spring just borrows the annotations from AspectJ.
4.2. Preparation work

①Add dependencies

Just add the following dependencies on top of the dependencies required by IOC:

<dependencies>
    <!--spring context依赖-->
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.2</version>
    </dependency>

    <!--spring aop依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>6.0.2</version>
    </dependency>
    <!--spring aspects依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.0.2</version>
    </dependency>

    <!--junit5测试-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!--log4j2的依赖-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.19.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j2-impl</artifactId>
        <version>2.19.0</version>
    </dependency>
</dependencies>

②Prepare the target resources to be proxied

interface:

public interface Calculator {
    
    
    
    int add(int i, int j);
    
    int sub(int i, int j);
    
    int mul(int i, int j);
    
    int div(int i, int j);
    
}

Implementation class:

@Component
public class CalculatorImpl implements Calculator {
    
    
    
    @Override
    public int add(int i, int j) {
    
    
    
        int result = i + j;
    
        System.out.println("方法内部 result = " + result);
    
        return result;
    }
    
    @Override
    public int sub(int i, int j) {
    
    
    
        int result = i - j;
    
        System.out.println("方法内部 result = " + result);
    
        return result;
    }
    
    @Override
    public int mul(int i, int j) {
    
    
    
        int result = i * j;
    
        System.out.println("方法内部 result = " + result);
    
        return result;
    }
    
    @Override
    public int div(int i, int j) {
    
    
    
        int result = i / j;
    
        System.out.println("方法内部 result = " + result);
    
        return result;
    }
}
4.3. Create aspect classes and configure them
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {
    
    
    
    @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
    
    
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    }

    @After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint){
    
    
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->后置通知,方法名:"+methodName);
    }

    @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
    
    
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
    }

    @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
    
    
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    }
    
    @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
    
    
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
    
    
            System.out.println("环绕通知-->目标对象方法执行之前");
            //目标对象(连接点)方法的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
    
    
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
    
    
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }
    
}

Configure in Spring configuration 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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--
        基于注解的AOP的实现:
        1、将目标对象和切面交给IOC容器管理(注解+扫描)
        2、开启AspectJ的自动代理,为目标对象自动生成代理
        3、将切面类通过注解@Aspect标识
    -->
    <context:component-scan base-package="com.atguigu.aop.annotation"></context:component-scan>

    <aop:aspectj-autoproxy />
</beans>

Execute the test:

public class CalculatorTest {
    
    

    private Logger logger = LoggerFactory.getLogger(CalculatorTest.class);

    @Test
    public void testAdd(){
    
    
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        Calculator calculator = ac.getBean( Calculator.class);
        int add = calculator.add(1, 1);
        logger.info("执行成功:"+add);
    }

}

Results of the:
Insert image description here

4.4. Various notifications
  • Pre-notification: Use the @Before annotation to identify it and execute it before the proxied target method
  • Return notification: Use the @AfterReturning annotation to identify it and execute it after the proxied target method successfully ends (End of life)
  • Exception notification: Use the @AfterThrowing annotation to identify it and execute it after the proxy target methodends abnormally (Died unexpectedly)
  • Post notification: Use the @After annotation to identify it and execute it after the proxy target methodfinally ends (Final conclusion)
  • Surround notification: use the @Around annotation mark, and use the try...catch...finally structure to surround the entire proxied target method, including the above four All locations for notifications

The execution order of various notifications:

  • Before Spring version 5.3.x:
    • Pre-notification
    • target operation
    • post notification
    • Return notification or exception notification
  • Spring version 5.3.x and later:
    • Pre-notification
    • target operation
    • Return notification or exception notification
    • post notification
4.5. Pointcut expression syntax

①Effect
Insert image description here
②Language details

  • Use the * sign to replace the "permission modifier" and "return value" parts to indicate that the "permission modifier" and "return value" are not limited

  • In the package name part, a "*" sign can only represent one layer in the package hierarchy, indicating that this layer is arbitrary.

    • For example: *.Hello matches com.Hello but does not match com.atguigu.Hello
  • In the package name part, use "*..." to indicate any package name and any package depth.

  • In the class name part, the entire class name part is replaced with *, indicating that the class name is arbitrary

  • In the class name part, you can use * to replace part of the class name

    • For example: *Service matches all classes or interfaces whose names end with Service
  • In the method name part, you can use the * sign to indicate that the method name is arbitrary.

  • In the method name part, you can use the * sign to replace part of the method name.

    • For example: *Operation matches all methods whose method names end with Operation
  • In the method parameter list part, use (...) to indicate that the parameter list is arbitrary

  • In the method parameter list part, use (int,...) to indicate that the parameter list starts with an int type parameter.

  • In the method parameter list part, the basic data type and the corresponding packaging type are different

    • The use of int in the pointcut expression does not match the Integer used in the actual method.
  • In the method return value section, if you want to explicitly specify a return value type, you must also specify the permission modifier.

    • 例如:execution(public int Service.(…, int)) 正确
      例如:execution(
      int *…Service.(…, int)) 错误

Insert image description here

4.6. Reuse pointcut expressions

①Statement

@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){
    
    }

② Used in the same section

@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    
    
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

③Used in different aspects

@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    
    
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
4.7. Obtain notification-related information

①Get connection point information

To obtain connection point information, you can set the formal parameter of JoinPoint type in the parameter position of the notification method.

@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
    
    
    //获取连接点的签名信息
    String methodName = joinPoint.getSignature().getName();
    //获取目标方法到的实参信息
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

②Get the return value of the target method

The returning attribute in @AfterReturning is used to transfer a formal parameter of the notification method to receive the return value of the target method.

@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
    
    
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}

③Get the exception of the target method

The throwing attribute in @AfterThrowing is used to transfer a formal parameter of the notification method to receive the exception of the target method.

@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
    
    
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}
4.8. Surround notification
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
    
    
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    Object result = null;
    try {
    
    
        System.out.println("环绕通知-->目标对象方法执行之前");
        //目标方法的执行,目标方法的返回值一定要返回给外界调用者
        result = joinPoint.proceed();
        System.out.println("环绕通知-->目标对象方法返回值之后");
    } catch (Throwable throwable) {
    
    
        throwable.printStackTrace();
        System.out.println("环绕通知-->目标对象方法出现异常时");
    } finally {
    
    
        System.out.println("环绕通知-->目标对象方法执行完毕");
    }
    return result;
}
4.9. Priority of aspects

When multiple aspects exist on the same target method, the priority of the aspects controls the inner and outer nesting order of the aspects.

  • High priority aspect: outside
  • Low priority aspects: Inside

Use the @Order annotation to control the priority of aspects:

  • @Order (smaller number): high priority
  • @Order (larger number): low priority

Insert image description here

5. XML-based AOP

5.1. Preparation work

Reference annotation-based AOP environment

5.2. Implementation
<context:component-scan base-package="com.atguigu.aop.xml"></context:component-scan>

<aop:config>
    <!--配置切面类-->
    <aop:aspect ref="loggerAspect">
        <aop:pointcut id="pointCut" 
                   expression="execution(* com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
        <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
        <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
        <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning>
        <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
        <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
    </aop:aspect>
</aop:config>

Guess you like

Origin blog.csdn.net/m0_62946761/article/details/133620738