Configure and use AOP in Spring

1. Related concepts

AOP

AOP (Aspect Oriented Programming) is called aspect-oriented programming, which can be said to be the complement and improvement of OOP (Object Oriented Programming, object-oriented programming). OOP introduces concepts such as encapsulation, inheritance, and polymorphism to establish an object hierarchy, which can well simulate the vertical modules in the process of transaction, but it is not suitable for defining horizontal relationships.

For example, the various operation processes in a process are as follows. Each operation process needs to perform a log recording function, so each operation module needs to write a log recording code. This kind of distribution in each module is independent of the specific module. The code is called cross cutting (cross cutting), in the OOP design, it leads to a lot of code duplication, which is not conducive to the reuse of various modules.

AOP technology is just the opposite. It uses a technology called "crosscutting" to dissect the inside of the encapsulated object, encapsulate the common behavior of those multiple classes into a reusable module, and name it "Aspect" "That is the cut surface. It is easy to reduce the repetitive code of the system, reduce the coupling between modules, and is beneficial to the future operability and maintainability. It is mainly used to solve some system-level problems in program development, such as logging, transactions, and permission waiting. The design of the Struts2 interceptor is based on the idea of ​​AOP and is a classic example.

AOP divides the software system into two parts: core concerns and cross-cutting concerns. The main process of business processing is the core concern , and the part that is not related to it is the cross-cutting concern . One of the characteristics of cross-cutting concerns is that they often occur at multiple places in the core concerns, and are basically similar everywhere, such as permission authentication, logs, and things. The role of AOP is to separate various concerns in the system and separate core concerns from cross-cutting concerns.

There are two ways to implement AOP, one is the pre-compiled method represented by AspectJ, and the other is the dynamic proxy at runtime represented by Spring. Compared with AspectJ, SpringAOP is not a comprehensive AOP solution, it only provides an integration with SpringIOC container for solving common problems in development.

the term

  • Aspect: A modular class that cross-cuts concerns, where you can define entry points and notifications
  • Target Object (Target Object): The core focus of the realization of the main business process is the notified or proxy object. Including connection points
  • JointPoint (joint point): the point that is intercepted during the execution of the program, generally a specific method of the object.
  • Advice (notification): the processing operations performed by AOP at specific entry points
  • Pointcut (pointcut): is a connection point with a notification, the notification is associated with the pointcut expression in AOP
  • AOP proxy (AOP Proxy): AOP framework creates objects through proxy target classes.
  • weave (weaving): the process of applying facets to target objects and creating AOP proxy objects
  • introduction (introduction): Without modifying the code, the introduction can dynamically add some methods or fields for the class at runtime

Notification type

  • Before: Notify before the execution of the connection point method, @Before only needs to specify the pointcut expression
  • AfterReturning: Notify after the target method is completed normally, @AfterReturning In addition to specifying the pointcut expression, you can also specify a return value parameter name returning, representing the return value of the target method
  • AfterThrowing: The connection point method throws an exception and is notified when exiting. In addition to specifying the pointcut expression, @AfterThrowing can also specify a returning value parameter name, which can be used to access the thrown in the target method Exception object
  • After: Executed after the target method is completed, regardless of whether the target method throws an exception. @After can specify an entry point expression
  • Around: surround notification, define the operation to be performed before and after the target method is completed, surround notification is the most important type of notification, like transactions, logs, etc. are all around notifications

2. Use AOP in Spring

Configuration based on XML

The first step is to use maven to create a project and import the dependencies in the pom.xml file as follows. In addition to spring and junit, three dependencies of spring-aop, aspectjrt and aspectjweaver need to be introduced.

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.4.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

The second step is to create a simple Java project. As shown in the figure below, there is an IOperation interface. The operations1 and 2 classes implement the interface's doOperation () method, which represents the core focus of executing business logic. The LogHandler is used for the log output aspect, which represents the cross-cutting concern class. By implementing the MethodBeforeAdvice and AfterReturningAdvice interfaces of spring.aop, the entry point Before and AfterReturning type notifications are defined. Similarly, there is the afterThrowing () method of the ThrowsAdvice interface

public interface IOperation {
    void doOperation();
}

