The principle and application of spring's dynamic agent aop

1, Dynamic agent

  • Dynamic proxy is based on interface proxy or subclass proxy

1. Proxy interface

public interface Calculator {
    public int add(int x, int y);
    public int sub(int x, int y);
    public int mul(int x, int y);
    public int div(int x, int y);
}

2. Implementation class of the interface being proxied

public class MyCalculator implements Calculator{
    @Override
    public int add(int x, int y) {
        return x+y;
    }
    @Override
    public int sub(int x, int y) {
        return x-y;
    }
    @Override
    public int mul(int x, int y) {
        return x*y;
    }
    @Override
    public int div(int x, int y) {
        return x/y;
    }
}

3. Creating proxy objects with reflections

public class CalculatorProxy {
    public static Calculator getCalculatorProxy(Calculator calculator) {
        //Gets the class loader for the proxy object
        ClassLoader loader = calculator.getClass().getClassLoader();
        //Gets all the interfaces implemented by the proxy object
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        //handle executed by proxy object
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //Before advice 
                LogUtils.beforeRun(method, args);
                //Method operation
                Object o = null;
                try {
                    o = method.invoke(calculator, args); //Run the method, and the calculator is based on the interface agent
                    //Post notification
                    LogUtils.ReturnRun(method, args);
                } catch (Exception e) {
                    //Exception notification
                    LogUtils.ExceptionRun(method, args);
                } finally {
                    //Final notice
                    LogUtils.AfterRun(method, args);
                }
                return o; //Parameter return value
            }
        };
        Object o = Proxy.newProxyInstance(loader, interfaces, h);
        return (Calculator) o;
    }
}

4. Log enhancement methods

public class LogUtils {
    public static void beforeRun(Method method, Object[] args) {
        System.out.println("method" + method.getName() + "Pre run log" + Arrays.asList(args));
    }
    public static void ReturnRun(Method method, Object[] args) {
        System.out.println("method" + method.getName() + "Run return log" + Arrays.asList(args));
    }
    public static void ExceptionRun(Method method, Object[] args) {
        System.out.println("method" + method.getName() + "Run exception log" + Arrays.asList(args));
    }
    public static void AfterRun(Method method, Object[] args) {
        System.out.println("method" + method.getName() + "Run end log" + Arrays.asList(args));
    }
}

5. Test and results

@Test
public void proxyCalculator() {
    Calculator calculator = new MyCalculator();
    //Get proxy object
    Calculator proxy = CalculatorProxy.getCalculatorProxy(calculator);
    //Proxy here is actually a proxy type, class com.sun.proxy.$Proxy4, not a Calculator type
    //If it is of type calculator, the method of calculator cannot be enhanced
    System.out.println(proxy.getClass()); 
    proxy.add(1, 2);
    System.out.println("=====================================================================");
    proxy.sub(1, 2);
    System.out.println("=====================================================================");
    proxy.mul(1, 2);
    System.out.println("=====================================================================");
    proxy.div(6, 0);
    System.out.println("=====================================================================");
}

2, AOP

1. Write the class of the proxy

public class Calculator { //There is no inherited parent class or implementation interface here. aop will use cglib to proxy common proxy objects of this class
    public int add(int x, int y) {
        System.out.println("Execution target method");
        return x+y;
    }
    public int sub(int x, int y) {
        System.out.println("Execution target method");
        return x-y;
    }
    public int mul(int x, int y) {
        System.out.println("Execution target method");
        return x*y;
    }
    public int div(int x, int y) {
        System.out.println("Execution target method");
        return x/y;
    }
}

2. Annotation configures two facet classes

  • Pointcut configuration pointcut expression
  • joinPoint gets information about the current enhancement method
    • joinPoint.getArgs(); Get enhanced method parameter list
    • getSignature().getName(); Gets the name of the method
  • @Before("cutPoint()") pre notification
  • @After returning (value = "cutPoint()", returning = "result") post notification
    • returning can be used to receive return values
  • @AfterThrowing(value = "cutPoint()", throwing = "exception") exception notification
    • throwing is used to specify that only the current exception type will execute the exception notification method, so generally try to write exceptions as large as possible
  • @Around(value = "cutPoint()") surround notification
