Spring framework - Part 3

Chapter one: dynamic agent

1.1 - transfer case

1.1.1- demand

Account A transfers 100 yuan to account B

  • Account A minus 100
  • Account B plus 100

1.1.2 - database script

CREATE DATABASE  IF NOT EXISTS db1
USE db1
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(40),
money FLOAT
)CHARACTER SET utf8 COLLATE utf8_general_ci;
INSERT INTO account(NAME,money) VALUES('A',1000);
INSERT INTO account(NAME,money) VALUES('B',1000);

1.1.3 - environment construction

Maven introduces dependency package

  <dependencies>
    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--dbUtils-->
    <dependency>
      <groupId>commons-dbutils</groupId>
      <artifactId>commons-dbutils</artifactId>
      <version>1.4</version>
    </dependency>
    <!--c3p0-->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>
    <!--spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
    <!--spring-test-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.26</version>
    </dependency>
  </dependencies>

Entity class

/*
	Account entity
*/
public class Account {
  private int id;
  private String name;
  private float money;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public float getMoney() {
    return money;
  }

  public void setMoney(float money) {
    this.money = money;
  }

  @Override
  public String toString() {
    return "Account{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", money=" + money +
            '}';
  }
}

Persistence layer interface and implementation class

Interface

public interface IAccountDao {

  /**
   * Query a data according to id
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * To update
   * @param account
   */
  void update(Account account);

}

Implementation class

public class AccountDaoImpl implements IAccountDao {
  private QueryRunner runner = null;
  public void setRunner(QueryRunner runner) {
    this.runner = runner;
  }

  @Override
  public Account findOne(int id) {
    try{
      String sql = "select * from account where id=?";
      return runner.query(sql,new BeanHandler<>(Account.class),id);
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }


  @Override
  public void update(Account account) {
    try{
      String sql = "UPDATE account SET NAME=?,money=? WHERE id=?";
      runner.update(sql,account.getName(),account.getMoney(),account.getId());
    }catch (Exception e){
      e.printStackTrace();
    }
  }


}

Business layer interface and implementation class

Interface

public interface IAccountServices {
  /**
   * Query a data according to id
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * To update
   * @param account
   */
  void update(Account account);


  /**
   * [Transfer]
   * @param idOne Transfer account
   * @param idTwo Accounts receivable
   * @param money Transfer money
   */
  void transfer(int idOne,int idTwo,float money);
}

Implementation class

public class AccountServicesImpl implements IAccountServices {
  private IAccountDao dao = null;
  public void setDao(IAccountDao dao) {
    this.dao = dao;
  }
  @Override
  public Account findOne(int id) {
    return dao.findOne(id);
  }
  @Override
  public void update(Account account) {
    dao.update(account);
  }
  @Override
  public void transfer(int idOne, int idTwo, float money) {
     // Find transfer account A by id
      Account one = dao.findOne(idOne);
      // Find collection account B according to id
      Account two = dao.findOne(idTwo);
      // Decrease in transfer account
      one.setMoney(one.getMoney()-money);
      // Add money to collection account
      two.setMoney(two.getMoney()+money);
      // Update transfer account
      dao.update(one);
      // Update collection account
      dao.update(two);
  }

}

IOC configuration of Spring

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--To configure IAccountService object-->
    <bean id="accountService" class="cn.lpl666.services.impl.AccountServicesImpl">
        <property name="dao" ref="accountDao"></property>
    </bean>
    <!--To configure IAccountDao object-->
    <bean id="accountDao" class="cn.lpl666.dao.impl.AccountDaoImpl">
        <property name="runner" ref="queryRunner"></property>
    </bean>
    <!--To configure QueryRunner-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--Inject data source-->
        <constructor-arg name="ds" ref="c3p0"></constructor-arg>
    </bean>
    <!--Configure data sources-->
    <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--Inject necessary information to connect to database-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>

1.1.4- test

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class ClientTest {
  // Get IAccountServices object
  @Autowired
  private IAccountServices services = null;

  /**
   * Transfer test
   */
  @Test
  public void transfer(){
    services.transfer(1,2,100);
  }
}

1.1.5 - problem analysis

Problem: if there is an exception in the process of transfer (such as sudden downtime), it may cause database data error (dirty data will appear in the database)

Exception pseudo code:

  @Override
  public void transfer(int idOne, int idTwo, float money) {
     // Find transfer account A by id
      Account one = dao.findOne(idOne);
      // Find collection account B according to id
      Account two = dao.findOne(idTwo);
      // Decrease in transfer account
      one.setMoney(one.getMoney()-money);
      // Add money to collection account
      two.setMoney(two.getMoney()+money);
      // Update transfer account
      dao.update(one);
      int i = 10 / 0; // [exceptions will be generated here]
      // Update collection account
      dao.update(two);
  }
// Result: account A reduced the money, but account B did not increase the money

Reason:

