Spring: AOP aspect oriented programming

Transfer case

Requirements: use spring framework to integrate DBUtils technology to realize user transfer function

  • Basic functions

Step analysis:

  1. Create a java project and import coordinates
  2. Write Account entity class
  3. Write the AccountDao interface and implementation class
  4. Write the AccountService interface and implementation class
  5. Write spring core configuration file
  6. Write test code

1. Create a java project and import coordinates

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.15</version>
    </dependency>

    <dependency>
        <groupId>commons-dbutils</groupId>
        <artifactId>commons-dbutils</artifactId>
        <version>1.6</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
</dependencies>

2. Write AccountDao interface and implementation class

public interface AccountDao {
    /**
     * Transfer out operation
     * @param outUser
     * @param money
     */
    void out(String outUser,Double money);

    /**
     * Transfer in operation
     * @param inUser
     * @param money
     */
    void in(String inUser,Double money);
}
@Repository("AccountDao")
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private QueryRunner queryRunner;

    @Override
    public void out(String outUser, Double money) {
        String sql = "update account set money = money - ? where name = ?";
        try {
            queryRunner.update(sql, money, outUser);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    @Override
    public void in(String inUser, Double money) {
        String sql = "update account set money = money + ? where name = ?";
        try {
            queryRunner.update(sql, money, inUser);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

3. Write AccountService interface and implementation class

public interface AccountService {
        /**
         * Transfer method
         */
        void transfer(String outUser,String inUser,Double money);
}
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    /**
     * Transfer method
     */
    @Override
    public void transfer(String outUser, String inUser, Double money) {
        // Write transaction related code
        // The money reduction method was called
        accountDao.out(outUser,money);

        // Simulation error
        // int i= 1/0;

        // The add money method was called
        accountDao.in(inUser,money);
    }
}

4. Write spring core 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
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
">
    <!-- Enable annotation scanning -->
    <context:component-scan base-package="com.zm"/>
    <!-- introduce properties -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- to configure DataSource -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!-- to configure queryRunner -->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>
</beans>

5. Write test code

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer("tom", "jerry", 100d);
    }
}

problem analysis
The above code transaction is in the Dao layer, and the transfer out and transfer in operations are independent transactions. However, in actual development, the business logic should be controlled in one transaction, so the transaction should be moved to the Service layer.

  • Traditional affairs

Step analysis:

  1. Write thread binding tool class
  2. Write transaction manager
  3. Modify the service layer code
  4. Modify dao layer code

1. Write thread binding tool class

@Component
public class ConnectionUtils {
    @Autowired
    private DataSource dataSource;

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    /**
     * Get the bound connection on the current thread: if the obtained connection is empty, get the connection from the data source and put it in ThreadLocal (bound to the current thread)
     */
    public Connection getThreadConnection() {
        // 1. Get the connection from ThreadLocal first
        Connection connection = threadLocal.get();
        // 2. Judge whether there is a Connection in the current thread
        if(connection == null){
            // 3. Obtain a connection from the data source and store it in ThreadLocal
            try {
                // Not null
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return  connection;
    }

    /**
     * Unbind the connection of the current thread
     */
    public void  removeThreadConnection(){
        threadLocal.remove();
    }
}

2. Write transaction manager

@Component("transactionManager")
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * Open transaction
     */
    public void beginTransaction(){
        // Get connection object
        Connection connection = connectionUtils.getThreadConnection();
        try {
            // A manual transaction was started
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * Commit transaction
     */
    public void commit(){
        Connection connection = connectionUtils.getThreadConnection();
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * Rollback transaction
     */
    public void rollback(){
        Connection connection = connectionUtils.getThreadConnection();
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * Release resources
     */
    public void release(){
        // Change manual transaction back to auto commit transaction
        Connection connection = connectionUtils.getThreadConnection();
        try {
            connection.setAutoCommit(true);
            // Return connections to the connection pool
            connectionUtils.getThreadConnection().close();
            // Unbind thread
            connectionUtils.removeThreadConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

3. Modify the service layer code

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Autowired
    private TransactionManager transactionManager;

    @Override
    public void transfer(String outUser, String inUser, Double money) {
        try {
            // 1. Start transaction
            transactionManager.beginTransaction();

            // 2. Business operation
            // Write transaction related code
            // The money reduction method was called
            accountDao.out(outUser, money);
            // Simulation error
            // int i= 1/0;
            // The add money method was called
            accountDao.in(inUser, money);

            // 3. Submission of services
            transactionManager.commit();
        } catch (Exception e) {
            // 4. Rollback transaction
            transactionManager.rollback();
            e.printStackTrace();
        } finally {
            // 5. Release resources
            transactionManager.release();
        }
    }
}

4. Modify Dao layer code

@Repository("AccountDao")
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private QueryRunner queryRunner;

    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * Transfer out operation
     */
    @Override
    public void out(String outUser, Double money) {
        String sql = "update account set money = money - ? where name = ?";
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), sql, money, outUser);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    /**
     * Transfer in operation
     */
    @Override
    public void in(String inUser, Double money) {
        String sql = "update account set money = money + ? where name = ?";
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), sql, money, inUser);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

problem analysis
The above code can realize transaction control through the transformation of the business layer, but due to the addition of transaction control, a new problem also arises: the business layer method becomes bloated and filled with a lot of duplicate code. And the business layer method and transaction control method are coupled, which violates the object-oriented development idea.

Proxy optimized transfer case

Business code and transaction code can be split to enhance business methods through dynamic agent. This will not affect the business layer and solve the problem of coupling.

Common dynamic agent technologies:

  • JDK agent, a dynamic proxy technology based on interface, uses the interceptor (invocationHandler) and the reflection mechanism to generate an anonymous class of the proxy interface. It calls InvokeHandler before calling the specific method, so as to enhance the method.

  • CGLIB proxy, dynamic proxy technology based on parent class - dynamically generate a subclass to be proxy, and the subclass overrides all non final methods of the class to be proxy; In the subclass, the method interception technology is used to intercept the calls of all the parent methods, and the crosscutting logic is weaved in to enhance the methods

  • JDK dynamic proxy mode

Remove the transaction control code of AccountServiceImpl

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String outUser, String inUser, Double money) {
        accountDao.out(outUser, money);
        // Simulation error
        int i= 1/0;
        accountDao.in(inUser, money);
    }
}

JDK factory class

@Component
public class JDKProxyFactory {
    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    /**
     * JDK dynamic proxy technology is used to generate the proxy object of the target class
     * ClassLoader loader : Class loader: get the class loader with the proxy object
     * Class<?>[] interfaces :  All interfaces that the proxy class needs to implement
     * InvocationHandler h :  When the proxy object calls any method in the interface, the invoke method in InvocationHandler will be executed
     */
    public AccountService createAccountServiceJdkProxy() {
        return (AccountService) Proxy.newProxyInstance(
                accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * @param proxy Current proxy object reference
                     * @param method The reference of the called target method
                     * @param args The parameters used by the called target method
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) {
                        try {
                            if ("transfer".equalsIgnoreCase(method.getName())) {
                                System.out.println("JDK Proxy: Pre-Enhance ...");
                                transactionManager.beginTransaction();

                                method.invoke(accountService, args);

                                System.out.println("JDK Proxy: Post-Enhance ...");
                                transactionManager.commit();
                            } else {
                                method.invoke(accountService, args);
                            }
                        } catch (Exception e) {
                            transactionManager.rollback();
                            e.printStackTrace();
                        } finally {
                            transactionManager.release();
                        }

                        return null;
                    }
                });
    }

}

Test code

@Autowired
private JDKProxyFactory jdkProxyFactory;

@Test
public void testTransferProxyJDK(){
    // The proxy object currently returned is actually the proxy object of AccountService
    AccountService accountServiceJDKProxy = jdkProxyFactory.createAccountServiceJDKProxy();
    // When the proxy object invokes any method in the proxy interface, the underlying invoke method will be executed
    accountServiceJDKProxy.transfer("tom", "jerry", 100d);
}
  • CGLIB dynamic proxy mode

CGLIB factory class

@Component
public class CglibProxyFactory {
    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    /**
     * Write the API corresponding to cglib to generate proxy objects for return
     * Parameter 1: bytecode object of target class
     * Parameter 2: action class. When the proxy object calls the original method of the target object, the intercept method will be executed
     */
    public AccountService createAccountServiceCglibProxy() {
        return (AccountService) Enhancer.create(accountService.getClass(), new MethodInterceptor() {
            /**
             * @param o Represents the generated proxy object
             * @param method Reference to the calling target method
             * @param objects Method input parameter
             * @param methodProxy Proxy method
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
                try {
                    System.out.println("CGLIB Proxy: Pre-Enhance ...");
                    // Start transaction manually: call the start transaction method in the transaction manager class
                    transactionManager.beginTransaction();

                    method.invoke(accountService, objects);

                    System.out.println("CGLIB Proxy: Post-Enhance ...");
                    transactionManager.commit();
                } catch (Exception e) {
                    // Manually rollback transactions
                    transactionManager.rollback();
                    e.printStackTrace();
                } finally {
                    // Release resources manually
                    transactionManager.release();
                }

                return null;
            }
        });
    }
}

Getting to know AOP

  • What is AOP

AOP is the abbreviation of Aspect Oriented Programming, which means Aspect Oriented Programming.

AOP is the continuation of OOP (object-oriented programming). It is a hot spot in software development and an important content in Spring framework. Using AOP can isolate each part of business logic, reduce the coupling between each part of business logic, improve the reusability of program and improve the efficiency of development.

Advantages:

  1. During the running of the program, the function of the method is enhanced without modifying the source code
  2. The logic is clear. When developing the core business, you don't have to pay attention to the code to enhance the business
  3. Reduce duplicate code, improve development efficiency and facilitate later maintenance
  • AOP underlying implementation

In fact, the underlying layer of AOP is implemented through the dynamic proxy technology provided by Spring. During operation, Spring dynamically generates proxy objects through dynamic proxy technology. When the proxy object method is executed, it intervenes to enhance the function, and calls the method of the target object, so as to complete the function enhancement.

  • AOP related terms

The bottom layer of Spring's AOP implementation is to encapsulate the code of the above dynamic agent. After encapsulation, we only need to code the parts that need attention, and complete the method enhancement of the specified goal through configuration.

Before formally explaining the operation of AOP, we must understand the relevant terms of AOP. The commonly used terms are as follows:

* Target(Target object): The target object of the agent; Proxy class

* Proxy(Proxy): a class is AOP After weaving in the enhancement, a result proxy class is generated; Generate proxy object

* Joinpoint(Connection points): the so-called connection points refer to those points that can be intercepted; stay spring In, these points refer to methods because spring Only connection points of method type are supported; Methods that can be intercepted and enhanced

* Pointcut(Entry point): the so-called entry point refers to what we want to do Joinpoint Definition of interception; That is, the method of real interception and enhancement

* Advice(notice/ Enhanced): the so-called notification refers to the interception of Joinpoint The next thing to do is to classify Notifications: pre notification, post notification, exception notification, final notification and surround notification - A type that can be manually controlled by code; Enhanced business logic

* Aspect(Aspect: it is the combination of entry point and notification (Introduction)

* Weaving(Weaving: refers to the process of applying enhancements to the target object to create a new proxy object. Spring Dynamic proxy weaving is adopted, and AspectJ Compile time weaving and class load time weaving are adopted
  • Clear matters for AOP development

Development stage

  1. Entry point for writing core business code (target method of target class)
  2. Extract the common code and make it into notification (enhancement method)
  3. In the configuration file, declare the relationship between pointcuts and notifications, that is, facets

Run phase (Spring framework completes automatically)

The Spring framework monitors the execution of pointcut methods. Once it is monitored that the pointcut method is running, the proxy mechanism is used to dynamically create the proxy object of the target object. According to the notification category, the corresponding function of the notification is woven into the corresponding position of the proxy object to complete the complete code logic operation.

Underlying agent implementation

In Spring, the framework will decide which dynamic proxy method to adopt according to whether the target class implements the interface:

  • When the bean implements the interface, the JDK proxy mode is used
  • When the bean does not implement the interface, it can be implemented with cglib (cglib can be forcibly used) (add < AOP: AspectJ AutoProxy proxy target class = "true" / > in the spring configuration)

Summary

  • aop - aspect oriented programming
  • The underlying implementation of aop: dynamic agent based on JDK and dynamic agent based on Cglib
  • Key concepts of aop:
        Pointcut: a truly enhanced method
        Advice: encapsulates ways to enhance business logic
        Aspect: pointcut + notification
        Weaving: the process of combining pointcuts with notifications to generate proxy objects

AOP development based on XML

  • quick get start

Step analysis:

  1. Create a java project and import AOP related coordinates
  2. Create target interfaces and target implementation classes (define pointcuts)
  3. Create notification class and method (define notification)
  4. Give spring the right to create the target class and notification class objects
  5. Configure the weaving relationship and section in the core configuration file
  6. Write test code

1. Create a java project and import AOP related coordinates

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    <java.version>1.11</java.version>
    <maven.compiler.source>1.11</maven.compiler.source>
    <maven.compiler.target>1.11</maven.compiler.target>
</properties>

<dependencies>
    <!-- Import spring of context Coordinates, context rely on aop -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <!-- aspectj This is required for the weaving (tangent point) expression of jar Package) -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <!-- spring integration junit -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

2. Create target interface and target implementation class

public interface AccountService {
    /**
     * Target method: (entry point: the method to be intercepted and enhanced)
     */
    void transfer();
}
public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer() {
        System.out.println("The transfer method is implemented....");
        //int i = 1/0;
    }
}

3. Create notification class

public class MyAdvice {
    public void before(){
        System.out.println("Pre notification executed....");
    }
}

4. Give the creation right of target class and notification class objects to spring

<!-- Target class to IOC container -->
<bean id="accountServcie" class="com.zm.service.impl.AccountServiceImpl"/>

<!-- Notification class to IOC container -->
<bean id="myAdvice" class="com.zm.advice.MyAdvice"/>

5. Configure the weaving relationship and section in the core configuration file
Import AOP namespace

<?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
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <bean id="accountServcie" class="com.zm.service.impl.AccountServiceImpl"/>
    
    <bean id="myAdvice" class="com.zm.advice.MyAdvice"/>
    
    <aop:config>
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut="execution(public void com.zm.service.impl.AccountServiceImpl.transfer())"/>
        </aop:aspect>
    </aop:config>

</beans>

6. Write test code

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer();
    }
}
  • Detailed explanation of XML configuration AOP

Tangent expression

Expression syntax:

excution( [Modifier ]  Return value type package name.Class name.Method name (parameter)
  • The access modifier can be omitted
  • The returned value type, package name, class name and method name can be replaced by asterisk *, representing any
  • A point between the package name and the class name. It represents the class under the current package. There are two points  .. Represents the classes under the current package and its sub packages
  • The parameter list can use two points.. to represent any number and any type of parameter list

example:

  • execution([modifier] return value type package name. Class name. Method name (parameter))
    execution(public void com.renda.service.impl.AccountServiceImpl.transfer(java.lang.String))

  • The access modifier can be omitted
    execution(void com.renda.service.impl.AccountServiceImpl.transfer(java.lang.String))

  • The returned value type, package name, class name and method name can be replaced by asterisk *, representing any
    execution(* .....())

  • A point between the package name and the class name represents the classes under the current package, and two points.. represent the classes under the current package and its sub packages
    execution(* ...*())

  • The parameter list can use two points.. to represent any number and any type of parameter list
    execution(* ...*(..))

Tangent expression extraction:
When multiple enhanced pointcut expressions are the same, the pointcut expression can be extracted. In the enhancement, the pointcut ref attribute is used instead of the pointcut attribute to reference the extracted pointcut expression.

public class MyAdvice {
    public void before(){
        System.out.println("Pre notification executed....");
    }

    public void afterReturning(){
        System.out.println("Post notification executed....");
    }

    public void afterThrowing(){
        System.out.println("Exception notification executed....");
    }

    public void after(){
        System.out.println("The final notice was executed....");
    }

    /**
     * @param pjp Proceeding JoinPoint - Connection point being executed: tangent point
     */
    public Object around(ProceedingJoinPoint pjp){
        Object proceed = null;
        try {
            System.out.println("Pre notification executed");
            // Tangent point method execution
            proceed = pjp.proceed();
            System.out.println("Post notification executed");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("Exception notification executed");
        }finally {
            System.out.println("The final notice was executed");
        }

        return proceed;
    }
}

<aop:config>
    <!-- Extracted tangent expression -->
    <aop:pointcut id="myPointcut" expression="execution(* com.zm.service.impl.AccountServiceImpl.*(..))"/>
    <!-- Configuration aspect: entry point + notice -->
    <aop:aspect ref="myAdvice">
        <aop:before method="before" pointcut-ref="myPointcut"/>
        <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
        <aop:after method="after" pointcut-ref="myPointcut"/>
        <!-- <aop:around method="around" pointcut-ref="myPointcut"/> -->
    </aop:aspect>
</aop:config>

Notification type

Configuration syntax for notifications:

<aop:Notification type method="Method name in notification class pointcut="Tangent expression"></aop:Notification type>
namelabelexplain
Before advice <aop:before>Used to configure pre notification. Specifies that the enhanced method is executed before the pointcut method
Post notification<aop:afterReturning>Used to configure post notifications. Specifies that the enhanced method is executed after the pointcut method
Exception notification<aop:afterThrowing>Used to configure exception notification; Specifies that the enhanced method is executed after an exception occurs
Final notice<aop:after>Used to configure the final notification; The pointcut method executes regardless of whether there is an exception during execution
Around Advice <aop:around>Used to configure surround notification; Developers can manually control when enhanced code is executed

Note: in general, surround notifications are used independently

  • Summary

  • aop weaving configuration
    <aop:config>
         < AOP: aspect ref = "notification class" >
               < AOP: before method = "notification method name" pointcut = "pointcut expression" > < / AOP: before >
         </aop:aspect>
    </aop:config>

  • Type of notification
    Pre notification, post notification, exception notification and final notification
    Around Advice

  • Tangent expression
    execution([modifier] return value type package name. Class name. Method name (parameter))

Annotation based AOP development

  • quick get start

Step analysis:

  1. Create a java project and import AOP related coordinates
  2. Create target interfaces and target implementation classes (define pointcuts)
  3. Create notification class (define notification)
  4. Give spring the right to create the target class and notification class objects
  5. Use annotations to configure the weaving relationship in the notification class and upgrade it to a faceted class
  6. Turn on the automatic agent for component scanning and AOP in the configuration file
  7. Write test code

1. Create a java project and import AOP related coordinates

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    <java.version>1.11</java.version>
    <maven.compiler.source>1.11</maven.compiler.source>
    <maven.compiler.target>1.11</maven.compiler.target>
</properties>

<dependencies>
    <!-- Import spring of context Coordinates, context rely on aop -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <!-- aspectj Weaving in -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <!-- spring integration junit -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

2. Create target interface and target implementation class

public interface AccountService {
    void transfer();
}
public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer() {
        System.out.println("The transfer method is implemented....");
    }
}

3. Create notification class

public class MyAdvice {
    public void before(){
        System.out.println("Pre notification executed....");
    }
}

4. Give the creation right of target class and notification class objects to spring

@Service
public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer() {
        System.out.println("The transfer method is implemented....");
    }
}

@Component
public class MyAdvice {
    ...
}

5. Use annotations to configure the weaving relationship in the notification class and upgrade it to the aspect class

@Component
@Aspect // Upgrade to aspect class: configure the relationship between pointcuts and notifications
public class MyAdvice {
    @Before("execution(* com.zm.service.impl.AccountServiceImpl.*(..))")
    public void before(){
        System.out.println("Pre notification executed....");
    }
}

6. Enable the automatic agent of component scanning and AOP 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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- open IOC Annotation scan -->
    <context:component-scan base-package="com.zm"/>

    <!-- aop Automatic agent: dynamic agent is used to enhance weaving and generate agent; proxy-target-class="true" Indicates mandatory use cglib Dynamic agent-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>

</beans>

7. Write test code

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        accountService.transfer();
    }

}
  • Annotation configuration AOP details

