Spring AOP based on AspectJ

brief introduction

AspectJ is an AOP framework based on Java language. Spring 2.0 added support for AspectJ pointcut expressions. Because AspectJ did not appear in spring 1.0;

Support for annotations has been added in AspectJ 1.5, allowing you to define facets directly in the Bean class. New version of Spring framework
We all use AspectJ to develop AOP, and provide a very flexible and powerful pointcut expression;

Of course, whether Spring's own AOP or AspectJ related concepts are the same;

Annotation configuration

Dependent import:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

Notification type

@Notification types provided by AspectJ:

  1. @Before pre notification is executed before the original method is executed

  2. @AfterReturning post notification executes before the original method executes

  3. @Around completely intercepts the execution of the original method. Before and after execution, you can add logic or not execute the original method

  4. @AfterThrowing throws a notification to execute the original method when there is an exception

  5. @After final notification, no matter whether it is abnormal or not, it will be executed after the original method call

  6. @DeclareParents referral notification, equivalent to introduction interceptor

Defining cut-off points

Defining the pointcut through the execution function

Syntax: execution (access modifier return type method name parameter exception)

Example expression:

Match all class public methods: execution(public * (..)) the first * represents the return value.. represents any arbitrary type parameter

Match all methods under the specified package: execution(* cn.xxx.dao. *(..)) the first * means ignore permission and return value type

Match all methods under the specified package: execution(* cn.xxx.dao.. * (..)) contains subpackages

Match all methods of the specified class: execution(* cn.xxx.service.UserService. *(..))

Match all class methods to implement a specific interface: execution(* cn.xxx.dao.GenericDAO +. * (..))

Match all methods starting with save: execution(* save * (..))

Before advice

pom dependence:

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

xml needs to add aop namespace and xsd:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--    Enable aspectj    -->
    <aop:aspectj-autoproxy/>
<!--    target-->
    <bean id="personDao" class="com.yh.demo1.PersonDao"/>
<!--    section-->
    <bean class="com.yh.demo1.MyAspect"/>
</beans>

test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test1 {
    @Autowired
    PersonDao personDao;

    @Test
    public void test(){
        personDao.delete();
        personDao.update();
    }
}

Section type:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
      //Indicates that all methods under PersonDao are tangent points
    @Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
    public void beforeAdvice(){
        System.out.println("before code run.....");
    }
}

When we need to get pointcut information (enhanced code), we can add parameters in the notification, like the following

@Aspect
public class MyAspect {
    @Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
    public void beforeAdvice2(JoinPoint point){
        System.out.println("before code run2....." + point);
    }
}

Post notice:

//When you need to get the return value of the original method, you can add the returning parameter to the annotation to specify the parameter name Aspectj, which will automatically put the return value into the parameter
@AfterReturning(value = "execution(* com.yh.demo1.PersonDao.delete(..))",returning = "result")
public void afterAdvice(Object result){
    System.out.println("After deleting method execution .....   The return value is:"+ result);
}

Post notification can get the return value of the target method

Surround notification:

@Around(value = "execution(* com.yh.demo1.PersonDao.insert(..))")
public void aroundAdvice(ProceedingJoinPoint point) throws Throwable {
    //code............
    System.out.println("Surround front..");

    //Execute the original method uuu you can declare the variable receive when you need to get the return value
    Object result = point.proceed();
    System.out.println("Original method return value: "+result);
    //code............
    System.out.println("Surround post..");
}

The biggest difference between surround notification and other notifications is that surround notification can control whether to call the original method or not

Note: the parameter type must be ProceedingJoinPoint, otherwise the original method cannot be executed,

Exception notification

@AfterThrowing(value = "execution(* com.yh.demo1.PersonDao.save(..))",throwing = "e")
public void exceptionHandler(JoinPoint point,Exception e){
    System.out.println(point + " Method appears"+e.getMessage()+"abnormal");
}

This notification is only executed when it appears in the method. If you need to get exception information, you can add throwing to the annotation to specify the parameter name

We can use orbit + exception notification to process database transactions, start and submit transactions in orbit, and roll back transactions in exception notification. Of course, Spring has encapsulated transactions and does not need to write by itself

Final notice