  • This business operates the database many times, but not synchronously.
  • Synchronous operation means that if there is an exception, all database operations in this business will fail, so dirty data will not appear in the database.

Solution: transactional operations

1.2 - transaction transfer case

The requirements and database scripts are the same as above

1.2.1 - environment construction

Same for entity and Maven project

Database connection tool class

Objective to ensure the consistency of database connection objects in the same thread.

/**
 * Tool class of connection, which is used to get a connection from the data source and implement the binding with the thread
 */
public class ConnectionUtils {
  private ThreadLocal<Connection> td = new ThreadLocal<>();
  private DataSource dataSource;
  public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  /**
   * Get the connection object on the current thread
   * @return
   */
  public Connection getThreadConnection(){
    try {
      // Get connection from current thread
      Connection connection = td.get();
      // Check for presence
      if(connection==null){
        // If not, get a connection from the data source object
        connection = dataSource.getConnection();
        // Save to current thread
        td.set(connection);
      }
      return connection;
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }
  /**
   * Unbind connection object and thread
   */
  public void removeConnection(){
    td.remove();
  }
}

Transaction management tool class

Purpose, encapsulate transaction operations (start transaction, commit transaction, rollback transaction)

/**
 * Transaction management related tool class, which includes: open transaction, commit transaction, rollback transaction and release connection
 */
public class TransactionManager {
  private ConnectionUtils connectionUtils;
  public void setConnectionUtils(ConnectionUtils connectionUtils) {
    this.connectionUtils = connectionUtils;
  }

