1
Abstract: AOP is also called "aspect-oriented programming" in spring. It is a supplement to our traditional object-oriented programming. The main operating object is the "aspect". It can be simply understood that it runs through the method and executes the method. The operation to be performed before, during execution, after execution, after return value, and after exception.

This article is shared from the Huawei Cloud Community " " What kind of experience is it like to understand "AOP Aspect-Oriented Programming"? ", author: Grey Little Ape.

1. What is Spring's AOP?

AOP is also called "aspect-oriented programming" in spring. It can be said to be a supplement to our traditional object-oriented programming. Literally, as the name suggests, its main operating object is the "aspect", so we can simply that runs through the method before, during, after, after the method is executed, after the return value, and after the exception. It is equivalent to cutting our original program executed by one line in the middle and adding some other operations.

When applying AOP programming, you still need to define public functions, but you can clearly define where and how this function is applied, and you don't have to modify the affected classes. In this way, the cross-cutting concerns are modularized into special classes-such classes are usually called "aspects".

For example, the following figure is a model diagram of an AOP aspect, some operations performed before and after a certain method is executed, and these operations will not affect the operation of the program itself.
image.png

There is a more professional terminology in AOP aspect programming. I will give it to you all:
image.png

Now that you have a general understanding of the basic concepts of AOP aspect programming, the next step is the actual operation.

2. AOP framework environment construction

1. Import the jar package

At present, the most popular and commonly used AOP framework is AspectJ. We also use AspectJ when doing SSM development. To use this framework technology, we need to import the jar package it supports.

  • aopalliance.jar
  • aspectj.weaver.jar
  • spring-aspects.jar

Regarding all the jar packages and related configuration files used in SSM development, I have prepared them for you!

Click the link to download and it will be used. [The most comprehensive network] SSM development must rely on-Jar package, reference documents, common configuration

2. Introduce the AOP namespace

When using AOP aspect programming, it is necessary to introduce the AOP namespace in the container.
image.png

3. Write configuration

In fact, when doing AOP aspect programming, one of the most commonly used and necessary tags is, <aop:aspectj-autoproxy></aop:aspectj-autoproxy>,

We need to add this element to the container. When the Spring IOC container detects the <aop:aspectj-autoproxy> element in the bean configuration file, it will automatically create a proxy for the bean matching the AspectJ aspect.
At the same time, there are two ways to use AOP aspects in current spring, namely AspectJ annotations or AOP based on XML configuration.

Let me introduce to you the use of these two methods in turn.

Three, AOP development based on AspectJ annotations

In the last article, I also talked to you about the power of annotation development in spring, so we can also use annotations to write about AOP development. Let me introduce to you how to write AOP using annotations.

1. Five types of notice annotations

First, you must declare the AspectJ aspect in Spring. You only need to declare the aspect as a bean instance in the IOC container.

After the AspectJ aspects are initialized in the Spring IOC container, the Spring IOC container will create proxies for beans that match the AspectJ aspects.

In AspectJ annotations, an aspect is just a Java class annotated with @Aspect, which often contains many notifications. Notification is a simple Java method marked with some kind of annotation.

AspectJ supports 5 types of notification annotations:

  1. @Before: pre-notification, executed before the method is executed
  2. @After: Post notification, executed after the method is executed
  3. @AfterRunning: return notification, execute after the method returns the result
  4. @AfterThrowing: exception notification, executed after the method throws an exception
  5. @Around: Surround the notification, and execute around the method

2. Entry point expression specification

These five notification annotations can also be followed by specific parameters to specify which aspect method is triggered when which method is executed. So what is the specific operation?

Here we need to introduce a noun to you: " entry point expression ". By adding the expression parameter in the comment, we can locate one or more specific connection points by expression,

The grammatical format specification of the pointcut expression is:

execution([Permission Modifier] [Return Value Type] [Simple Class Name/Full Class Name] [Method Name] ([Parameter List]))

Among them, there are two commonly used special symbols in expressions:

The asterisk "*" represents all meanings, and the asterisk can also represent any numeric type

"." sign: "..." means any type or file under any path,

Here are a few examples:
expression:

execution( com.atguigu.spring.ArithmeticCalculator.(…))