@Aspect //Tag this is a faceted class
@Component //Inject into the container
public class Logs {
    //Configuring global enhancements
    @Pointcut("execution(public int com.myproject.service.Calculator.*(..))")
    public void cutPoint() { }

    /**
     *
     * @param joinPoint Gets information about the current enhancement method
     *                  joinPoint.getArgs(); //Get enhanced method parameter list
     *                  getSignature().getName(); //Gets the name of the method
     */
    //Before advice 
    @Before("cutPoint()")
    public static void beforeRun(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs(); //Get parameter list
        String method = joinPoint.getSignature().getName(); //Gets the name of the method
        System.out.println("[logs]" + "method" + method + "Before advice " + Arrays.asList(args));
    }

    //Return notification
    //returning can be used to receive return values
    @AfterReturning(value = "cutPoint()",returning = "result")
    public static void ReturnRun(JoinPoint joinPoint, Object result) {
        Object[] args = joinPoint.getArgs(); //Get parameter list
        String method = joinPoint.getSignature().getName(); //Gets the name of the method
        System.out.println("[logs]" +"method" + method + "Return notification" + Arrays.asList(args) + "The return value is" + result);
    }

    //Exception notification
    //throwing is used to specify that only the current exception type will execute the exception notification method, so generally try to write exceptions as large as possible
    @AfterThrowing(value = "cutPoint()", throwing = "exception")
    public static void ExceptionRun(JoinPoint joinPoint, NullPointerException exception) {
        Object[] args = joinPoint.getArgs(); //Get parameter list
        String method = joinPoint.getSignature().getName(); //Gets the name of the method
        System.out.println("[logs]" + "method" + method + "Exception log notification" + Arrays.asList(args));
    }

    //Post notification
    @After("cutPoint()")
    public static void AfterRun(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs(); //Get parameter list
        String method = joinPoint.getSignature().getName(); //Gets the name of the method
        System.out.println("[logs]" + "method" + method + "Post notification" + Arrays.asList(args));
    }

    //Around Advice 
    @Around(value = "cutPoint()")
    public static Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //Method executes the return value
        Object result = null;
        Object[] args = joinPoint.getArgs(); //Get parameter list
        String method = joinPoint.getSignature().getName(); //Gets the name of the method
        try {
            //Before advice 
            System.out.println("[logs surround]" + "method" + method + "Before advice " + Arrays.asList(args));
            //The underlying layer actually calls the target method through reflection
            result = joinPoint.proceed(args);
            System.out.println("[logs surround]" + "method" + method + "Return notification" + Arrays.asList(args) + "The return value is" + result);
        } catch (Exception e) {
            System.out.println("[logs surround]" + "method" + method + "Exception notification" + Arrays.asList(args));
            throw new RuntimeException();
        } finally {
            System.out.println("[logs surround]" + "method" + method + "Post notification" + Arrays.asList(args));
        }
        //Returns the result of the target method execution
        return result;
    }
}
@Aspect //Tag this is a faceted class
@Component //Inject into the container
public class Valid { //Enhancement of parameter validation
    //Configuring global enhancements
    @Pointcut("execution(public int com.myproject.service.Calculator.*(..))")
    public void cutPoint() { }

    /**
     *
     * @param joinPoint Gets information about the current enhancement method
     *                  joinPoint.getArgs(); //Get enhanced method parameter list
     *                  getSignature().getName(); //Gets the name of the method
     */
    //Before advice 
    @Before("cutPoint()")
    public static void beforeRun(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs(); //Get parameter list
        String method = joinPoint.getSignature().getName(); //Gets the name of the method
        System.out.println("[Valid]" + "method" + method + "Before advice " + Arrays.asList(args));
    }