  /**
   * Open transaction
   */
  public void beginTransaction(){
    try {
      connectionUtils.getThreadConnection().setAutoCommit(false);
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }

  /**
   * Submission of affairs
   */
  public void commit(){
    try {
      connectionUtils.getThreadConnection().commit();
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }

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

  /**
   * Release connection
   */
  public void  release(){
    try {
      connectionUtils.getThreadConnection().close();
      connectionUtils.removeConnection();
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }
}

Persistence layer interface and implementation class

Interface

public interface IAccountDao {

  /**
   * Query a data according to id
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * To update
   * @param account
   */
  void update(Account account);

}

Implementation class

public class AccountDaoImpl implements IAccountDao {
  private QueryRunner runner = null;
  public void setRunner(QueryRunner runner) {
    this.runner = runner;
  }
  // Database connection object
  private ConnectionUtils connectionUtils;
  public void setConnectionUtils(ConnectionUtils connectionUtils) {
    this.connectionUtils = connectionUtils;
  }


  @Override
  public Account findOne(int id) {
    try{
      String sql = "select * from account where id=?";
      return runner.query(connectionUtils.getThreadConnection(),sql,new BeanHandler<>(Account.class),id);
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }


  @Override
  public void update(Account account) {
    try{
      String sql = "UPDATE account SET NAME=?,money=? WHERE id=?";
      runner.update(connectionUtils.getThreadConnection(),sql,account.getName(),account.getMoney(),account.getId());
    }catch (Exception e){
      e.printStackTrace();
    }
  }


}

Business layer interface and implementation class

Interface

public interface IAccountServices {
  /**
   * Query a data according to id
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * To update
   * @param account
   */
  void update(Account account);


  /**
   * Transfer accounts
   * @param idOne Transfer account
   * @param idTwo Accounts receivable
   * @param money Transfer money
   */
  void transfer(int idOne,int idTwo,float money);
}

Implementation class

public class AccountServicesImpl implements IAccountServices {
  private IAccountDao dao = null;
  public void setDao(IAccountDao dao) {
    this.dao = dao;
  }
  // Transaction management object
  private TransactionManager transactionManager = null;
  public void setTransactionManager(TransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }

  @Override
  public Account findOne(int id) { ;
    try{
      transactionManager.beginTransaction();
      Account account =  dao.findOne(id);
      transactionManager.commit();
      return account;
    }catch (Exception e){
      transactionManager.rollback();
      e.printStackTrace();
    }finally {
      transactionManager.release();
    }
    return null;
  }


  @Override
  public void update(Account account) {
    try{
      transactionManager.beginTransaction();
      dao.update(account);
      transactionManager.commit();
    }catch (Exception e){
      transactionManager.rollback();
      e.printStackTrace();
    }finally {
      transactionManager.release();
    }
  }


  @Override
  public void transfer(int idOne, int idTwo, float money) {
    try{
      // Open transaction
      transactionManager.beginTransaction();
      // Find transfer account by id
      Account one = dao.findOne(idOne);
      // Find collection account by id
      Account two = dao.findOne(idTwo);
      // Decrease in transfer account
      one.setMoney(one.getMoney()-money);
      // Add money to collection account
      two.setMoney(two.getMoney()+money);
      // Update transfer account
      dao.update(one);
      //int i = 2/0; / / exception, transfer failed, transaction rollback required
      // Update collection account
      dao.update(two);
      // Submission of affairs
      transactionManager.commit();
    }catch (Exception e){
      // Transaction rollback
      transactionManager.rollback();
      e.printStackTrace();
    }finally {
      // Release connection
      transactionManager.release();
    }
  }

}

IOC configuration of Spring framework

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--To configure IAccountService object-->
    <bean id="accountService" class="cn.lpl666.services.impl.AccountServicesImpl">
        <property name="dao" ref="accountDao"></property>
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>
    <!--To configure IAccountDao object-->
    <bean id="accountDao" class="cn.lpl666.dao.impl.AccountDaoImpl">
        <property name="runner" ref="queryRunner"></property>
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
    <!--To configure QueryRunner-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
    <!--Configure data sources-->
    <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--Inject necessary information to connect to database-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!--To configure ConnectionUtils-->
    <bean id="connectionUtils" class="cn.lpl666.utils.ConnectionUtils">
        <!--Inject data source-->
        <property name="dataSource" ref="c3p0"></property>
    </bean>
    <!--To configure TransactionManager-->
    <bean id="transactionManager" class="cn.lpl666.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

1.2.2- test

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class ClientTest {
  // Get IAccountServices object
  @Autowired
  private IAccountServices services = null;

  /**
   * Transfer test
   */
  @Test
  public void transfer(){
    services.transfer(1,2,100);
  }
}

1.2.3 - problem analysis

  • Problem: in the business layer, transactions are used in multiple businesses. At this time, the transaction operations are repeated and the code is bloated.
  • Reason: duplicate transaction operation
  • Solution: use dynamic agent enhancement method to isolate transaction operations in business methods and simplify business code.

1.3 - overview of dynamic agents

Principle: use reflection mechanism to create proxy class at runtime.

Features: bytecode is created as soon as it is used.

Function: it can enhance the method without modifying the source code.

Classification: ① - dynamic agent based on interface; ② dynamic agent based on subclass

1.4 - dynamic agent based on interface

####1.4.1 - General

  • Class involved: Proxy
  • Provider: JDK official
  • How to create a Proxy object: using the newProxyInstance method in the Proxy class
  • Requirements for creating proxy object: the class to be proxy needs to implement at least one interface, otherwise it cannot be used.
  • Parameters of the newProxyInstance method
    • Parameter 1: ClassLoader loader, classloader, which is used to load the bytecode of the proxy object and uses the same loader as the proxy object
    • Parameter 2: class <? > [] interfaces, an array of bytecodes, is used to make the proxy object and the proxy object have the same method.
    • Parameter 3: InvocationHandler enables processing to provide enhanced code.
      • How to write the proxy code is generally to write an implementation class of the interface. In general, anonymous internal classes are used, and the classes using the interface are written by who and who

1.4.2 - code demonstration

demand

To enhance the sellProduct method in the Producer object method (on the basis of not modifying the source code), the enhancement business is to deduct 20% of the cost.

Code demo

IProducer interface

public interface IProducer {
  /**
   * promoting products
   * @param money
   */
  void sellProduct(double money);

  /**
   * After-sale service
   * @param money
   */
  void sellService(double money);
}

Producer implementation class

package cn.lpl666.proxy;
public class Producer implements IProducer {

    @Override
    public void sellProduct(double money) {
        System.out.println("Products sold: consumption"+money);
    }

    @Override
    public void sellService(double money) {
        System.out.println("After sales service: consumption" + money);
    }
}

Test implementation enhancements

package cn.lpl666.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Client {
  public static void main(String[] args) {
    final Producer producer = new Producer();
    // Create proxy object
    IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
              /**
               * Function: this method is used to execute any proxy object interface method
               * @param proxy   References to proxy objects
               * @param method  Method executed
               * @param args    Parameters required for the current execution method
               * @return
               * @throws Throwable
               */
              @Override
              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object returnValue = null;
                double money = (double) args[0] * 0.8;
                if (method.getName().equals("sellProduct")) {
                  returnValue = method.invoke(producer, money);
                }
                return returnValue;
              }
            }
    );

    proxyProducer.sellProduct(10000);
  }
}

1.5 - dynamic agent based on subclass

1.5.1- overview

  • Classes involved: Enhancer
  • Provider: third party cglib Library
  • How to create a proxy object: using the Create method in the Enhancer class
  • Requirements for creating proxy object: the class to be proxy cannot be the final class
  • Parameters of create method:
    • Class, bytecode, used to specify the bytecode of the proxy object
    • Callback callback, callback, used to implement the enhancement method
      • How to write the proxy code is generally to write an implementation class of the interface (usually using the implementation class of its sub interface MethodInterceptor). In general, anonymous internal classes are used, and the classes using this interface are written by who and who

1.5.2 - code demonstration

Maven introduces cglib Library

