Spring learning day 3: AOP?

Posted by furiousweebee on Tue, 12 Oct 2021 02:24:09 +0200

Problems in transfer operation

Suppose we want to implement a transfer operation, we add a transfer method in IAccountService (business layer interface of the account),

/**
     * transfer accounts
     * @param sourceName    Name of transfer out merchant
     * @param targetName    Transfer in account name
     * @param money         Transfer amount
     */
    void transfer(String sourceName,String targetName,float money);

Then add the implementation of the method to its implementation class

    @Override
    public void transfer(String sourceName, String targetName, float money) {
        //1. Query transfer out account by name
        Account source = dao.findAccountByName(sourceName);
        //2. Query transfer in account by name
        Account target = dao.findAccountByName(targetName);
        //3. Deduction from transfer out account
        source.setMoney(source.getMoney()-money);
        //4. Transfer in account plus money
        target.setMoney(target.getMoney()+money);
        //5. Update transfer out account
        dao.updateAccount(source);
        //6. Update transfer in account
        dao.updateAccount(target);
    }

Add test method

package com.spring.test;

import com.spring.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountservicImpl {
    @Autowired
    private IAccountService service;

    /**
     * Test transfer
     */
    @Test
    public void testTransfer() {
        service.transfer("aaa","bbb",100);
    }
}

At this time, the operation can be executed normally

But at this time, we have to do something in the implementation of the method

    @Override
    public void transfer(String sourceName, String targetName, float money) {
        //1. Query transfer out account by name
        Account source = dao.findAccountByName(sourceName);
        //2. Query transfer in account by name
        Account target = dao.findAccountByName(targetName);
        //3. Deduction from transfer out account
        source.setMoney(source.getMoney()-money);
        //4. Transfer in account plus money
        target.setMoney(target.getMoney()+money);
        //5. Update transfer out account
        dao.updateAccount(source);
        //Make trouble
        int a=1/0;
        //6. Update transfer in account
        dao.updateAccount(target);
    }

At this time, an error will be reported during the operation, and when you check the database, you will find that the money has been deducted, but the transferred account has not been received.
After the teacher's analysis:
We need to encapsulate these operations into a transaction (modify the statement if successful, and roll back if unsuccessful)

Preliminary problem solving

Let's solve this problem:
First, we need to create a ConnectionUtils to implement the binding between thread and connection:

import javax.sql.DataSource;
import java.sql.Connection;

/**
 * Connection tool class
 * Implement thread binding
 * @author 28985
 */
public class ConnectionUtils {
    private ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();

    private DataSource source;

    public void setSource(DataSource source) {
        this.source = source;
    }

    public Connection getThreadConnection(){
        try {
            //Get connection from threadlocal
            Connection connection = t1.get();
            //Determine whether there is a connection on the thread
            if (connection==null){
                //Get the connection from the data source, bind it with the thread and store it in ThreadLocal
                connection=source.getConnection();
                t1.set(connection);
            }
            return connection;
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public void removeConnection(){
        t1.remove();
    }
}

The details of ThreadLocal are as follows: https://www.jianshu.com/p/6fc3bba12f38
After that, we need to create a transaction related class to provide us with various transaction operation methods TransactionManage

/**
 * Transaction related
 * @author 28985
 */
public class TransactionManage {

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * Open transaction
     */
    public void openTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }

    }

    /**
     * Commit transaction
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * Rollback transaction
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * Close transaction
     */
    public void release(){
        try {
            connectionUtils.getThreadConnection().close();//Return the connection back to the connection pool
            connectionUtils.removeConnection();//Unbind threads and connections
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }
    }

}

Then you can make adjustments to the business layer
Its basic structure is:

	try {
            //1 start transaction
            //2 perform the operation
            //3 commit transaction
            //4 return results
        }
        catch (Exception e){
            //5 rollback operation
        }
        finally {
            //6 release the connection
        }

So you have the following code

/**
 * Business layer implementation class of account
 * Transaction control should be in the business layer
 * @author 28985
 */
public class IAccountServiceImpl implements IAccountService {
    private IAccountDao dao;
    private TransactionManage manage;

    public void setManage(TransactionManage manage) {
        this.manage = manage;
    }

    public IAccountDao getDao() {
        return dao;
    }