meaning:

All methods declared in the ArithmeticCalculator interface. The first "" represents any modifier and any return value. The second "" represents any method. "..." matches any number and type of parameters. If the target class, interface and the aspect class are in the same package, the package name can be omitted.

expression:

execution(public ArithmeticCalculator.(…))

meaning:

All public methods of ArithmeticCalculator interface

expression:

execution(public double ArithmeticCalculator.*(…))

meaning:

The method of returning double type value in ArithmeticCalculator interface

expression:

execution(public double ArithmeticCalculator.*(double, …))

meaning:

The first parameter is a method of type double. "..." matches any number of parameters of any type.

expression:

execution(public double ArithmeticCalculator.*(double, double))

meaning:

The parameter type is double, double type method

Here is another expression with the most vague positioning:

execution(" (…)")

represents any method of any class under any package, but this expression must not be written, haha, otherwise every method you execute will be executed by a notification method!

At the same time, in AspectJ, pointcut expressions can be combined by operators such as "&&", "||", and "!".

like:

execution ( .add(int,…)) || execution( .sub(int,…))

Indicates that the first parameter in any class is the add method or sub method of type int

3. Annotation practice

Now that we know the use of annotations and pointcut expressions, the next step is to practice.

For the pointcut expression, we can directly use "" in the annotation to write it, and we can also assign the pointcut to the pointcut attribute in the @AfterReturning annotation and @AfterThrowing annotation, but there is no pointcut parameter in other annotations.
image.png

The pointcut expression is applied to the actual aspect class as follows:

@Aspect    //切面注解
@Component    //其他业务层
public class LogUtli {
//    方法执行开始,表示目标方法是com.spring.inpl包下的任意类的任意以两个int为参数,返回int类型参数的方法
    @Before("execution(public int com.spring.inpl.*.*(int, int))")
    public static void LogStart(JoinPoint joinPoint) {
        System.out.println("通知记录开始...");
    }
//    方法正常执行完之后
    /**
     * 在程序正常执行完之后如果有返回值,我们可以对这个返回值进行接收
     * returning用来接收方法的返回值
     * */
    @AfterReturning(pointcut="public int com.spring.inpl.*.*(int, int)",returning="result")
    public static void LogReturn(JoinPoint joinPoint,Object result) {
        System.out.println("【" + joinPoint.getSignature().getName() + "】程序方法执行完毕了...结果是:" + result);
    }
}

The above is just one of the simplest notification methods, but in actual use, we may switch multiple notification methods to the same target method. For example, the same target method has both pre-notification, exception notification and post-notification.置notification.

But in this way, we only cut into some notification methods when the target method is executed, so can we get some information about the executed target method in the notification method? Of course it is possible.

4. JoinPoint obtains method information

Here we can use the JoinPoint interface to get the information of the target method, such as the return value of the method, method name, parameter type, etc.
image.png

For example, before the method execution starts, we obtain the method name and input parameters of the target method and output them.

//    方法执行开始
    @Before("execution(public int com.spring.inpl.*.*(int, int))")
    public static void LogStart(JoinPoint joinPoint) {
            Object[] args = joinPoint.getArgs();    //获取到参数信息
            Signature signature = joinPoint.getSignature(); //获取到方法签名
            String name = signature.getName();    //获取到方法名
            System.out.println("【" + name + "】记录开始...执行参数:" + Arrays.asList(args));
    }

5. The return value and exception information of the receiving method

For some target methods, they may return values after execution, or throw exceptions in the middle of the method. In these cases, how should we obtain this information?

First, let's get the return value when the method is executed,

Here we can use the @AfterReturning annotation, which indicates that the notification method is executed after the target method is executed normally.

In the return notification, as long as the returning attribute is added to the @AfterReturning annotation, you can access the return value of the connection point.

The value of this attribute is the name of the parameter used to pass in the return value, but note that a parameter with the same name must be added to the signature of the notification method.

At runtime, Spring AOP will pass the return value through this parameter. Since we may not know the type of the return value, we generally set the type of the return value to Object.

At the same time, the original pointcut expression needs to appear in the pointcut attribute, as shown below:

//    方法正常执行完之后
    /**
     * 在程序正常执行完之后如果有返回值,我们可以对这个返回值进行接收
     * returning用来接收方法的返回值
     * */
    @AfterReturning(pointcut="public int com.spring.inpl.*.*(int, int)",returning="result")
    public static void LogReturn(JoinPoint joinPoint,Object result) {
            System.out.println("【" + joinPoint.getSignature().getName() + "】程序方法执行完毕了...结果是:" + result);
    }

For receiving abnormal information, the method is actually the same.

We need to add the throwing attribute to the @AfterThrowing annotation, and we can also access the exception thrown by the connection point. Throwable is the top-level parent class of all error and exception classes, so any errors and exceptions can be caught in the exception notification method.

If you are only interested in a particular exception type, you can declare the parameter as the parameter type of other exceptions. Then the notification is executed only when an exception of this type and its subclasses is thrown.

Examples are as follows:

//    异常抛出时
    /**
     * 在执行方法想要抛出异常的时候,可以使用throwing在注解中进行接收,
     * 其中value指明执行的全方法名
     * throwing指明返回的错误信息
     * */
    @AfterThrowing(pointcut="public int com.spring.inpl.*.*(int, int)",throwing="e")
    public static void LogThowing(JoinPoint joinPoint,Object e) {
        System.out.println("【" + joinPoint.getSignature().getName() +"】发现异常信息...,异常信息是:" + e);
    }

6. Surround notifications

When we introduced the notification annotations above, everyone should have seen that there is actually another very important notification- surround notification ,

Surround notifications are the most powerful of all notification types, and can fully control the connection point, and even control whether to execute the connection point.

For surround notification, the parameter type of the connection point must be ProceedingJoinPoint. It is a sub-interface of JoinPoint, allowing control when to execute and whether to execute a connection point.

In the surround notification, you need to explicitly call the proceed() method of ProceedingJoinPoint to execute the delegated method. If you forget to do so, the notification will be executed, but the target method will not be executed. This means that we need to pass in the parameter ProceedingJoinPoint in the method to receive various information about the method.

Note:
The method surrounding the notification needs to return the result of the execution of the target method, that is, the return value of calling joinPoint.proceed();, otherwise a null pointer exception will occur.

For specific use, see the following example:

/**
     * 环绕通知方法
     * 使用注解@Around()
     * 需要在方法中传入参数proceedingJoinPoint 来接收方法的各种信息
     * 使用环绕通知时需要使用proceed方法来执行方法
     * 同时需要将值进行返回,环绕方法会将需要执行的方法进行放行
     * *********************************************
     * @throws Throwable 
     * */
    @Around("public int com.spring.inpl.*.*(int, int)")
    public Object MyAround(ProceedingJoinPoint pjp) throws Throwable {
 
//        获取到目标方法内部的参数
        Object[] args = pjp.getArgs();
 
        System.out.println("【方法执行前】");
//        获取到目标方法的签名
        Signature signature = pjp.getSignature();
        String name = signature.getName();
        Object proceed = null;
        try {
//            进行方法的执行
            proceed = pjp.proceed();
            System.out.println("方法返回时");
        } catch (Exception e) {
            System.out.println("方法异常时" + e);
        }finally{
            System.out.println("后置方法");
        }
 
        //将方法执行的返回值返回
        return proceed;
    }

7. The execution order of notification annotations

So now that the use of these five notification annotations has been introduced, let's summarize an execution sequence when these notification annotations are all in the same target method.

Under normal circumstances:

@Before(Pre-notification)—>@After(Post-notification)---->@AfterReturning(Return notification)

Execute under abnormal conditions:

@Before(Pre-notification)—>@After(Post-notification)---->@AfterThrowing(Exception notification)

When the normal notification and the surrounding notification are executed at the same time:

The order of execution is:

Surround front----normal front----surround return/abnormal----surround back----normal rear----normal return/abnormal

8. Reuse the definition of entry point

For the above notification annotations, we have defined the pointcut expression on each notification annotation.

But imagine a question. If we don't want to set notification methods for this method, or we want to switch these notification methods into another target method, don't we want to change the entry point expression in the annotation one by one? This is too much trouble, right?

So Spring thought of a way, reuse the pointcut expression.