Tangent expression

Extraction of tangent expression

@Component
@Aspect // Upgrade to aspect class: configure the relationship between pointcuts and notifications
public class MyAdvice {

    @Pointcut("execution(* com.zm.service.impl.AccountServiceImpl.*(..))")
    public void myPoint(){

    }

    @Before("MyAdvice.myPoint()")
    public void before(){
        System.out.println("Pre notification executed....");
    }

    @AfterReturning("MyAdvice.myPoint()")
    public void afterReturning(){
        System.out.println("Post notification executed....");
    }

    @AfterThrowing("MyAdvice.myPoint()")
    public void afterThrowing(){
        System.out.println("Exception notification executed....");
    }

    @After("MyAdvice.myPoint()")
    public void after(){
        System.out.println("The final notice was executed....");
    }



    /**
     * @param pjp Proceeding JoinPoint - Connection point being executed: tangent point
     */
    public Object around(ProceedingJoinPoint pjp){
        Object proceed = null;
        try {
            System.out.println("Pre notification executed");
            // Tangent point method execution
            proceed = pjp.proceed();
            System.out.println("Post notification executed");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("Exception notification executed");
        }finally {
            System.out.println("The final notice was executed");
        }

        return proceed;
    }

}

Notification type

Configuration syntax of notification: @ notification annotation ("pointcut expression")