public class Operation1 implements IOperation {
    public void doOperation() {
        System.out.println("执行业务操作1");
    }
}

public class Operation2 implements IOperation {
    public void doOperation() {
        System.out.println("执行业务操作2");
    }
}

//LogHandler
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class LogHandler implements MethodBeforeAdvice, AfterReturningAdvice {
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("操作执行前,打印日志...");
    }

    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("操作执行后,打印日志...");
    }
}

The third step is to configure Spring's AOP. Create the spring-aop.xml file under resource as follows: First, define the Bean--o1, o2 of the proxy class and the logHandler of the aspect class. Then configure the entry point and the cut plane. Finally set up AOP proxy proxy1 and proxy2.

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义目标类 -->
    <bean id="o1" class="com.aop.Operation1"/>
    <bean id="o2" class="com.aop.Operation2"/>
    <!-- 定义切面类 -->
    <bean id="logHandler" class="com.aop.LogHandler"/>

    <!-- 定义切入点,这里定义所有名为doOperaion的方法 -->
    <bean id="logPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*doOperation"/>
    </bean>
    <!-- 配置切面,使切入点与通知相关联 -->
    <bean id="logHandlerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="logHandler"/>
        <property name="pointcut" ref="logPointcut"/>
    </bean>

    <!-- 为o1设置代理 -->
    <bean id="proxy1" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理的目标o1 -->
        <property name="target" ref="o1"/>
        <!-- 使用切面 -->
        <property name="interceptorNames" value="logHandlerAdvisor"/>
        <!-- 代理对应的接口 -->
        <property name="proxyInterfaces" value="com.aop.IOperation"/>
    </bean>
    <!-- 为o2设置代理 -->
    <bean id="proxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理的目标o2 -->
        <property name="target" ref="o2"/>
        <!-- 使用切面 -->
        <property name="interceptorNames" value="logHandlerAdvisor"/>
        <!-- 代理对应的接口 -->
        <property name="proxyInterfaces" value="com.aop.IOperation"/>
    </bean>
</beans>

The fourth step is to use the AOP proxy proxy1, proxy2 to perform the corresponding operations in the test class

    @Test
    void printLog() {
        //读取上下文配置文件
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
        IOperation op1 = (IOperation) appCtx.getBean("proxy1");      //通过代理proxy来使用Operation1对象
        IOperation op2 = (IOperation) appCtx.getBean("proxy2");

        op1.doOperation();
        op2.doOperation();
    }

The execution result is as shown in the left figure. It can be seen that Operation1 and Operation2 are executed, and the method of the logHandler of the aspect class is called before and after the execution to output the log. The structure of the entire AOP is shown below.

 

Configuration via aop tag

Because the configuration via xml is too complicated, most of them use the aop tag to configure after the spring2.0 version. Unlike xml, firstly, the aspect class does not need to implement a specific interface method

//定义用于日志输出的切面类
public class LogHandler {
    public void beforeLog() {
        System.out.println("操作执行前打印日志...");
    }
}

Second, use the <aop> tag in the xml file to configure as follows. First, introduce the aop tag xmlns in the <beans> tag: aop = "http://www.springframework.org/schema/aop", and then define the target class Beans of o1, o2 and aspect class logHandler. Then configure the aspect through <aop: config>, and configure the entry point and notification in it.

<?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">

    <!-- 定义目标类 -->
    <bean id="o1" class="com.aop.Operation1"/>
    <bean id="o2" class="com.aop.Operation2"/>
    <!-- 定义切面类,也就是切入点执行前后需要做的事情 -->
    <bean id="logHandler" class="com.aop.LogHandler"/>


    <!-- 切面配置 -->
    <aop:config>
        <!-- 配置切面为logHandler类 -->
        <aop:aspect id="logAop" ref="logHandler">
            <!-- 配置切入点为com.aop包下所有类的doOperation方法 -->
            <aop:pointcut id="operationPoint" expression="execution(* com.aop..*.*doOperation(..))"/>
            <!-- 配置before前置通知为beforeLog()方法 -->
            <aop:before method="beforeLog" pointcut-ref="operationPoint"/>
        </aop:aspect>
    </aop:config>
