Detailed Explanation of Spring AOP (Super Comprehensive)

1. AOP

1 Overview

​ AOP is the abbreviation of Aspect Oriented Programming, which means: aspect-oriented programming, a technology that realizes the unified maintenance of program functions through pre-compilation and dynamic proxy during runtime. AOP is a continuation of OOP, a hot spot in software development, an important content in the Spring framework, and a derivative paradigm of functional programming. AOP can be used to isolate various parts of business logic, thereby reducing the coupling degree between various parts of business logic, improving the reusability of programs, and improving the efficiency of development at the same time.

​ Spring AOP is a framework based on the AOP programming model, which can effectively reduce the duplication of code between systems and achieve the purpose of loose coupling. Spring AOP is implemented in pure Java, does not require a special compilation process and class loader, and implants enhanced code into the target class through a proxy during runtime. There are two implementations: interface-based JDK dynamic proxy and inheritance-based CGLIB dynamic proxy.

​ AspectJ is an AOP framework based on the Java language. Starting from Spring 2.0, Spring AOP has introduced support for AspectJ. AspectJ extends the Java language and provides a special compiler that provides horizontal code implantation at compile time.

2. AOP Terminology

​ In order to better understand AOP, we need to understand some of its related terms. These terms are not specific to Spring, and some of them apply equally to other AOP frameworks, such as AspectJ. Their meanings are as follows:

**Advice (Advice):** refers to what to do after intercepting the Joinpoint, that is, the enhanced content of the entry point. It includes different types of notifications such as "around", "before" and "after" (the types of notifications will be explained later); pre-notification, post-notification, surround notification, final notification, and exception notification. Surround alone.

**Joinpoint (Joinpoint):** refers to those points that can be intercepted. In Spring, it refers to the method that can be intercepted by the dynamic proxy (the front and back of the method (throwing an exception) are all connections) point);

Pointcut : Refers to which Joinpoints to intercept, that is, the intercepted connection point, the method in the intercepted class (filter the connection point, select the few methods you want);

**Aspect:**Aspect is composed of Pointcut and Advice, which includes both the definition of enhanced logic and the definition of pointcut, that is, the combination of the two;

Introduction : Allows us to add new method attributes to existing classes (that is, use the aspect (that is, the new method attribute: notification definition) to the target class);

**Target (Target): **The target object of the proxy, that is, the enhanced object (the target class mentioned in the introduction, that is, the object to be notified);

**Proxy (Proxy):** refers to the generated proxy object;

**Weaving:** refers to the process of applying the enhanced code to the target to generate a proxy object;

Spring AOP is the framework responsible for implementing aspects, and it weaves the enhanced logic defined by aspects into the entry points specified by aspects.

AOP is one of the cores of Spring, and AOP is often used in Spring to simplify programming. The main advantages of using AOP in the Spring framework are as follows:

  • Provides declarative enterprise services, especially as a replacement for EJB declarative services. Most importantly, this service is declarative transaction management;
  • Allows users to implement custom aspects. In some scenarios that are not suitable for OOP programming, AOP is used to supplement;
  • Each part of the business logic can be isolated, so that the coupling degree between the parts of the business logic is reduced, the reusability of the program is improved, and the development efficiency is also improved.

3. Getting Started Case

  1. Create a maven project, modify pom.xml to add spring-context, spring-test, aspectjweaver, lombok dependencies
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
  <!-- 引入spring框架相关jar,包含了aop相关的支持-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
  </dependency>
  <!-- spring单元测试支持的jar-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.10.RELEASE</version>
  </dependency>
  <!-- spring aop中解析切入点表达式,以及用到其中的注解-->
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
  </dependency>
  <!-- @DATA等注解支持-->   
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
  </dependency>
</dependencies>
  1. UserService
public interface UserService {
    
    
    void add();
    void del();
    void update();
    void select();
}
  1. UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
    
    

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

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

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

    @Override
    public void select() {
    
    
        System.out.println("select");
    }
}
  1. Write a notification class Logger, where the logging function is simulated
public class Logger {
    
    
    public void printLog(){
    
    
        System.out.println("日志记录了.....");
    }
}
  1. 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: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-->
    <bean id="userService" class="com.hqyj.cl.service.impl.UserServiceImpl"/>
    <!-- 配置通知类的bean-->
    <bean id="logger" class="com.hqyj.cl.utils.Logger"/>
    <!-- 配置AOP-->
    <aop:config>
        <!--切入点
            expression: 表达式
                execution(* com.hqyj.cl.pojo.UserServiceImpl.*(..))
                第一个 *: 返回值类型
                第二个参数: 切入的位置
                第三个参数 *: 切入的方法
                (..): 表示方法参数随便是多少个
        -->
        <aop:aspect id="aspect" ref="logger">
            <!-- 配置前置通知-->
            <aop:before method="printLog" pointcut="execution(* com.hqyj.cl.service.impl.UserServiceImpl.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. UserServiceImplTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    
    "classpath:applicationContext.xml"})
public class UserServiceImplTest {
    
    
    @Autowired
    private UserService userService;