That is to say, these repeated pointcut expressions are expressed in one method, then our notification annotation only needs to call this method that uses the pointcut expression to achieve the same effect as before. In this case, we Even if you want to change the access method of the pointcut expression, you don't need to notify the annotations one by one.

The way to get a reusable pointcut expression is:

  1. Just define a void non-implemented method
  2. Annotate the method @Pointcut()
  3. Add the extracted reusable pointcut expression in the annotation
  4. Use the value attribute to add the method to the annotation of the corresponding aspect function
    image.png

The complete example is as follows:

@Aspect    //切面注解
@Component    //其他业务层
public class LogUtli {
    /**
     * 定义切入点表达式的可重用方法
     * */
    @Pointcut("execution(public int com.spring.inpl.MyMathCalculator.*(int, int))")
    public void MyCanChongYong() {}
 
//    方法执行开始
    @Before("MyCanChongYong()")
    public static void LogStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();    //获取到参数信息
        Signature signature = joinPoint.getSignature(); //获取到方法签名
        String name = signature.getName();    //获取到方法名
        System.out.println("【" + name + "】记录开始...执行参数:" + Arrays.asList(args));
    }
//    方法正常执行完之后
    /**
     * 在程序正常执行完之后如果有返回值,我们可以对这个返回值进行接收
     * returning用来接收方法的返回值
     * */
    @AfterReturning(value="MyCanChongYong()",returning="result")
    public static void LogReturn(JoinPoint joinPoint,Object result) {
        System.out.println("【" + joinPoint.getSignature().getName() + "】程序方法执行完毕了...结果是:" + result);
    }
 
//    异常抛出时
    /**
     * 在执行方法想要抛出异常的时候,可以使用throwing在注解中进行接收,
     * 其中value指明执行的全方法名
     * throwing指明返回的错误信息
     * */
    @AfterThrowing(value="MyCanChongYong()",throwing="e")
    public static void LogThowing(JoinPoint joinPoint,Object e) {
        System.out.println("【" + joinPoint.getSignature().getName() +"】发现异常信息...,异常信息是:" + e);
    }
 
//    结束得出结果
    @After(value = "execution(public int com.spring.inpl.MyMathCalculator.add(int, int))")
    public static void LogEnd(JoinPoint joinPoint) {
        System.out.println("【" + joinPoint.getSignature().getName() +"】执行结束");
    }
 
    /**
     * 环绕通知方法
     * @throws Throwable 
     * */
    @Around("MyCanChongYong()")
    public Object MyAround(ProceedingJoinPoint pjp) throws Throwable {
 
//        获取到目标方法内部的参数
        Object[] args = pjp.getArgs();
 
        System.out.println("【方法执行前】");
//        获取到目标方法的签名
        Signature signature = pjp.getSignature();
        String name = signature.getName();
        Object proceed = null;
        try {
//            进行方法的执行
            proceed = pjp.proceed();
            System.out.println("方法返回时");
        } catch (Exception e) {
            System.out.println("方法异常时" + e);
        }finally{
            System.out.println("后置方法");
        }
 
        //将方法执行的返回值返回
        return proceed;
    }
}

The above is the whole process of implementing AOP aspects using AspectJ annotations.

Here is another particularly interesting rule to remind everyone that means that when you have multiple aspect classes, the execution order of the aspect classes is executed according to the first character of the class name (not case sensitive).

Next, I will explain to you another way to implement AOP aspect programming-AOP implementation based on XML configuration.

Fourth, the realization of AOP based on XML configuration

The AOP aspect based on XML configuration, as the name implies, abandons the use of annotations and instead configures the aspect class in the IOC container. This declaration is based on the XML elements in the aop namespace.

In the bean configuration file, all Spring AOP configuration must be defined inside the <aop:config> element. For each aspect, an <aop:aspect> element must be created to reference the back-end bean instance for the specific aspect implementation.

The aspect bean must have an identifier for the <aop:aspect> element to reference.

So we should first add the required aspect class to the IOC container in the bean configuration file, and then configure it in the element tag of aop. When we use annotations for development, the five notification annotations and pointcut expressions can also be configured in the xml file.

1. Declare the entry point

Entry point use