</beans>

Execution expressions are used when configuring entry points, and some commonly used expressions are as follows. In addition, there are other matching expression types such as within (), this (), target (), args (), bean () and so on. For example, there is doOperation () method in com.aop.Operation2 class,

execution(public * *(..)) The entry point is all public methods
execution(* set*(..)) The entry point is all set methods
execution(* com.aop.Operation2.*(..)) The entry point is all methods of the Operation2 class
execution(* com.aop..(..)) The entry point is the method of all classes under the aop package
execution(* com...(..)) All methods of all packages and their sub-packages with the entry point of com
execution(* com.aop..do*(..)) The entry point is the method starting with "do" in all classes under com.aop

The configuration notification uses the <aop: before> tag, which represents the before type of notification. Similarly, there are <aop: after-returning>, <aop: after-throwing>, <aop: after>, <aop: around> respectively Corresponds to other notification types.

Finally, AOP is used in the test class. Unlike the xml configuration, after the aop tag is configured, the original objects o1 and o2 can be used directly without using its proxy object.

    @Test
    void printLog() {
        //读取上下文配置文件
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
        IOperation op1 = (IOperation) appCtx.getBean("o1");      //直接使用Operation1对象
        IOperation op2 = (IOperation) appCtx.getBean("o2");

        op1.doOperation();
        op2.doOperation();
    }

Introduction

Compared with some high-level languages ​​with dynamic classes, Java can no longer add new functions to classes once they are compiled. At this time we can use Introduction to add new methods to the compiled classes. As shown below, we want to introduce a new method for the Operation1 class, first define the introduced interface IIntroduction, the default implementation class of the interface IntroducedOperation

public interface IIntroduction {
    public void introduceOperate();
}

public class IntroducedOperation implements IIntroduction {
    public void introduceOperate(){
        System.out.println("执行引入的操作...");
    }
}

Then configure the Operation1 class in <aop: config> to introduce the above interface

    <aop:config>
        <aop:aspect id="logAop" ref="logHandler">
            <!-- 为Operation1类引入IIntroduction接口 -->
            <aop:declare-parents types-matching="com.aop.Operation1"
                                 implement-interface="com.aop.IIntroduction"
                                 default-impl="com.aop.IntroducedOperation"/>
        </aop:aspect>
    </aop:config>

The test is as follows in the test, it can be seen that the object o1 through Operation1 can use the introduced method

    @Test
    void printLog() {
        //读取上下文配置文件
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
        //将Operation1对象o1转化为IIntroduction,并且调用引入的方法
        IIntroduction introduction=(IIntroduction) appCtx.getBean("o1");
        introduction.introduceOperate();
    }

3、AspectJ的AOP

Use configuration

First open the automatic scanning and proxy of aspectJ aspect class in the 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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 对com.aop.aspectj包下的类进行自动扫描 -->
    <context:component-scan base-package="com.aop.aspectj"/>
    <!-- 开启aspectj自动代理 -->
    <aop:aspectj-autoproxy/>
</beans>

The second step is to define the aspect class. AspectJ will automatically scan and register the class with @Component and @Aspect annotations as the aspect class. The pointcut point is defined in a functional way through the @Pointcut annotation in the facet class, and its return value is void. The pre-notification is defined by @Before. The parameters in parentheses are the entry points, which can be the previously defined entry points or expressions.

package com.aop.aspectj;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LogAspect {
    //定义切入点,其接入点为类Operation1下的所有方法
    @Pointcut("execution(* com.aop.aspect.Operation1.*(..))")
    public void logPoint(){}

    //定义前置通知
    @Before("logPoint()")
    public void logBefore(){
        System.out.println("aspect输出前置通知");
    }
}

Finally, you can define and use the doOperation () method of the target class Operation1 to perform the operation, and output the result:

package com.aop.aspectj;

import org.springframework.stereotype.Component;

@Component
public class Operation1 {
    public void doOperation(){
        System.out.println("执行操作");
    }
}


//测试方法
    @Test
    void aspectJ(){
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("aspectj-aop.xml");
        Operation1 op1=(Operation1)appCtx.getBean("operation1");
        op1.doOperation();
    }