@After(value = "execution(* *delete(..))")
public void afterRun(){
    System.out.println("Final");
}

The final notification is called after, which is executed after the original method is called, whether or not an exception occurs in the original method

The latter is called after returning, which means that the execution will not be executed until the successful return

Expressions with logics:

In the expression, you can use the user logical operator, and & & or | not!

Example:

/*
execution(* cn.xxx.service.UserDao.insert(..))||execution(* cn.xxx.service.UserDao.delete(..))

execution(* cn.xxx.service.UserDao.*nsert(..))&&execution(* cn.xxx.service.UserDao.inser*(..))

!execution(* cn.xxx.service.UserDao.insert(..))
*/

Nomenclature of tangent points

Suppose that when multiple notifications are applied to the same tangent point, we need to write the execution expression repeatedly, and when we want to modify the tangent point later, multiple notifications need to be modified, which is very troublesome to maintain. We can complete the reuse and unified operation of the tangent point by specifying the name of the tangent point, so as to improve the development and maintenance efficiency;

//Define the name of the named tangent method, that is, the tangent name
@Pointcut(value = "execution(* com.yh.demo1.PersonDao.save(..))")
private void savePointcut(){}

@Pointcut(value = "execution(* com.yh.demo1.PersonDao.delete(..))")
private void deletePointcut(){}

Multiple notifications apply to the same tangent:

//Use named tangent
@Before(value = "savePointcut()")
public void beforeAdvice(){
    System.out.println("before code run.....");
}
//Use named tangent
@Around(value = "savePointcut()")
public void beforeAdvice2(ProceedingJoinPoint point) throws Throwable {
    System.out.println("Around the front");
    point.proceed();
    System.out.println("After surround");

One notification applies to multiple tangent points

//The same notice corresponds to multiple tangent points
@After(value = "savePointcut()||deletePointcut()")
public void afterAdvice(){
    System.out.println("after code run.....");
}

XML configuration

The jar required for XML configuration, the dependency between objects and the expression writing method are the same, just to write in another way;

xml:

        <!--target-->
    <bean id="studentDao" class="com.yh.demo2.StudentDao"/>
        <!--notice-->
    <bean id="advices" class="com.yh.demo2.XMLAdvice"/>

        <!--Weaving information-->
    <aop:config>
                <!--Definition of cut point-->
        <aop:pointcut id="select" expression="execution(* com.yh.demo2.StudentDao.select(..))"/>
                <!--Definition of section-->
        <aop:aspect ref="advices">
            <aop:before method="before" pointcut-ref="select"/>
            <aop:after-returning method="afterReturning" pointcut-ref="select" returning="result"/>
            <aop:after method="after" pointcut-ref="select" />
            <aop:after-throwing method="exception" pointcut-ref="select" throwing="e"/>
            <aop:around method="around" pointcut-ref="select"/>
        </aop:aspect>
      
        <!--Intrusive notification means that notification needs to implement the specified interface. Two kinds of interfaces cannot be used at the same time -->
        <aop:advisor advice-ref="advice2" pointcut-ref="select"/>
    </aop:config>
  <!--Intrusive notification Bean-->
  <bean id="advice2" class="com.yh.demo2.XMLAdvice2"/>

Notification class:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.JoinPoint;

public class XMLAdvice {
    public void before(JoinPoint pointcut){ System.out.println("Cut point of advance notice:"+pointcut); }

    public void afterReturning(JoinPoint point,Object result){
        System.out.println("Post notification cut point:"+point);
    }

    public void after(JoinPoint point){ System.out.println("Cut off point of final notice:"+point); }

    public void exception(JoinPoint point,Throwable e){
        System.out.println("Exception notification: " + e+"Tangent point:"+point);
    }

    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("Around the front");
        point.proceed();
        System.out.println("After surround");
    }
}

You will find that neither XML nor annotation need to specify the proxy manually, nor the target object. Aspectj will obtain the target object information from the tangent point and automatically create the proxy;

At present, AspectJ is a more popular way. Whether to use XML or annotation should be recommended by the team according to the specific situation of the project;

Tags: Java Spring xml Junit

Posted on Sat, 11 Jan 2020 07:29:42 -0500 by -Karl-