  <dependencies>
    <!--cglib-->
    <!-- https://mvnrepository.com/artifact/cglib/cglib -->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>
  </dependencies>

Code

Class Producer

public class Producer  {
    
    public void sellProduct(double money) {
        System.out.println("Products sold: consumption"+money);
    }
    
    public void sellService(double money) {
        System.out.println("After sales service: consumption" + money);
    }
}

test

public class Client {
  public static void main(final String[] args) {
    final Producer producer = new Producer();
    Producer cglibProducer =(Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
      /**
       *
       * @param o  References to proxy objects
       * @param method  Method of execution
       * @param objects Parameters of the method executed
       * @param methodProxy  Proxy object for the current execution method
       * @return
       * @throws Throwable
       */
      @Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object returnValue = null;
        double money = (double) objects[0] * 0.8;
        if (method.getName().equals("sellProduct")) {
          returnValue = method.invoke(producer, money);
        }
        return returnValue;
      }
    });
    cglibProducer.sellProduct(14000);
  }
}

1.6 - dynamic agent simplified transfer case

Dynamic agent can be used to simplify the operation of the transfer case in 1.2 of this chapter due to the code bloated problem caused by repeated transactions

1.6.1 - Environmental Construction

Business layer implementation class code

It can be found that in the following business codes, there are only business operations, but no transaction operations. The business layer code becomes simple and clean and no longer overstaffed.

public class AccountServicesImpl implements IAccountServices {
  private IAccountDao dao = null;
  public void setDao(IAccountDao dao) {
    this.dao = dao;
  }

  @Override
  public Account findOne(int id) {
    return dao.findOne(id);
  }

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


  @Override
  public void transfer(int idOne, int idTwo, float money) {
// Find transfer account by id
    Account one = dao.findOne(idOne);
    // Find collection account by id
    Account two = dao.findOne(idTwo);
    // Decrease in transfer account
    one.setMoney(one.getMoney() - money);
    // Add money to collection account
    two.setMoney(two.getMoney() + money);
    // Update transfer account
    dao.update(one);
    // int i = 2/0; / / exception, transfer failed, transaction rollback required
    // Update collection account
    dao.update(two);
  }

}

Factory class, providing business layer objects

Because of the enhancement of methods in business layer objects

public class BeanFactory {
  // Account business layer object
  private IAccountServices accountServices;
  public final void setAccountServices(IAccountServices accountServices) {
    this.accountServices = accountServices;
  }
  // Transaction management object
  private TransactionManager transactionManager;
  public void setTransactionManager(TransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }
  // Provide the enhancement of creating account business object and calling object method internally
  public IAccountServices getAccountServices(){
    // Create the proxy object of IAccountServices and add the transaction operation when the business method executes
    return (IAccountServices) Proxy.newProxyInstance(accountServices.getClass().getClassLoader(), accountServices.getClass().getInterfaces(), new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnValue = null;
        try{
          // Open transaction
          transactionManager.beginTransaction();
          // Processing business
          returnValue=method.invoke(accountServices,args);
          // Submission of affairs
          transactionManager.commit();
        }catch (Exception e){
          // Transaction rollback
          transactionManager.rollback();
          e.printStackTrace();
        }finally {
          // Release connection
          transactionManager.release();
        }
        return returnValue;
      }
    });
  }
}

IOC configuration of Spring framework

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--To configure IAccountService object-->
    <bean id="factoryIAccountService" factory-bean="beanFactory" factory-method="getAccountServices"></bean>
    <!--To configure BeanFactory-->
    <bean id="beanFactory" class="cn.lpl666.factory.BeanFactory">
        <!--Inject transaction management object-->
        <property name="transactionManager" ref="transactionManager"></property>
        <!--injection AccountService object-->
        <property name="accountServices" ref="accountService"></property>
    </bean>
    <!--To configure IAccountService object-->
    <bean id="accountService" class="cn.lpl666.services.impl.AccountServicesImpl">
        <property name="dao" ref="accountDao"></property>
    </bean>
    <!--To configure IAccountDao object-->
    <bean id="accountDao" class="cn.lpl666.dao.impl.AccountDaoImpl">
        <property name="runner" ref="queryRunner"></property>
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
    <!--To configure QueryRunner-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
    <!--Configure data sources-->
    <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--Inject necessary information to connect to database-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!--To configure ConnectionUtils-->
    <bean id="connectionUtils" class="cn.lpl666.utils.ConnectionUtils">
        <!--Inject data source-->
        <property name="dataSource" ref="c3p0"></property>
    </bean>
    <!--To configure TransactionManager-->
    <bean id="transactionManager" class="cn.lpl666.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

1.6.2- test

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class ClientTest {
  // Get IAccountServices object
  @Autowired
  @Qualifier("factoryIAccountService")
  private IAccountServices services = null;

  /**
   * Transfer test
   */
  @Test
  public void transfer(){
    services.transfer(1,2,100);
  }
}