    @Test
    public void add(){
    
    
        userService.add();
    }
}

​ The test result will output a sentence before calling the add method日志记录了.....

4. Notification type

​ Advice is literally translated as notice, and some materials are translated as "enhanced processing". There are 5 types in total, as shown in the following table:

notify illustrate
before (pre-notification) Advice method executes before target method call
after-returning (post-returning notification) The notification method will be called after the target method returns
after-throwing (exception notification) The notification method will be called after the target method throws an exception
after (final notice) The advice method is called after the target method returns or an exception
around (around notification) The advice method will wrap the target method

4.1 Sample code

  1. Logger notification class
public class Logger {
    
    
    public void printLogBefore(){
    
    
        System.out.println("日志记录了...前置通知");
    }

    public void printLogRound(){
    
    
        System.out.println("日志记录了...环绕通知");
    }

    public void printLogAfter(){
    
    
        System.out.println("日志记录了...后置通知");
    }

    public void printLogException(){
    
    
        System.out.println("日志记录了...异常通知");
    }

    public void printLogFinally(){
    
    
        System.out.println("日志记录了...最终通知");
    }
}
  1. UserService
public interface UserService {
    
    
    void add();
    void del();
    void update();
    void select();
}
  1. UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
    
    

    @Override
    public void add() {
    
    
//        int i = 1/0;
        System.out.println("add");
    }

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

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

    @Override
    public void select() {
    
    
        System.out.println("select");
    }
}
  1. 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: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-->
    <bean id="userService" class="com.hqyj.cl.service.impl.UserServiceImpl"/>
    <!-- 配置通知类的bean-->
    <bean id="logger" class="com.hqyj.cl.utils.Logger"/>
    <!-- 配置AOP-->
    <aop:config>
        <!--切入点
            expression: 表达式
                execution(* com.hqyj.cl.pojo.UserServiceImpl.*(..))
                第一个 *: 返回值类型
                第二个参数: 切入的位置
                第三个参数 *: 切入的方法
                (..): 表示方法参数随便是多少个
        -->
        <aop:aspect id="aspect" ref="logger">
            <!-- aop:pointcut标签单独配置切入点表达式,方便各通知标签引用 -->
            <aop:pointcut id="pointcut" expression="execution(* com.hqyj.cl.service.*.*(..))"/>
            <!-- 配置前置通知-->
            <aop:before method="printLogBefore" pointcut-ref="pointcut"/>
            <!-- 配置后置通知-->
            <aop:after-returning method="printLogAfter" pointcut-ref="pointcut"/>
            <!-- 异常通知配置/后置通知和异常通知这两类不会同时通知-->
            <aop:after-throwing method="printLogException" pointcut-ref="pointcut"/>
            <!-- 最终通知配置-->
            <aop:after method="printLogFinally" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. test class
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    
    "classpath:applicationContext.xml"})
public class UserServiceImplTest {
    
    

    @Test
    public void add(){
    
    
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = ac.getBean("userService", UserService.class);
        userService.add();
    }
}

​ When no exception occurs, the output is

日志记录了...前置通知
add
日志记录了...后置通知
日志记录了...最终通知

​ When an exception occurs, the output is

日志记录了...前置通知
日志记录了...异常通知
日志记录了...最终通知

4.2 Surround notifications

  • Notification that additional code can be executed both before and after the execution of the target method;
  • In the surround notification, the target method must be called explicitly before the target method will be executed. This explicit call is realized through ProceedingJoinPoint. You can receive a formal parameter of this type in the surround notification, and the spring container will automatically pass the object Enter, note that this parameter must be in the first parameter position of the surround notification;
  • Only surround notifications can receive ProceedingJoinPoint, while other notifications can only receive JoinPoint;
  • The surrounding notification needs to return a return value, otherwise the real caller will not get the return value, only a null;
  • The surrounding notification has the ability to control whether the target method is executed, whether to return a value, and to change the return value;
  • Although surround notification has such a capability, it must be used with caution, not because it is technically impossible, but to be careful not to destroy the goal of "high cohesion and low coupling" of software layering.
  1. Logger notification class
public class Logger {
    
    
    public void printLogBefore(){
    
    
        System.out.println("日志记录了...前置通知");
    }

    public Object printLogAround(ProceedingJoinPoint pjp){
    
    
        System.out.println("日志记录了...环绕通知");
        Object result = null;
        try{
    
    
            // 模拟执行前置通知
            printLogBefore();
            // 得到方法执行所需的参数
            Object[] args = pjp.getArgs();
            // 明确调用业务层方法(切入点方法)
            result = pjp.proceed(args);
            // 模拟执行后置通知
            printLogAfter();
            return result;
        }catch (Throwable t){
    
    
            // 模拟执行异常通知
            printLogException();
            // //如果真实对象调用方法时发生异常,将异常抛给虚拟机
            throw new RuntimeException(t);
        }finally {
    
    
            // 模拟执行最终通知
            printLogFinally();
        }
    }

    public void printLogAfter(){
    
    
        System.out.println("日志记录了...后置通知");
    }

    public void printLogException(){
    
    
        System.out.println("日志记录了...异常通知");
    }