    //Return notification
    //returning can be used to receive return values
    @AfterReturning(value = "cutPoint()",returning = "result")
    public static void ReturnRun(JoinPoint joinPoint, Object result) {
        Object[] args = joinPoint.getArgs(); //Get parameter list
        String method = joinPoint.getSignature().getName(); //Gets the name of the method
        System.out.println("[Valid]" +"method" + method + "Return notification" + Arrays.asList(args) + "The return value is" + result);
    }

    //Exception notification
    //throwing is used to specify that only the current exception type will execute the exception notification method, so generally try to write exceptions as large as possible
    @AfterThrowing(value = "cutPoint()", throwing = "exception")
    public static void ExceptionRun(JoinPoint joinPoint, NullPointerException exception) {
        Object[] args = joinPoint.getArgs(); //Get parameter list
        String method = joinPoint.getSignature().getName(); //Gets the name of the method
        System.out.println("[Valid]" + "method" + method + "Exception log notification" + Arrays.asList(args));
    }

    //Post notification
    @After("cutPoint()")
    public static void AfterRun(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs(); //Get parameter list
        String method = joinPoint.getSignature().getName(); //Gets the name of the method
        System.out.println("[Valid]" + "method" + method + "Post direct notification" + Arrays.asList(args));
    }
}

3. Tests and results

public class AopTest {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

    @Test
    public void test1() {
        //Get facet class
        Calculator calculator = context.getBean(Calculator.class);
        //The calculator here is not a calculator type, but a proxy object. Only the proxy object can enhance the methods of the class
        System.out.println(calculator.getClass());
        int add = calculator.add(1, 2);
        System.out.println(add);
    }
}

4. xml configuration

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

   <bean id="log" class="com.myproject.xml.LogsXml"></bean>
   <bean id="valid" class="com.myproject.xml.ValidXml"></bean>
   <bean id="calculator" class="com.myproject.service.Calculator"></bean>

   <aop:config>
      <aop:pointcut id="point" expression="execution(public * com.myproject.service.*.*(..))"/>
      <!--      Configure global pointcut expressions-->
<!--      order You can set the execution order of facet class methods. The smaller the value, the earlier the pre notification will be executed-->
      <aop:aspect ref="log" order="2">
<!--      If the surround notification is configured before the ordinary notification, it will be executed first, otherwise it will be executed after the ordinary notification-->
         <aop:around method="around" pointcut-ref="point"/>
         <aop:before pointcut-ref="point" method="beforeRun"/>
         <aop:after-returning method="returnRun" pointcut-ref="point" returning="result"/>
         <aop:after-throwing method="exceptionRun" pointcut-ref="point" throwing="exception"/>
         <aop:after method="afterRun" pointcut-ref="point" />
      </aop:aspect>
      <aop:aspect ref="valid" order="1">
         <aop:before pointcut-ref="point" method="beforeRun"/>
         <aop:after-returning method="returnRun" pointcut-ref="point" returning="result"/>
         <aop:after-throwing method="exceptionRun" pointcut-ref="point" throwing="exception"/>
         <aop:after method="afterRun" pointcut-ref="point" />
      </aop:aspect>
   </aop:config>
</beans>

3, Transaction enhancement

1. Configure transaction enhancement applicationcontext.xml

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

<!--   Import external profile-->
   <context:property-placeholder location="classpath:jdbc.properties"/>
<!--    Enable annotation package scanning-->
   <context:component-scan base-package="com.myproject"/>
<!--Configure data sources-->
   <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      <property name="jdbcUrl" value="${jdbc.url}"/>
      <property name="driverClass" value="${jdbc.driver}"/>
      <property name="user" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
   </bean>
<!--Operation database-->
   <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
      <property name="dataSource" ref="dataSource"/>
   </bean>
<!--   Configure transaction manager-->
   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"></property>
   </bean>
<!--Enable annotation based transaction configuration-->
   <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

2. The service layer needs to add transaction methods

  • Annotation configuration transactions only need to mark @ Transactional on the method
@Service
public class AccountService {
    @Autowired
    private AccountDao accountDao;