Chapter 2: AOP in Spring

2.1 - what is AOP

In the software industry, AOP is the abbreviation of Aspect Oriented Programming, which means: Face to face programming Through precompile A technique for unified maintenance of program functions by means of mode and runtime dynamic agents. AOP is OOP The continuation of is a hot spot in software development, as well as Spring An important part of the framework is Functional programming A derivative paradigm of. AOP can be used to isolate all parts of business logic, so that the Coupling degree Reduce, improve the reusability of the program, and improve the efficiency of development.

In short, it is to extract the repeated code of our program. When it needs to be executed, it uses the technology of dynamic agent to enhance our existing methods without modifying the source code.

2.2-difference between AOP and OOP

Although AOP and OOP are very similar in terms of words, they are two design ideas for different fields. OOP ( object-oriented programming )Abstract encapsulation of business process entities and their attributes and behaviors to achieve clearer and more efficient Logic unit Divide.

AOP is to extract the facets in the process of business processing. It is faced with a certain step or stage in the process of processing, so as to obtain the low level between the parts in the logic process Coupling The isolation effect of. There are essential differences between the two design ideas in objectives.

The above statement may be too theoretical, for example, for an employee Business entity Encapsulation is naturally the task of OOP/OOD. We can create an "Employee" class for it and encapsulate the "Employee" related properties and behaviors. It is impossible to encapsulate "Employee" with AOP design idea.

Similarly, the target area of AOP is to divide the action fragment "permission check". It's a bit different to encapsulate an action through OOD/OOP.

In other words, OOD/OOP is for noun domain, and AOP is for verb domain.

2.3-AOP related terms

  • Join point: the so-called join points refer to those intercepted points. In spring, these points refer to methods, because spring only supports connection points of method type.
  • Pointcut: a pointcut is a definition of which joinpoints we want to intercept.
  • Advice (notification / enhancement): the so-called notification refers to the notification that is the thing to be done after the Joinpoint is intercepted.
    • Notification types: pre notification, post notification, exception notification, final notification, and surround notification.
  • Introduction: introduction is a kind of special notice. Without modifying the class code, introduction can dynamically add some methods or fields to the class at runtime.
  • Target: the target object of the agent.
  • Weaving: refers to the process of applying enhancements to the target object to create a new proxy object. spring uses dynamic proxy weaving, while AspectJ uses compile time weaving and class load time weaving
  • Proxy: when a class is enhanced by AOP weaving, a result proxy class is generated.
  • Aspect: a combination of pointcuts and notifications.

2.4-AOP development steps

2.4.1 - development phase

  • Write core business code (development main line): most programmers do it, which requires familiarity with business requirements.
  • Extract the common code and make it into a notice. (at the end of the development phase): AOP programmers do it.
  • In the configuration file, declare the relationship between the pointcut and the notification, that is, the aspect. : AOP programmers.

2.4.2 - operation stage

The runtime is completed by the Spring framework

The Spring framework monitors the execution of pointcut methods. Once the pointcut method is monitored to run, 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 location of the proxy object to complete the complete code logic operation.

In spring, the framework will decide which dynamic proxy method to use according to whether the target class implements the interface.

2.5 - AOP based on XML configuration

2.5.1 - getting started

demand

Before the business layer method call, the log operation is implemented (without modifying the business layer source code)

Maven introduces dependency package

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>
    <!--spring-context-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
    <!-- [rg.aspectj/aspectjweaver] -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>

  </dependencies>

Business layer interface and implementation class

Interface

public interface IAccountService {
  /**
   * Simulated saving account
   */
  void saveAccount();

  /**
   * Simulated update account
   * @param i
   */
  void updateAccount(int i);

  /**
   * Delete account
   * @return
   */
  int  deleteAccount();
}

Implementation class

public class AccountServiceImpl implements IAccountService {
  @Override
  public void saveAccount() {
    System.out.println("Execution: account saved");
  }

  @Override
  public void updateAccount(int i) {
    System.out.println("Execution: updated account, account id: " + i);
  }

  @Override
  public int deleteAccount() {
    System.out.println("Execution: account deleted");
    return 0;
  }
}

Log tool class, providing common code for pointcut

/**
 * Utility class for logging, providing common code for pointcut
 */
public class Logger {
  /**
   * For printing logs: plan to execute the pointcut method before it executes (the pointcut method is the business method)
   */
  public  void printLog(){
    System.out.println("Logger Class printLog Method started logging...");
  }
}