<aop:pointcut> element declaration.
The entry point must be defined under the <aop:aspect> element, or directly under the <aop:config> element.

Defined under the <aop:aspect> element: only valid for the current aspect

Defined under the <aop:config> element: valid for all aspects

XML-based AOP configuration does not allow other pointcuts to be referenced by names in pointcut expressions.

2. Notice of declaration

In the aop namespace, each notification type corresponds to a specific XML element.

The notification element needs to use <pointcut-ref> to refer to the pointcut, or use <pointcut> to directly embed the pointcut expression.
The method attribute specifies the name of the notification method in the aspect class

For specific use, please see the following examples:

<?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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        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-4.0.xsd">

    <!-- 通过配置文件实现切面 
        1、将目标类和切面类加入到容器中 @component
        2、声明哪个类是切面类,@Aspect
        3、在配置文件中配置五个通知方法,告诉切面类中的方法都何时运行
        4、开启基于注解的AOP功能
    -->
 
    <!-- 将所需类加入到容器中 -->
    <bean id="myCalculator" class="com.spring.inpl.MyMathCalculator"></bean>
    <bean id="logUtil" class="com.spring.utils.LogUtli"></bean>
    <bean id="SecondUtli" class="com.spring.utils.SecondUtli"></bean>
 
    <!-- 进行基于AOP的配置 -->
    <!-- 当有两个切面类和一个环绕方法时,方法的执行是按照配置文件中配置的先后顺序执行的
        配置在前的就会先执行,配置在后的就会后执行,但同时环绕方法进入之后就会先执行环绕方法
     -->
    <aop:config>
        <!-- 配置一个通用类 -->
        <aop:pointcut expression="execution(public int com.spring.inpl.MyMathCalculator.*(int, int)))" id="myPoint"/>
        <!-- 配置某一个指定的切面类 -->
        <aop:aspect id="logUtil_Aspect" ref="logUtil">
            <!-- 为具体的方法进行指定
            method指定具体的方法名
            pointcut指定具体要对应的方法
             -->
            <aop:before method="LogStart" pointcut="execution(public int com.spring.inpl.MyMathCalculator.add(int, int))"/>
            <aop:after-throwing method="LogThowing" pointcut="execution(public int com.spring.inpl.MyMathCalculator.*(int, int)))" throwing="e"/>
            <aop:after-returning method="LogReturn" pointcut-ref="myPoint" returning="result"/>
            <aop:after method="LogEnd" pointcut-ref="myPoint"/>
            <!-- 定义一个环绕方法 -->
            <aop:around method="MyAround" pointcut-ref="myPoint"/>
        </aop:aspect>
 
        <!-- 定义第二个切面类 -->
        <aop:aspect ref="SecondUtli">
            <aop:before method="LogStart" pointcut="execution(public int com.spring.inpl.MyMathCalculator.*(..))"/>
            <aop:after-throwing method="LogThowing" pointcut-ref="myPoint" throwing="e"/>
            <aop:after method="LogEnd" pointcut-ref="myPoint"/>
        </aop:aspect>
 
    </aop:config>
</beans>

Summarize the process of implementing AOP aspect programming through XML configuration:

Realize aspects through configuration files

  1. Adding the target class and aspect class to the container is equivalent to the annotation @component
  2. Declare which class is an aspect class, equivalent to the annotation @Aspect
  3. Configure five notification methods in the configuration file to tell when the methods in the aspect class are run
  4. Enable annotation-based AOP function

Here is one more thing to note:

When there are two aspect classes and a surround method, the execution of the method is executed in the order configured in the configuration file. The first configuration will be executed first, and the second configuration will be executed later, but at the same time, the method will be executed around the method. After entering, the surround method will be executed first.

Final summary

So far, the process of implementing AOP aspect programming through AspectJ annotations and XML configuration has been shared with you.

Generally speaking, annotation-based declarations take precedence over XML-based declarations. Through AspectJ annotations, aspects can be compatible with AspectJ, and XML-based configuration is Spring-specific. As AspectJ is supported by more and more AOP frameworks, aspects written in annotated style will have more opportunities for reuse.

Click to follow and learn about Huawei Cloud's fresh technology for the first time~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量