You can also use annotations to define the configuration file, scan the component class through @ComponentScan on the configuration class of @Configuration, and enable the automatic proxy of AspectJ through @EnableAspectJAutoProxy. Finally, when using aop, use the AspectConfig class to load the configuration class instead of using the xml configuration file

package com.aop.aspectj;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.aop.aspectj")
@EnableAspectJAutoProxy
public class AspectConfig {
}

//测试类
    @Test
    void aspectJ(){
        //通过AspectConfig来加载配置类
        ApplicationContext appCtx=new AnnotationConfigApplicationContext(AspectConfig.class);
        Operation1 op1=(Operation1)appCtx.getBean("operation1");
        op1.doOperation();

    }

Advice notice

In the above example, @Before is used to define the pre-notification. Similarly, @AfterReturning is used to define the notification when the function returns. Its parameter pointcut specifies the entry point. Here, the expression is used directly instead of the previously defined entry point. The returning parameter receives the result returned by the pointcut function. @AfterThrowing defines the notification when a function throws an exception and can use the throwing parameter to receive the exception object. @After to define the post notification, regardless of whether the function throws an exception will be executed.

@Component
@Aspect
public class LogAspect {
    @AfterReturning(pointcut = "execution(* com.aop.aspectj.Operation1.doReturn(..))",
            returning = "returnValue")
    public void logReturning(Object returnValue){
        System.out.println("返回值:"+returnValue);
    }
}

It is worth noting the use of surround notifications defined by @Around . The surround notification receives the ProceedingJoinPoint object as a parameter, and then uses the object's proceed () method to execute the pointcut method and get the return value Object. Therefore, we can define the pre and post notification operations that need to be performed before and after proceed ()

//切面类的定义
@Component
@Aspect
public class LogAspect {
    @Pointcut("execution(* com.aop.aspectj.Operation1.*(..))")
    public void logPoint(){}

    @Around("logPoint()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知前");
        Object obj=pjp.proceed();       //执行切入点操作
        System.out.println("环绕通知后,返回值:"+obj);
        return obj;
    }
}

//目标类Operation1
@Component
public class Operation1 {
    public String doReturn(){
        System.out.println("执行操作...");
        return "这是返回值";
    }
}

//测试类
@Test
    void aspectJ(){
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("aspectj-aop.xml");
        Operation1 op1=(Operation1)appCtx.getBean("operation1");
        op1.doReturn();
    }

The results are as follows:

Pass parameters to Advice

Use args to capture the parameters of the pointcut function in the notification and pass it to the notification

//切面类
@Component
@Aspect
public class LogAspect {
    @Pointcut("execution(* com.aop.aspectj.Operation1.*(..))")
    public void logPoint(){}

    @Before("logPoint() && args(strArg)")                    //捕获连接点方法的参数
    public void logBefore(String strArg){                    //将参数传入到方法
        System.out.println("Advice接收参数:"+strArg);
    }
}

//目标类
@Component
public class Operation1 {
    //连接点函数
    public void doOperation(String str){
        System.out.println("执行操作");
    }
}

//测试方法
   @Test
    void aspectJ(){
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("aspectj-aop.xml");
        Operation1 op1=(Operation1)appCtx.getBean("operation1");
        op1.doOperation("一个字符串参数");
    }

The result is

Similarly, you can use @annotation to the capture of the annotation parameters: @Before ( "logPoint () && @annotation (strAnno)")

//自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodAnno {
    String value();
}

//目标类
@Component
public class Operation1 {
    @MethodAnno("这是一个注解字符串")                              //连接点函数添加注解
    public void doOperation(){
        System.out.println("执行操作");
    }
}

//切面类
@Component
@Aspect
public class LogAspect {
    @Pointcut("execution(* com.aop.aspectj.Operation1.*(..))")
    public void logPoint(){}

    @Before("logPoint() && @annotation(anno)")                    //通知接收注解
    public void logBefore(MethodAnno anno){
        System.out.println("Advice接收注解:"+anno.value());
    }
}

Output result

Published 124 original articles · Like 65 · Visit 130,000+

Guess you like

Origin blog.csdn.net/theVicTory/article/details/105371036