Configuration steps

       [implementation steps]
            1. Leave the notification Bean to spring for management
            2. Use the aop:config tab to start the AOP configuration
            3. Use aop:aspect label to indicate configuration aspect
                 id attribute: provides a unique id for the facet
                 ref property: is the Id of the specified notification class bean.
            4. Use the corresponding label inside the aop:aspect label to configure the notification type
               Our example now is to have the printLog method before the pointcut method executes: so it's a pre notification
               aop:before: indicates configuration pre notification
                    Method property: used to specify which method in the Logger class is a pre notification
                    Pointcut property: used to specify the pointcut expression. The meaning of the expression refers to which methods in the business layer are enhanced
            5. Writing of pointcut expression:
                Keyword: execution (expression)
                Expression: access modifier return value package name. Package name. Package name... Class name. Method name (parameter list)
                Standard expression:
                    public void cn.lpl666.service.impl.AccountServiceImpl.saveAccount()
                Access modifiers can be omitted
                    void cn.lpl666.service.impl.AccountServiceImpl.saveAccount()
                The return value can use wildcards to represent any return value
                    * cn.lpl666.service.impl.AccountServiceImpl.saveAccount()
                The package name can use wildcards to represent any package. But if there are several levels of packages, you need to write several *
                    * *.*.*.*.AccountServiceImpl.saveAccount())
                The package name can use.. to represent the current package and its subpackages
                    * *..AccountServiceImpl.saveAccount()
                Class name and method name can use * to implement generic matching
                    * *..*.*()
                Parameter list:
                    Data types can be written directly:
                        Basic type write name int directly
                        Reference type write package name. Method of class name java.lang.String
                    You can use wildcards to represent any type, but you must have parameters
                    You can use.. to indicate whether there are parameters or not. Parameters can be of any type
                All in one:
                    * *..*.*(..)

                The common way to write pointcut expressions in actual development:
                    All methods under the business layer implementation class
                        * cn.lpl666.service.impl.*.*(..)

Spring framework configuration AOP

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--To configure IAccountService object-->
    <bean id="accountService" class="cn.lpl666.service.impl.AccountServiceImpl"></bean>
    <!--To configure Logger object-->
    <bean id="logger" class="cn.lpl666.utils.Logger"></bean>
    <!--AOP To configure-->
    <aop:config>
        <!--Section configuration-->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- Configure the type of notification and associate the notification method with the pointcut method-->
            <aop:before method="printLog" pointcut="execution(* cn.lpl666.service.impl.*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

test

public class ClientTest {
  public static void main(String[] args) {
    // Create container object
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    // Create business tier objects
    IAccountService accountService = (IAccountService)ac.getBean("accountService");
    accountService.saveAccount();
    accountService.deleteAccount();
  }
}

results of enforcement

The printLog method in the Logger class started logging
 Execution: account saved
 The printLog method in the Logger class started logging
 Execution: account deleted

2.5.2-AOP Configuration Label

aop:config:

Role: used to declare the configuration of starting aop

<aop:config>
	<! -- configuration code is written here -- >
</aop:config>

Use aop:aspect to configure aspect

Function: used to configure cut plane.

Properties:

  • id: provides a unique id for the facet.
  • ref: refers to the id of the configured notification class bean.
<! -- facet configuration -- >
<aop:aspect id="logAdvice" ref="logger">
    <! -- the type of configuration notification should be written here -- >
</aop:aspect>

Using aop:pointcut to configure pointcut expressions

Role: used to configure pointcut expressions. It specifies which methods of which classes are enhanced.

Properties:

  • expression: used to define pointcut expressions.
  • id: used to provide a unique identifier for a pointcut expression
<aop:pointcut id="serviceImpl" expression="execution(* cn.lpl666.service.impl.*.*(..))"></aop:pointcut>

2.5.3 - notice type

Summary

Code demo

Log tool class, providing common code for pointcut

/**
 * Utility class for logging, providing common code for pointcut
 */
public class Logger {
  /**
   * Before advice
   */
  public  void beforePrintLog(){
    System.out.println("Advance notice: Logger Class printLog Method started logging...");
  }
  /**
   * Post notification
   */
  public  void afterReturningPrintLog(){
    System.out.println("Post notice: Logger Class printLog Method started logging...");
  }
  /**
   * Exception notification
   */
  public  void afterThrowPrintLog(){
    System.out.println("Exception notification: Logger Class printLog Method started logging...");
  }
  /**
   * Final notice
   */
  public  void afterPrintLog(){
    System.out.println("Final notice: Logger Class printLog Method started logging...");
  }

}