    /**
     *     Transactional Properties:
     *     Propagation propagation() default Propagation.REQUIRED; Propagation behavior of transactions
     *     Isolation isolation() default Isolation.DEFAULT; Isolation property of things
     *     int timeout() default -1; Set transaction timeout, int
     *     String timeoutString() default ""; Set transaction timeout, String
     *     boolean readOnly() default false; Set whether the transaction is a read-only transaction. You can optimize the transaction and speed up the query, but you can't add, delete or modify it
     *     Class<? extends Throwable>[] rollbackFor() default {}; Set which exceptions need to be rolled back, class array
     *     String[] rollbackForClassName() default {}; Set which exceptions need to be rolled back, String the full class name, and roll back the exceptions that were not rolled back
     *     Exception classification:
     *              (If an exception occurs during compilation, the system will not roll back by default
     *              (Default rollback in case of non inspected) runtime exception
     *     Class<? extends Throwable>[] noRollbackFor() default {}; Set which exceptions do not need to be rolled back, the class array, and the original rolled back exceptions do not need to be rolled back
     *     String[] noRollbackForClassName() default {}; Set which exceptions do not need to be rolled back. String the full class name
     */

    @Transactional(propagation = Propagation.REQUIRED)
    public void balanceSwap(int money) {
        System.out.println("ccc To users ddd transfer accounts" + money);
        accountDao.addBalance(money);
        accountDao.subBalance(money);
    }
}
@Service
public class CouterService {
    @Autowired
    private CouterDao couterDao;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void update(int money) {
        couterDao.update(money);
    }
}
@Service
public class MuliService {
    @Autowired
    private AccountService accountService;
    @Autowired
    private CouterService couterService;
    @Transactional
    public void updateAll(int money) {
        accountService.balanceSwap(money);//REQUIRED
        couterService.update(money); //REQUIRES_NEW
        int i = 1/0; //Manufacturing exception
    }
}
  • There are two methods in the above updateAll transaction, balanceSwap and update. Both methods add transactions;
  • The transaction propagation attribute of balanceSwap is REQUIRED, so balanceSwap is directly added to the updateAll transaction for execution;
  • The transaction propagation attribute of update is requirements_ New, update will restart another transaction execution;
  • When an exception occurs, the update transaction can be successfully committed, and the balanceSwap transaction is rolled back;
  • Tip: when adding a transaction to a method, you should use its proxy object to call it, otherwise the transaction will not take effect, such as the following:
@Service
public class AccountService2 {
    @Autowired
    private AccountDao accountDao;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void update1() {
        accountDao.subBalance(10);
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void update2() {
        accountDao.addBalance(10);
    }
    @Transactional
    public void update3() { 
        //If required according to the propagation behavior of the transaction_ New, even if exceptions are encountered, the two transactions are committed normally, but both transactions are rolled back here
        update1();
        update2();
        int i = 1/0;
    }
}
  • This is because update3 calls update1 and update2 inside the AccountService2 class without using the proxy object of AccountService2. Therefore, update1 and update2 are simply called here, which has nothing to do with transactions, As we mentioned earlier, aop enhances methods by using proxy objects to call methods.

3. Transaction propagation behavior requirements_ New and REQUIRED instances

service() { //This method adds a transaction
    //REQUIRED
    a(){
        //REQUIRES_NEW
        b(){}
        //REQUIRED
        c(){}
    }
    //REQUIRES_NEW
    d(){
        do()
        //REQUIRED
        e(){
            //REQUIRES_NEW
            f(){
                Exception2
            }
        }
        //REQUIRES_NEW
        g(){}
    }
    Exception1
}
  • If an exception occurs in Exception1, Transactions B, D, e and G can be successfully committed; a. C transaction rollback
  • If an exception occurs in Exception2, the transaction b can be successfully committed; a. C, F, g and e transactions are rolled back. To be exact, g will not be executed because the exception has occurred earlier. The do transaction is in requirements_ It can be submitted under new and rolled back under REQUIRED

Tags: Java Spring AOP

Posted on Sun, 03 Oct 2021 21:11:33 -0400 by Madzz