namelabelexplain
Before advice @BeforeUsed to configure pre notification. Specifies that the enhanced method is executed before the pointcut method
Post notification@AfterReturningUsed to configure post notification. Specifies that the enhanced method is executed after the pointcut method
Exception notification@AfterThrowingUsed to configure exception notification; specifies the enhanced method to execute after an exception occurs
Final notice@AfterUsed to configure the final notification; it will be executed regardless of whether there are exceptions when the pointcut method is executed
Around Advice @AroundUsed to configure surround notifications; developers can manually control when enhanced code is executed

be careful
When the current four notifications are combined, a Bug of Spring execution order appears. The wrong execution order is as follows:

@Before -> @After -> @AfterReturning(If there are exceptions:@AfterThrowing)

If the surround notification @ Around annotation is used alone, there will be no Bug. The execution sequence is as follows:

@Before -> @AfterReturning(If there are exceptions:@AfterThrowing)-> @After

Annotation only configuration

Remove the applicationContext.xml configuration file and add the SpringConfig configuration class

@Configuration
@ComponentScan("com.zm")
@EnableAspectJAutoProxy // Turn on the automatic proxy of AOP and replace the < AOP: AspectJ AutoProxy / > configured in xml
public class SpringConfig {
}

Modify test class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        accountService.transfer();
    }

}
  • summary

  • Use the @ Aspect annotation to label the facet class
  • Use @ Before and other annotations to mark the notification method
  • Use @ Pointcut annotation to extract tangent expression
  • Configure aop auto proxy < aop: AspectJ AutoProxy / > or @ EnableAspectJAutoProxy