Spring AOP configuration

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--To configure IAccountService object-->
    <bean id="accountService" class="cn.lpl666.service.impl.AccountServiceImpl"></bean>
    <!--To configure Logger object-->
    <bean id="logger" class="cn.lpl666.utils.Logger"></bean>
    <!--AOP To configure-->
    <aop:config>
        <aop:pointcut id="serviceImpl" expression="execution(* cn.lpl666.service.impl.*.*(..))"></aop:pointcut>
        <!--Section configuration-->
        <aop:aspect id="logAdvice" ref="logger">
              <!--
				[Properties of notification type label]
                    method: Specifies the name of the method in the notification.
                    pointct: Define pointcut expression
                    pointcut-ref: Specifies a reference to a pointcut expression
			-->
            <!-- Pre notification, and establish association between notification method and pointcut method-->
            <aop:before method="beforePrintLog" pointcut-ref="serviceImpl"></aop:before>
            <!-- Post notification and establish association between notification method and pointcut method-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="serviceImpl"></aop:after-returning>
            <!-- Exception notification, and establish association between notification method and pointcut method-->
            <aop:after-throwing method="afterThrowPrintLog" pointcut-ref="serviceImpl"></aop:after-throwing>
            <!-- Final notification, and establish association between notification method and pointcut method-->
            <aop:before method="afterPrintLog" pointcut-ref="serviceImpl"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

test

public class ClientTest {
  public static void main(String[] args) {
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    IAccountService accountService = (IAccountService)ac.getBean("accountService");
    accountService.saveAccount();
  }
}

Operation result

Pre notice: the printLog method in the Logger class has started logging
 Final notification: the printLog method in the Logger class has started logging
 Execution: account saved
 Post notification: the printLog method in the Logger class has started logging

2.5.4 - surround notification

The utility class of the Logger log, which provides the common code of the pointcut - surround notification

/**
 * Utility class for logging, providing common code for pointcut
 */
public class Logger {
  /**
   * Around Advice
   * Problem: when surround notification is configured, the notification method executes, but the tangent method does not
   * Analysis: through the comparison of previous dynamic agents, there is a call aspect method in dynamic agent's surround notification, but this method does not call
   * Solve:
   *      Spring The framework provides an interface, ProceedingJoinPoint. In this interface, there is a method called proceed(), which is equivalent to explicitly calling the pointcut method.
   *      This interface can be used as a parameter around the notification method. When the program executes, the Spring framework will provide us with the implementation class of this interface for us to use
   */
  public void aroundPrintLog(ProceedingJoinPoint pro){
    try{
      Object[] args = pro.getArgs(); // Get the parameters needed to execute the method
      System.out.println("Advance notice: Logger Class printLog Method started logging...");
      pro.proceed(args); // Call business tier methods
      System.out.println("Post notice: Logger Class printLog Method started logging...");
    }catch (Throwable throwable){
      System.out.println("Exception notification: Logger Class printLog Method started logging...");
    }finally {
      System.out.println("Final notice: Logger Class printLog Method started logging...");
    }
  }


}

Spring framework configuration

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--To configure IAccountService object-->
    <bean id="accountService" class="cn.lpl666.service.impl.AccountServiceImpl"></bean>
    <!--To configure Logger object-->
    <bean id="logger" class="cn.lpl666.utils.Logger"></bean>
    <!--AOP To configure-->
    <aop:config>
        <aop:pointcut id="serviceImpl" expression="execution(* cn.lpl666.service.impl.*.*(..))"></aop:pointcut>
        <!--Section configuration-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--Around Advice-->
            <aop:around method="aroundPrintLog" pointcut-ref="serviceImpl"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

test

public class ClientTest {
  public static void main(String[] args) {
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    IAccountService accountService = (IAccountService)ac.getBean("accountService");
    accountService.saveAccount();
  }
}

Result

Pre notice: the printLog method in the Logger class has started logging
 Execution: account saved
 Post notification: the printLog method in the Logger class has started logging
 Final notification: the printLog method in the Logger class has started logging

2.6 - AOP based on annotation

2.6.1 - implementation mode 1

Business layer implementation class code

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
  @Override
  public void saveAccount() {
    System.out.println("Execution: account saved");
    // int i = 10/0;
  }

  @Override
  public void updateAccount(int i) {
    System.out.println("Execution: updated account, account id: " + i);
  }

  @Override
  public int deleteAccount() {
    System.out.println("Execution: account deleted");
    return 0;
  }
}

The Logger tool class implementation provides common code for pointcuts

/**
 * Utility class for logging, providing common code for pointcut
 */
@Component("logger")
@Aspect // Indicates that this is a tangent class
public class Logger {
  // The expression of the corresponding package associated with the tangent class
  @Pointcut("execution(* cn.lpl666.service.impl.*.*(..))")
  public void serviceImpl(){}
  /**
   * Before advice
   */
  @Before("serviceImpl()")
  public  void beforePrintLog(){
    System.out.println("Pre notice: Logger Class printLog Method started logging...");
  }
  /**
   * Post notification
   */
  @AfterReturning("serviceImpl()")
  public  void afterReturningPrintLog(){
    System.out.println("Post notice: Logger Class printLog Method started logging...");
  }
  /**
   * Exception notification
   */
  @AfterThrowing("serviceImpl()")
  public  void afterThrowPrintLog(){
    System.out.println("Exception notification: Logger Class printLog Method started logging...");
  }
  /**
   * Final notice
   */
  @After("serviceImpl()")
  public  void afterPrintLog(){
    System.out.println("Final notice: Logger Class printLog Method started logging...");
  }
  //@Around("serviceImpl()")
  public void aroundPrintLog(ProceedingJoinPoint pro){
    try{
      Object[] args = pro.getArgs(); // Get the parameters needed to execute the method
      System.out.println("Advance notice: Logger Class printLog Method started logging...");
      pro.proceed(args); // Call business tier methods
      System.out.println("Post notice: Logger Class printLog Method started logging...");
    }catch (Throwable throwable){
      System.out.println("Exception notification: Logger Class printLog Method started logging...");
    }finally {
      System.out.println("Final notice: Logger Class printLog Method started logging...");
    }
  }


}