    public void setDao(IAccountDao dao) {
        this.dao = dao;
    }

    public IAccountServiceImpl(IAccountDao dao) {
        this.dao = dao;
    }

    public IAccountServiceImpl() {
    }

    @Override
    public List<Account> findAllAccount() {
        try {
            //1 start transaction
            manage.openTransaction();
            //2 perform the operation
            List<Account> allAccount = dao.findAllAccount();
            //3 commit transaction
            manage.commit();
            //4 return results
            return allAccount;
        }
        catch (Exception e){
            //5 rollback operation
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 release the connection
            manage.release();
        }
    }

    @Override
    public Account findAccountById(Integer id) {
        try {
            //1 start transaction
            manage.openTransaction();
            //2 perform the operation
            Account account = dao.findAccountById(id);
            //3 commit transaction
            manage.commit();
            //4 return results
            return account;
        }
        catch (Exception e){
            //5 rollback operation
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 release the connection
            manage.release();
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            //1 start transaction
            manage.openTransaction();
            //2 perform the operation
            dao.saveAccount(account);
            //3 commit transaction
            manage.commit();
        }
        catch (Exception e){
            //5 rollback operation
            manage.rollback();
        }
        finally {
            //6 release the connection
            manage.release();
        }

    }

    @Override
    public void updateAccount(Account account) {
        try {
            //1 start transaction
            manage.openTransaction();
            //2 perform the operation
            dao.updateAccount(account);
            //3 commit transaction
            manage.commit();
        }
        catch (Exception e){
            //5 rollback operation
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 release the connection
            manage.release();
        }

    }

    @Override
    public void delAccount(int id) {
        try {
            //1 start transaction
            manage.openTransaction();
            //2 perform the operation
            dao.delAccount(id);
            //3 commit transaction
            manage.commit();
        }
        catch (Exception e){
            //5 rollback operation
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 release the connection
            manage.release();
        }

    }

    @Override
    public void transfer(String sourceName, String targetName, float money) {
        try {
            //1 start transaction
            manage.openTransaction();
            //2 perform the operation
            //2.1. Query transfer out account by name
            Account source = dao.findAccountByName(sourceName);
            //2.2. Query transfer in account by name
            Account target = dao.findAccountByName(targetName);
            //2.3. Deduction from transfer out account
            source.setMoney(source.getMoney()-money);
            //2.4. Transfer in account plus money
            target.setMoney(target.getMoney()+money);
            //2.5. Update transfer out account
            dao.updateAccount(source);
            //2. Make trouble
            int a=1/0;
            //2.6. Update transfer in account
            dao.updateAccount(target);
            //3 commit transaction
            manage.commit();
        }
        catch (Exception e){
            //5 rollback operation
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 release the connection
            manage.release();
        }

    }

}

So we can find that the Connection has been transferred from QueryRunner to ConnectionUtils
Therefore, for each runner. Query ("select * from account", new beanlisthandler < account > (account. Class));
Change to runner. Query (connectionutil. Getthreadconnection(), "select * from account", new beanlisthandler < account > (account. Class));
Namely:

/**
 * Persistence layer implementation class of account
 * @author 28985
 */
public class AccountDaoImpl implements IAccountDao {
    private QueryRunner runner;

    private ConnectionUtils connectionUtil;