    public void printLogFinally(){
    
    
        System.out.println("日志记录了...最终通知");
    }
}
  1. 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: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-->
    <bean id="userService" class="com.hqyj.cl.service.impl.UserServiceImpl"/>
    <!-- 配置通知类的bean-->
    <bean id="logger" class="com.hqyj.cl.utils.Logger"/>
    <!-- 配置AOP-->
    <aop:config>
        <!--切入点
            expression: 表达式
                execution(* com.hqyj.cl.pojo.UserServiceImpl.*(..))
                第一个 *: 返回值类型
                第二个参数: 切入的位置
                第三个参数 *: 切入的方法
                (..): 表示方法参数随便是多少个
        -->
        <aop:aspect id="aspect" ref="logger">
            <!-- aop:pointcut标签单独配置切入点表达式,方便各通知标签引用 -->
            <aop:pointcut id="pointcut" expression="execution(* com.hqyj.cl.service.*.*(..))"/>
            <!-- 环绕通知-->
            <aop:around method="printLogAround" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. test class
@Test
public void add(){
    
    
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = ac.getBean("userService", UserService.class);
    userService.add();
}

4.3 JoinPoint

Any advice method can define the first parameter as org.aspectj.lang.JoinPoint type (surrounding advice needs to define the first parameter as ProceedingJoinPoint type, which is a subclass of JoinPoint). The JoinPoint interface provides a series of useful methods, such as getArgs() (returns method parameters), getSignature() (returns information about the method being notified)

2. Implement AOP based on annotations (understand)

1. Notes

​ Implementing AOP through full annotations mainly uses the following annotations:

// 标明这是一个配置文件,代替了之前的Spring-config.xml配置文件
@Configuration  
// 相当于Spring-config.xml配置文件<context:component-scan base-package="com.hqyj.cl"/>,配置自动扫描的包
@ComponentScan("com.hqyj.aop") 
// 相当于Spring-config.xml配置文件<aop:aspectj-autoproxy/>,自动为切面方法中匹配的方法所在的类生成代理对象
@EnableAspectJAutoProxy
// 标注这个类是一个切面
@Aspect 
// Pointcut是植入Advice的触发条件
@Pointcut
// 前置通知
@Before()
// 后置通知
@AfterReturning
// 最终通知
@After
// 异常通知
@AfterThrowing
// 环绕通知
@Around

2. Sample code

  1. SpringConfiguration configuration class
@Configuration
@ComponentScan("com.hqyj.cl.aop")
@EnableAspectJAutoProxy
public class SpringConfiguration {
    
    

    @Bean
    public UserService userService(){
    
    
        return new UserServiceImpl();
    }

}
  1. AnnotationLogger notification class
@Aspect // 标注这个类是一个切面
@Component // 标注该类会创建一个bean
public class AnnotationLogger {
    
    

    @Pointcut("execution(* com.hqyj.cl.service.*.*(..))")
    public void pointCut(){
    
    

    }

    // 前置通知:目标方法执行之前执行以下方法体的内容
    @Before("pointCut()")
    public void beforeMethod(){
    
    
        System.out.println("前置通知");
    }

    // 后置通知:目标方法正常执行完毕时执行以下代码
    @AfterReturning(value = "pointCut()")
    public void afterReturningMethod(){
    
    
        System.out.println("后置通知");
    }

    // 最终通知:目标方法执行之后执行以下方法体的内容,不管是否发生异常。
    @After("pointCut()")
    public void afterMethod(){
    
    
        System.out.println("最终通知");
    }

    // 异常通知:目标方法发生异常的时候执行以下代码
    @AfterThrowing("pointCut()")
    public void afterThrowing(){
    
    
        System.out.println("异常通知");
    }

}
  1. Same as above for UserService and UserServiceImpl

  2. test class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    
    "classpath:applicationContext.xml"})
public class AnnotationLoggerTest {
    
    
    @Test
    public void annotationLoggerTest(){
    
    
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        UserService userService = ac.getBean("userService", UserService.class);
        userService.add();
    }
}

3. Surround notification

​ Test yourself

3. Dynamic agent process

Configure the aspect first, and then configure the notification in the aspect. To point the notification to a certain place, and to configure a notification for a certain method or some methods, you must first specify where the method is. At this time, you need to configure an entry point. The whole process is a dynamic proxy process.

Agency mode: Static agency mode, for example, Zhang San wants to rent a house, but cannot find a house, so he has to find an intermediary, a typical agency mode. The intermediary acts as the agent, and Zhang San is the agent. Static proxy has a limitation. For example, if Zhang San wants to rent a house, he can only find a rental agency, but if he wants to get married, he has to find a wedding agency. A class needs a proxy object. Dynamic proxy mode, (need to implement the InvocationHandler interface), the proxy object is generated during the running process, no matter what object you are, you can encapsulate it into a class, and you can get the object through the reflection of ioc, so whether you rent a house or For wedding celebrations, proxy objects can be dynamically generated according to requirements.

Guess you like

Origin blog.csdn.net/ailaohuyou211/article/details/130394284