Spring framework xml configuration

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- inform spring Packages to scan when creating containers -->
    <context:component-scan base-package="cn.lpl666"/>
    <!--Spring Open notes AOP-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

Test class

public class ClientTest {
  public static void main(String[] args) {
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    IAccountService accountService = (IAccountService)ac.getBean("accountService");
    accountService.saveAccount();
  }
}

results of enforcement

Pre notice: the printLog method in the Logger class has started logging
 Execution: account saved
 Final notification: the printLog method in the Logger class has started logging
 Post notification: the printLog method in the Logger class has started logging

2.6.2 - implementation mode 2 - pure annotation

Remove xml configuration file

Logger tool class

/**
 * Utility class for logging, providing common code for pointcut
 */
@Component("logger")
@Aspect // Indicates that this is a tangent class
public class Logger {
  // The expression of the corresponding package associated with the tangent class
  @Pointcut("execution(* cn.lpl666.service.impl.*.*(..))")
  public void serviceImpl(){}
  /**
   * Before advice
   */
  @Before("serviceImpl()")
  public  void beforePrintLog(){
    System.out.println("Advance notice: Logger Class printLog Method started logging...");
  }
  /**
   * Post notification
   */
  @AfterReturning("serviceImpl()")
  public  void afterReturningPrintLog(){
    System.out.println("Post notice: Logger Class printLog Method started logging...");
  }
  /**
   * Exception notification
   */
  @AfterThrowing("serviceImpl()")
  public  void afterThrowPrintLog(){
    System.out.println("Exception notification: Logger Class printLog Method started logging...");
  }
  /**
   * Final notice
   */
  @After("serviceImpl()")
  public  void afterPrintLog(){
    System.out.println("Final notice: Logger Class printLog Method started logging...");
  }
  /**
   * Around Advice
   * Problem: when surround notification is configured, the notification method executes, but the tangent method does not
   * Analysis: through the comparison of previous dynamic agents, there is a call aspect method in dynamic agent's surround notification, but this method does not call
   * Solve:
   *      Spring The framework provides an interface, ProceedingJoinPoint. In this interface, there is a method called proceed(), which is equivalent to explicitly calling the pointcut method.
   *      This interface can be used as a parameter around the notification method. When the program executes, the Spring framework will provide us with the implementation class of this interface for us to use
   */
  //@Around("serviceImpl()")
  public void aroundPrintLog(ProceedingJoinPoint pro){
    try{
      Object[] args = pro.getArgs(); // Get the parameters needed to execute the method
      System.out.println("Advance notice: Logger Class printLog Method started logging...");
      pro.proceed(args); // Call business tier methods
      System.out.println("Post notice: Logger Class printLog Method started logging...");
    }catch (Throwable throwable){
      System.out.println("Exception notification: Logger Class printLog Method started logging...");
    }finally {
      System.out.println("Final notice: Logger Class printLog Method started logging...");
    }
  }


}

Spring configuration class

@Configuration    // Indicates that the class is a configuration class
@ComponentScan("cn.lpl666")  // Packages to be scanned by spring container
@EnableAspectJAutoProxy   // Enable Aop annotation
public class SpringConfiguration {
}

test

public class App 
{
    public static void main(String[] args) {
        
        // Create container object
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        // Create business object
        IAccountService accountService = (IAccountService)ac.getBean("accountService");
        accountService.saveAccount();
    }
}

2.6.3 - annotation steps

Step 1: configure the notification class with annotations

Step 2: use on the notification class @Aspect Annotation declared as facet

Step 3: configure notifications using annotations on enhanced methods

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

Step 4: pointcut expression annotation

  • @Pointcut
    • Role: specify pointcut expression
    • Property: value, which specifies the content of the expression
  // The expression of the corresponding package associated with the tangent class
  @Pointcut("execution(* cn.lpl666.service.impl.*.*(..))")
  public void serviceImpl(){}
  /**
   * Before advice
   */
  @Before("serviceImpl()") 
  public  void beforePrintLog(){
    System.out.println("Pre notice: Logger Class printLog Method started logging...");
  }

Tags: Programming Spring Database xml MySQL

Posted on Sat, 15 Feb 2020 23:14:45 -0800 by IceDragon