    public void setConnectionUtil(ConnectionUtils connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    @Override
    public List<Account> findAllAccount() {
        try {
            return runner.query(connectionUtil.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountById(Integer id) {
        try {
            return runner.query(connectionUtil.getThreadConnection(),"select * from account where id ="+id,new BeanHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            runner.update(connectionUtil.getThreadConnection(),"insert into account(name,money) value(?,?)",account.getName(),account.getMoney());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(Account account) {
        try {
            runner.update(connectionUtil.getThreadConnection(),"update account set name=?,money=? where id = ?",account.getName(),account.getMoney(),account.getId());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void delAccount(int id) {
        try {
            runner.update(connectionUtil.getThreadConnection(),"delete from account where id = ?",id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Account findAccountByName(String name) {
        try {
            List<Account> accounts=runner.query(connectionUtil.getThreadConnection(),"select * from account where name = ?",new BeanListHandler<Account>(Account.class),name);
            if (accounts.size()==0||accounts == null){
                return null;
            }
            if (accounts.size()>1){
                throw new RuntimeException("There is a problem with the result set");
            }
            return accounts.get(0);


        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

Then comes the 'spring' configuration file
Note that there is no need to inject datasource into QueryRunner

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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">
    <!--Configure business layer-->
    <bean id="accountService" class="com.spring.service.impl.IAccountServiceImpl">
        <property name="dao" ref="accountDao"/>
        <property name="manage" ref="transactionManage"/>
    </bean>

    <!--Configure persistence layer-->
    <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
        <property name="runner" ref="runner"/>
        <property name="connectionUtil" ref="connectionUtils"></property>
    </bean>
    
    <!--to configure runner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--        <constructor-arg name="ds" ref="ds"/>   At this time, it is no longer necessary to pass QueryRunner The connection has been handed over to Utils To manage-->
    </bean>
    <!--to configure DataSource((data source)-->
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="root"/>
        <property name="password" value="adminadmin"/>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_test"/>
    </bean>

<!--    Configure connection tool class connection and thread binding implementation class-->
    <bean id="connectionUtils" class="com.spring.utils.ConnectionUtils">
        <property name="source" ref="ds"></property>
    </bean>

<!--    Configure transaction related-->
    <bean id="transactionManage" class="com.spring.utils.TransactionManage">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

After running the previous transfer method, you will find that the transaction works and the rollback is successful
But there is a problem here. The code is very bloated and the reuse rate is very low, so we will use dynamic agent to solve these problems

Dynamic agent

Features: bytecode (class) files are created with use and loaded with use
Function: enhance the method without modifying the implementation class source code
    implement the method when there is no implementation class
Classification:
    interface based dynamic proxy
    subclass based dynamic proxy
https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984#0

Interface based dynamic agent

Class involved: Proxy
Provider: jdk official
How to create a proxy object:
    newProxyInstance method in Proxy class
Requirements for creating proxy objects:
    the proxied object implements at least one interface, otherwise it cannot be used
Parameters of newProxyInstance method:
    ClassLoader class loader
       load the bytecode of the proxy object and use the same class loader as the proxy object
    Class [] bytecode array
       let the proxy object and the proxy object have the same method (the interface of the proxy object)
InvocationHandler allows us to write how to proxy. Generally, we write an implementation class of the interface, which is usually an internal class
Example:
Manufacturer's interface:

package com.spring.proxy.producers;

/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/2 9:56
 */
public interface IProducer {
    /**
     * purchase
     * @param money Money received by the manufacturer
     */
    public void buy(float money);

    /**
     * After sale
     * @param money Money received by the manufacturer s
     */
    public void shouHou(float money);
}

Manufacturer implementation class

package com.spring.proxy.producers;

/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/2 10:00
 */
public class ProducerImpl implements IProducer{

    @Override
    public void buy(float money) {
        System.out.println("The goods have been sold and received"+money+"element");
    }

    @Override
    public void shouHou(float money) {
        System.out.println("Goods received after sale"+money+"element");
    }
}

Main function

    public static void main(String[] args) {
        ProducerImpl producer = new ProducerImpl();
        IProducer producer1 = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
        /**
          *Any interface method that executes the proxied object will pass through this method
       	  */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                float money = (float) args[0];
                if ("buy".equals(method.getName())) {
                    money *= 0.8f;//Manufacturer's draw 0.2
                }
                return method.invoke(producer,money);
            }
        });
        producer1.buy(10000);
    }

The running result is

This indicates that the producer1 object has been proxied

Subclass based dynamic agent

Category involved: Enhancer
Provider: cglib
How to create a proxy object:
    creat e method in Enhancer class
Requirements for creating proxy objects:
    the proxied object cannot be the final class
Parameters of newProxyInstance method:
    ClassLoader class loader
       load the bytecode of the proxy object and use the same class loader as the proxy object
    Class [] bytecode array
       let the proxy object and the proxy object have the same method (the interface of the proxy object)
InvocationHandler allows us to write how to proxy. Generally, we write an implementation class of the interface, which is usually an internal class

Because cglib is used, we first import the dependency of cglib

    <dependencies>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
    </dependencies>

Change main code

public class Client {
    public static void main(String[] args) {
        ProducerImpl producer = new ProducerImpl();
        IProducer cglibproducer = (IProducer) Enhancer.create(ProducerImpl.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                float money = (float) objects[0];
                if ("buy".equals(method.getName())) {
                    money *= 0.8f;//Manufacturer's draw 0.2
                }
                return method.invoke(producer,money);
            }
        });
        cglibproducer.buy(1000);
    }
}

At this point, you can run and get the results:

Using dynamic agent to solve the problem of bloated transaction control code

Let's delete all the transaction control codes in the original service and change them to their original appearance

package com.spring.service.impl;

import com.spring.dao.IAccountDao;
import com.spring.domain.Account;
import com.spring.service.IAccountService;
import com.spring.utils.TransactionManage;

import java.util.List;

/**
 * Business layer implementation class of account
 * Transaction control should be in the business layer
 * @author 28985
 */
public class IAccountServiceImpl implements IAccountService {
    private IAccountDao dao;

    public IAccountDao getDao() {
        return dao;
    }

    public void setDao(IAccountDao dao) {
        this.dao = dao;
    }

    public IAccountServiceImpl(IAccountDao dao) {
        this.dao = dao;
    }

    public IAccountServiceImpl() {
    }

    @Override
    public List<Account> findAllAccount() {
        return dao.findAllAccount();
    }

    @Override
    public Account findAccountById(Integer id) {
        return dao.findAccountById(id);
    }

    @Override
    public void saveAccount(Account account) {
        dao.saveAccount(account);
    }

    @Override
    public void updateAccount(Account account) {
        dao.updateAccount(account);
    }

    @Override
    public void delAccount(int id) {
        dao.delAccount(id);
    }

    @Override
    public void transfer(String sourceName, String targetName, float money) {
        //2.1. Query transfer out account by name
        Account source = dao.findAccountByName(sourceName);
        //2.2. Query transfer in account by name
        Account target = dao.findAccountByName(targetName);
        //2.3. Deduction from transfer out account
        source.setMoney(source.getMoney()-money);
        //2.4. Transfer in account plus money
        target.setMoney(target.getMoney()+money);
        //2.5. Update transfer out account
        dao.updateAccount(source);
        //2. Make trouble
//        int a=1/0;
        //2.6. Update transfer in account
        dao.updateAccount(target);
    }

}

At this point, create another BeanFactory factory class
It is used to create the proxy object of AccountService

/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/2 12:19
 */
public class BeanFactory {

    private IAccountService accountService;

    private TransactionManage manage;

    public void setManage(TransactionManage manage) {
        this.manage = manage;
    }

    public void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    /**
     * Gets the proxy object of the service
     * @return
     */
    public IAccountService getAccountService() {
        IAccountService proxyAccountService=(IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtObject;
                        try {
                            //1 start transaction
                            manage.openTransaction();
                            //2 perform the operation
                            rtObject = method.invoke(accountService,args);
                            //3 commit transaction
                            manage.commit();
                            //4 return results
                            return rtObject;
                        }
                        catch (Exception e){
                            //5 rollback operation
                            manage.rollback();
                            throw new RuntimeException(e);
                        }
                        finally {
                            //6 release the connection
                            manage.release();
                        }
                    }
                });
        return proxyAccountService;
    }
}

Transaction related code has been enhanced in the agent
Then inject manage and accountService for beanfactory in bean.xml
And produce an accountService with an agent added

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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">
<!--    Factory production agent AccountService-->
    <bean id="proxyService" factory-bean="beanFactory" factory-method="getAccountService"/>
<!--    Configuration factory-->
    <bean id="beanFactory" class="com.spring.factory.BeanFactory">
        <property name="accountService" ref="accountService"/>
        <property name="manage" ref="transactionManage"/>
    </bean>

    <!--Configure business layer-->
    <bean id="accountService" class="com.spring.service.impl.IAccountServiceImpl">
        <property name="dao" ref="accountDao"/>
    </bean>

    <!--Configure persistence layer-->
    <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
        <property name="runner" ref="runner"/>
        <property name="connectionUtil" ref="connectionUtils"></property>
    </bean>
    
    <!--to configure runner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--        <constructor-arg name="ds" ref="ds"/>   At this time, it is no longer necessary to pass QueryRunner The connection has been handed over to Utils To manage-->
    </bean>
    <!--to configure DataSource((data source)-->
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="root"/>
        <property name="password" value="adminadmin"/>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_test"/>
    </bean>

<!--    Configure connection tool class connection and thread binding implementation class-->
    <bean id="connectionUtils" class="com.spring.utils.ConnectionUtils">
        <property name="source" ref="ds"></property>
    </bean>

<!--    Configure transaction related-->
    <bean id="transactionManage" class="com.spring.utils.TransactionManage">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

Run test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountservicImpl {
    @Qualifier("proxyService")
    @Autowired
    private IAccountService service;

    /**
     * Test transfer
     */
    @Test
    public void testTransfer() {
        service.transfer("aaa","bbb",100);
    }
}

Note that a note is added here: @ Qualifier("proxyService") is because the proxy object produced by the factory and the beanaccountService configured in the business layer meet the conditions of Autowired. At this time, we need to specify it

So far, using dynamic agents to solve the problem of transaction control is over, but we find that the configuration file is very cumbersome, so we need to launch AOP in spring

AOP

What is AOP

The Chinese meaning of AOP is a technology for aspect oriented programming, which realizes the unified maintenance of program functions through precompiled mode and dynamic agent during operation. AOP is the continuation of OOP, a hot spot in software development, an important content in Spring framework, and a derivative paradigm of functional programming. 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-------- Baidu Encyclopedia

Role and advantages of AOP

Function: during the operation of the program, the source code is not modified, and the existing methods are enhanced
Advantages:
Reduce duplicate codes
Improve development efficiency
Convenient maintenance

AOP in spring

AOP related terms (understand)

Joinpoint:
The so-called connection points refer to those intercepted points. In spring, these points refer to methods, because spring only supports connection points of method type
pointout:
The so-called pointcut refers to the definition of which joinpoints we want to intercept (in this case, it refers to the methods that need transaction control, and the connection points that do not need transaction control cannot be called pointcuts)
Advice:
Notification refers to what to do after intercepting the Joinpoint
Notification types: pre notification, post notification, exception notification, final notification, and surround notification

Introduction:
A special notification that dynamically adds some methods to a class during runtime without modifying the code
Target (target object)
The target object of the proxy (the proxied object)
Weaving:
Refers to the process of applying enhancements to the target object to create a new proxy object
srping uses dynamic proxy weaving, AspectJ uses compile time weaving and class loading time weaving
Proxy:
After a class is woven and enhanced by AOP, a result proxy class is generated
Aspect:
It's a combination of pointcuts and notifications

AOP based on xml

Create business layer interface and implementation class

package com.spring.services;

/**
 * Business layer interface of account
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/5 20:13
 */
public interface IAccountService {
    /**
     * Simulated save account
     */
    public void save();

    /**
     * Simulation update account
     * @param i
     */
    public void update(int i);

    /**
     * Simulate account deletion
     * @return
     */
    public int delete();
}

import com.spring.services.IAccountService;

/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/5 20:27
 */
public class AccountServiceImpl implements IAccountService {
    @Override
    public void save() {
        System.out.println("preservation");
    }

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

    @Override
    public int delete() {
        System.out.println("delete");
        return 0;
    }
}

Provide common methods when creating a logger class

package com.spring.utils;

/**Logging tool, which provides public code
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/5 20:29
 */
public class Logger {
    /**
     * Pre message
     */
    public void beforePrint(){
        System.out.println("Pre message");
    }
    /**
     * Post message
     */
    public void afterPrint(){
        System.out.println("Post message");
    }
    /**
     * Exception message
     */
    public void tryPrint(){
        System.out.println("Exception message");
    }
    /**
     * news
     */
    public void finallyPrint(){
        System.out.println("Final message");
    }
}

Configuration xml file

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- to configure srping of Ioc,hold service Object configuration-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>


    <!-- to configure Logger class -->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>

    <!--to configure AOP-->
    <aop:config>
        <!-- Configure pointcut expressions id Property specifies the unique identity of the expression. expression Property specifies the content of the expression
              This label is written in aop:aspect The inside of the label can only be used in the current section.
              It can also be written in aop:aspect Outside, all sections are now available
          -->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--Configure section -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- Configure pre notification: executed before the pointcut method is executed
            <aop:before method="beforePrint" pointcut-ref="pt1" ></aop:before>-->

            <!-- Configure post notification: the value after the pointcut method executes normally. It and exception notification can always be executed only once
            <aop:after-returning method="afterPrint" pointcut-ref="pt1"></aop:after-returning>-->

            <!-- Configure exception notification: executed after the exception is generated during the execution of pointcut methods. It and post notification can always execute only one
            <aop:after-throwing method="tryPrint" pointcut-ref="pt1"></aop:after-throwing>-->

            <!-- Configure final notification: the pointcut method executes after it whether it executes normally or not
            <aop:after method="finallyPrint" pointcut-ref="pt1"></aop:after>-->
        </aop:aspect>
    </aop:config>

</beans>

Write main method:

import com.spring.services.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/6 15:50
 */
public class AopTest {
    public static void main(String[] args) {
        ApplicationContext appletContext = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService)appletContext.getBean("accountService");
        as.save();
        as.update(1);
        as.delete();
    }
}

Operation results:

Pre message
 preservation
 Post message
 Final message
 Pre message
 to update
 Post message
 Final message
 Pre message
 delete
 Post message
 Final message

Let's change the delete method

    @Override
    public int delete() {
        System.out.println("delete");
        int k = 5/0;
        return 0;
    }

The result is:

Pre message
 preservation
 Post message
 Final message
 Pre message
 to update
 Post message
 Final message
 Pre message
 delete
 Exception message
 Final message
Exception in thread "main" java.lang.ArithmeticException: / by zero

At this point, we can conclude that the exception message and the post message conflict, and there can only be one

Annotation based AOP

The xml configuration of annotation based aop is very simple
You only need to specify the package to scan and turn on spring's support for aop annotations
The configuration file is as follows

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

<!--    Specify the packages to scan-->
    <context:component-scan base-package="com.spring"/>
<!--    open spring yes aop Annotation support-->
    <aop:aspectj-autoproxy/>
</beans>

First, implement the previously implemented in xml

    <!-- to configure srping of Ioc,hold service Object configuration-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    <!-- to configure Logger class -->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>

These two lines of code
We use annotations instead
Just add @ Service and @ Component annotations to AccountServiceImpl and Logger
Next is the aop part

The first sentence has no practical effect. There is no need to configure here
In the second sentence, pointcut, we use the pointcut annotation instead to define an empty function

    @Pointcut("execution(* com.spring.services.impl.*.*(..))")
    public void pointcut(){}

The third sentence is the statement
We need to add the @ Aspect annotation to the class

@Aspect

The following corresponding notes

@Before("pointcut()")
@AfterReturning("pointcut()")
@AfterThrowing("pointcut()")
@After("pointcut()")
@Around("pointcut()")

So the logger is changed to

package com.spring.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**Logging tool, which provides public code
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/5 20:29
 */
@Component
@Aspect//Indicates that the current class is a faceted class
public class Logger {
    @Pointcut("execution(* com.spring.services.impl.*.*(..))")
    public void pointcut(){}

    /**
     * Pre message
     */
    @Before("pointcut()")
    public void beforePrint(){
        System.out.println("Pre message");
    }
    /**
     * Post message
     */
    @AfterReturning("pointcut()")
    public void afterPrint(){
        System.out.println("Post message");
    }
    /**
     * Exception message
     */
    @AfterThrowing("pointcut()")
    public void tryPrint(){
        System.out.println("Exception message");
    }
    /**
     * news
     */
    @After("pointcut()")
    public void finallyPrint(){
        System.out.println("Final message");
    }

    /**
     * Around Advice 
     * @param pjp
     */
//    @Around("pointcut()")
    public void aroundPrint(ProceedingJoinPoint pjp){
        try {
            System.out.println("Front");
            Object proceed = pjp.proceed(pjp.getArgs());
            System.out.println("Post");
        } catch (Throwable e) {
            System.out.println("abnormal");
            throw new RuntimeException(e);
        }finally {
            System.out.println("final");
        }
    }
}

Finally, you can get the same result as the xml configuration. Note that in some versions of spring 5.0. X, the post message and final message are sequential bug s

Do not use xml at all

@Configuration
@ComponentScan(basePackages="com.spring")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

Topics: Java Spring Spring Boot