AOP optimized transfer case

Still using the previous transfer case, the two agent factory objects are deleted directly and implemented with spring's AOP idea

  • xml configuration implementation

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

    <!-- Enable annotation scanning -->
    <context:component-scan base-package="com.zm"/>

    <!-- introduce properties,load jdbc configuration file -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- to configure DataSource -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- to configure queryRunner -->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>



    <!-- AOP to configure -->
      <aop:config>
          <!-- 1.Tangent expression -->
          <aop:pointcut id="myPointcut" expression="execution(* com.zm.service.impl.AccountServiceImpl.*(..))"/>
          <!-- 2.Section configuration -->
          <aop:aspect ref="transactionManager">
              <aop:before method="beginTransaction" pointcut-ref="myPointcut"/>
              <aop:after-returning method="commit"  pointcut-ref="myPointcut"/>
              <aop:after-throwing method="rollback" pointcut-ref="myPointcut"/>
              <aop:after method="release" pointcut-ref="myPointcut"/>
          </aop:aspect>
      </aop:config>

</beans>

Transaction manager (notification)

package com.zm.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * The transaction manager tool class includes: start transaction, commit transaction, rollback transaction, and release resource
 *      Spring AOP Notification class for
 *
 */
@Component("transactionManager")
public class TransactionManager {

    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * Open transaction
     */
    public void beginTransaction(){
        // Get connection object
        Connection connection = connectionUtils.getThreadConnection();
        try {
            // A manual transaction was started
            connection.setAutoCommit(false);
            System.out.println("Open transaction");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * Commit transaction
     */
    public void commit(){
        Connection connection = connectionUtils.getThreadConnection();
        try {
            connection.commit();
            System.out.println("Commit transaction");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * Rollback transaction
     */
    public void rollback(){
        Connection connection = connectionUtils.getThreadConnection();
        try {
            connection.rollback();
            System.out.println("Rollback transaction");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * Release resources
     */
    public void release(){
        // Change manual transaction back to auto commit transaction
        Connection connection = connectionUtils.getThreadConnection();
        try {
            connection.setAutoCommit(true);
            // Return connections to the connection pool
            connectionUtils.getThreadConnection().close();
            // Unbind thread
            connectionUtils.removeThreadConnection();
            System.out.println("Release resources");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}
  • Annotation configuration implementation

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

    <!-- Enable annotation scanning -->
    <context:component-scan base-package="com.zm"/>

    <!-- introduce properties -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- to configure DataSource -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- to configure queryRunner -->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

    <!-- open AOP Automatic proxy for -->
    <aop:aspectj-autoproxy/>

</beans>

Transaction manager (notification)

@Component("transactionManager")
@Aspect // Indicates that this class is a section class
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;

    @Around("execution(* com.zm.service.impl.AccountServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws SQLException {
        Object proceed = null;

        try {
            // Start manual transaction
            System.out.println("Open transaction");
            connectionUtils.getThreadConnection().setAutoCommit(false);

            // Pointcut method execution
            proceed = pjp.proceed();

            // Manually commit transactions
            System.out.println("Commit transaction");
            connectionUtils.getThreadConnection().commit();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            // Manually rollback transactions
            System.out.println("Rollback transaction");
            connectionUtils.getThreadConnection().rollback();
        } finally {
            System.out.println("Release resources");
            // Restore manual transactions to automatic transactions
            connectionUtils.getThreadConnection().setAutoCommit(true);
            // Return connections to the connection pool
            connectionUtils.getThreadConnection().close();
            // Unbind thread
            connectionUtils.removeThreadConnection();
        }

        return proceed;
    }
}

Tags: Java Spring AOP RESTful

Posted on Mon, 27 Sep 2021 05:14:00 -0400 by PyraX