Introduction to AOP (traditional Program)

Tips: If you want a quick look up, skip to the section on getting To know AOP (Spring Programs)

The previous content to link past Java, JavaWeb knowledge gradually introduced to AOP, mainly the real Spring AOP content includes the following sections can jump ha

(I) AOP terminology

(2) AOP introduction case: XML, annotation mode

(3) Transaction control based on Spring: XML, annotation mode, pure annotation mode

(1) A simple analysis of AOP

In the software industry, AOP stands for Aspect Oriented Programming, which means Aspect Oriented Programming. It is a technique that implements unified maintenance of program functions by means of precompilation and dynamic proxies at runtime. AOP is a continuation of OOP, a hot topic in software development, an important content in Spring framework, and a derivative paradigm of functional programming. Using AOP can isolate each part of the business logic, so that the coupling degree between each part of the business logic is reduced, the reusability of the program is improved, and the efficiency of development is improved.

— Baidu Encyclopedia

The introduction to Spring AOP, which I personally found to be very obscure, makes me realize that when I look back at the introduction, what this means is that we take some repetitive code from the program, and when we need it to execute, A technique for dynamically enhancing or adding functionality to a program without moving the source code through precompilation or dynamic proxies at runtime

Take out some duplicate code? What code was it? For example!

In the following method, we simulate the management of transactions in the program. A and B in the following code can be regarded as some transaction scenarios of “start transaction” and “commit transaction”, and these code can be regarded as one of the repeated code mentioned above

And some duplicate code is mostly about rights management or log log in some although affects our code of business logic such as “clean”, but have to exist, if there is any way to extract these methods, make our business code more concise, natural we can concentrate on business with us, good for development, And that’s what we want to focus on today

Finally have to mention is that AOP as Spring one of the core content of the framework, obviously used a large number of design patterns, design patterns, in the final analysis, is for decoupling, and improve the flexibility, scalability, and we learned some of the framework, encapsulate directly to these things, let you use directly, say a bit white, It is to make you lazy, so that you not only keep good code structure, but also do not need to write these complex data structures with you, improve the development efficiency

The name of any technology is just a noun. In fact, for the introduction, we need to understand more. By comparing traditional programs with programs that use Spring AOP related techniques, it’s much more useful to learn how to use AOP by knowing what it is and then applying it for what it is.

(ii) Demonstration case (traditional way)

Note: The first part of the following examples are in the previous article to improve the program, in order to take care of all friends, I will mention from the dependency to the class writing, convenient for you to have a need to practice, look at the overall structure of the program, to the following instructions also have a certain help

(1) Add the necessary dependencies

  • spring-context
  • mysql-connector-java
  • C3p0 (database connection pool)
  • Commons – Dbutils (tool for simplifying JDBC) – described briefly later
  • Junit (unit self-test)
  • spring-test

Note: Since I am creating a Maven project here, modify the POM.xml to add the necessary dependency coordinates

If you don’t use a dependent friend, just download the JAR package you need to import

<packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2. RELEASE</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2. RELEASE</version>
        </dependency>
    </dependencies>
Copy the code

At a glance, some of the Spring core dependencies, as well as database-related dependencies, as well as dependencies such as unit tests, are imported

(2) Create account table and entity

The first case we’ll use below involves a simulated transfer transaction between two accounts, so we create a table with fields like name and balance

A: Create the Account table

-- ----------------------------
-- Table structure for account
-- ----------------------------
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32),
  `balance` float,
  PRIMARY KEY (`id`))Copy the code

B: Create the Account class

Nothing to say, corresponding to our watch created entity

public class Account implements Serializable {
    private  Integer id;
    private String name;
    privateFloat balance; . Add get set toString methodCopy the code

(3) Create Service and Dao

Next, we will demonstrate the transaction problem. The transfer method is mainly used. Of course, there are also some methods of adding, deleting, changing and checking

A: The AccountService interface

public interface AccountService {
    /** * Query all *@return* /
    List<Account> findAll(a);

    /** * Method of transfer *@paramSourceName Transfer out the account *@paramTargetName Transfer to account *@param money
     */
    void transfer(String sourceName,String targetName,Float money);
}
Copy the code

B: The AccountServiceImpl implementation class

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

    public List<Account> findAll(a) {
        return accountDao.findAllAccount();
    }
    
    public void transfer(String sourceName, String targetName, Float money) {
        // Query the names of the accounts to be transferred to and from
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);

        // In/out account plus or minus
        source.setBalance(source.getBalance() - money);
        target.setBalance(target.getBalance() + money);
        // Update the transfer to/out accountaccountDao.updateAccount(source); accountDao.updateAccount(target); }}Copy the code

C: AccountDao interface

public interface AccountDao {

    /** * More detailed account information (modified) *@param account
     */
    void updateAccount(Account account);

    /** * Query all accounts *@return* /
    List<Account> findAllAccount(a);

    /** * Query by name@param accountName
     * @return* /
    Account findAccountByName(String accountName);
}
Copy the code

D: AccountDaoImpl implementation class

We introduced DBUtils such a database operation tool, its role is to encapsulate code, to simplify the purpose of JDBC operation, due to the integration of SSM framework, persistence layer things can be handed over to MyBatis to do, and today we focus on the knowledge in Spring, So I’ll just use this part

Basic explanation of the content used:

QueryRunner provides apis to manipulate SQL statements (INSERT Delete Update)

The ResultSetHander interface, which defines how to encapsulate the result set after the query (provided only for our use)

  • BeanHander: Encapsulates the first record in the result set into the specified JavaBean
  • BeanListHandler: Encapsulates all the records in the result set into the specified Javabeans, and encapsulates each Javabeans into the List
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner runner;

    public void updateAccount(Account account) {
        try {
            runner.update("update account set name=? ,balance=? where id=?", account.getName(), account.getBalance(), account.getId());
        } catch (Exception e) {
            throw newRuntimeException(e); }}public List<Account> findAllAccount(a) {
        try {
            return runner.query("select * from account".new BeanListHandler<Account>(Account.class));
        } catch (Exception e) {
            throw newRuntimeException(e); }}public Account findAccountByName(String accountName) {
        try {
            List<Account> accounts = runner.query("select * from account where name = ?".new BeanListHandler<Account>(Account.class), accountName);

            if (accounts == null || accounts.size() == 0) {
                return null;
            }
            if (accounts.size() > 1) {
                throw new RuntimeException("The result set is not unique. There is a problem with the data.");
            }
            return accounts.get(0);
        } catch (Exception e) {
            throw newRuntimeException(e); }}}Copy the code

(4) Configuration file

A:bean.xml

<?xml version="1.0" encoding="UTF-8"? >
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <! -- Enable scan -->
    <context:component-scan base-package="cn.ideal"></context:component-scan>

    <! - the configuration QueryRunner -- -- >
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <! -- Inject data source -->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <! -- Configure data source -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ideal_spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root99"></property>
    </bean>
</beans>
Copy the code

B: jdbcConfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ideal_spring
jdbc.username=root
jdbc.password=root99
Copy the code

(5) Test code

A: AccountServiceTest

In this case, we use Spring and Junit tests

Description: Replace the runner with the @RunWith annotation, specify the location of the Spring configuration file with @ContextConfiguration, and inject data into the variable with @Autowired

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService as;

    @Test
    public void testFindAll(a) {
        List<Account> list = as.findAll();
        for(Account account : list) { System.out.println(account); }}@Test
    public void testTransfer(a) {
        as.transfer("Bill"."Zhang".500f); }}Copy the code

(6) Implementation effect

First execute query all:

Then execute the simulated transfer method:

Method, that is, to zhang SAN li si transfer in 500, see the following as a result, there is no any problem

(3) Preliminary analysis and solution

(1) Analyze the transaction problem

First of all, we did not explicitly manage the transaction, but do not deny, the transaction must exist, if the transaction is not committed, obviously, the query function will not be able to test successfully, our code transaction is implicitly automatically controlled, SetAutoCommit (true) of the Connection object is used, that is, it is committed automatically

If you look at the configuration file, we only inject the data source. What does that mean?

<! - the configuration QueryRunner -- -- >
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
	<! -- Inject data source -->
	<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
Copy the code

In other words, each statement is a separate transaction:

Each call creates a new QueryRunner object and obtains a connection from the data source. However, when the QueryRunner object is called, the QueryRunner object is created and a connection is obtained from the data source. When a problem occurs in one of the steps, the preceding statement is still executed, but the following statement is terminated due to an exception, which is independent of each other as we said at the beginning

public void transfer(String sourceName, String targetName, Float money) {
        // Query the names of the accounts to be transferred to and from
        Account source = accountDao.findAccountByName(sourceName); / / 1
        Account target = accountDao.findAccountByName(targetName); / / 2

        // In/out account plus or minus
        source.setBalance(source.getBalance() - money); / / 3
        target.setBalance(target.getBalance() + money);
        // Update the transfer to/out account
        accountDao.updateAccount(source); / / 4
        // Simulate transfer exception
        int num = 100/0; / / exception
        accountDao.updateAccount(target); / / 5
}
Copy the code

Obviously, this is very inappropriate, even fatal. As written in our code, the account information of the transferred account has been updated with the deduction, but the account information of the transferred party failed to be successfully executed due to the previous anomaly. Li Si changed from 2500 to 2000, but Zhang SAN failed to receive the transfer successfully

(2) Preliminarily solve business problems

The problem above comes down to the fact that we can’t achieve overall transaction control (contrary to transaction consistency) because the methods in our persistence layer are transaction independent.

The first thing we need to do is bind the Connection to the current thread using a ThreadLocal object, so that there is only one transaction object in a thread.

A quick mention of Threadlocal:

Threadlocal is an intra-thread storage class that stores data in a specified thread. In other words, the data is bound to that thread and can only be retrieved through that thread

Here’s the official explanation:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copLy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

In other words, ThreadLoacl provides a way to store local variables within a thread. The special thing about these variables is that each thread obtains the variables independently, and the data values are obtained by the methods get and set

A: ConnectionUtils utility class

I create the utils package, and I create a ConnectionUtils utility class, and the main part of it is that I write a simple decision, if the thread already has a connection, I return it, and if it doesn’t, I get a link from the data source, save it, and return it

@Component
public class ConnectionUtils {
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

    @Autowired
    private DataSource dataSource;

    public Connection getThreadConnection(a) {

        try {
            // Obtain from ThreadLocal
            Connection connection = threadLocal.get();
            // Check whether it is null
            if (connection == null) {
                // Get a connection from the data source and store it in ThreadLocal
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            }
            return connection;
        } catch (Exception e) {
            throw newRuntimeException(e); }}public void removeConnection(a){ threadLocal.remove(); }}Copy the code

B: TransactionManager tool class

You can then create a utility class to manage transactions, including starting, committing, rolling back transactions, and releasing connections

@Component
public class TransactionManager {

    @Autowired
    private ConnectionUtils connectionUtils;

    /** * Start transaction */
    public void beginTransaction(a) {
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch(Exception e) { e.printStackTrace(); }}/** * Commit transaction */
    public void commit(a) {
        try {
            connectionUtils.getThreadConnection().commit();
        } catch(Exception e) { e.printStackTrace(); }}/** * Roll back transactions */
    public void rollback(a) {
        try {
            System.out.println("Roll back transactions" + connectionUtils.getThreadConnection());
            connectionUtils.getThreadConnection().rollback();
        } catch(Exception e) { e.printStackTrace(); }}/** * Release connection */
    public void release(a) {
        try {
            connectionUtils.getThreadConnection().close();// Return to the connection pool
            connectionUtils.removeConnection();
        } catch(Exception e) { e.printStackTrace(); }}}Copy the code

C: Business layer to add transaction code

Add transaction management code to the method, normally execute to start the transaction, perform the action (your business code), commit the transaction, roll back the transaction if an exception is caught, and finally execute to release the connection

In this case, even if an exception occurs in one of the steps, there is no actual change to the data, and the above problem is tentatively resolved

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

    @Autowired
    private TransactionManager transactionManager;

    public List<Account> findAll(a) {
        try {
            // Start the transaction
            transactionManager.beginTransaction();
            // Execute the operation
            List<Account> accounts = accountDao.findAllAccount();
            // Commit the transaction
            transactionManager.commit();
            // Return the result
            return accounts;
        } catch (Exception e) {
            // Roll back the operation
            transactionManager.rollback();
            throw new RuntimeException(e);
        } finally {
            // Release the connectiontransactionManager.release(); }}public void transfer(String sourceName, String targetName, Float money) {

        try {
            // Start the transaction
            transactionManager.beginTransaction();
            // Execute the operation

            // Query the names of the accounts to be transferred to and from
            Account source = accountDao.findAccountByName(sourceName);
            Account target = accountDao.findAccountByName(targetName);

            // In/out account plus or minus
            source.setBalance(source.getBalance() - money);
            target.setBalance(target.getBalance() + money);

            // Update the outward transfer account
            accountDao.updateAccount(source);
            // Simulate transfer exception
            int num = 100 / 0;
            accountDao.updateAccount(target);

            // Commit the transaction
            transactionManager.commit();

        } catch (Exception e) {
            // Roll back the operation
            transactionManager.rollback();
            e.printStackTrace();
        } finally {
            // Release the connectiontransactionManager.release(); }}}Copy the code

(4) Think and improve the way

Although the above, we have achieved in the business layer to carry on the control of the transaction, but clearly can see, we are in every way too much duplicated code, and in the business layer appeared coupled with transaction management methods, for example, transaction management in the class by a method name changes, will directly cause and couldn’t find the corresponding method in the business layer, All need to be modified, if there are many methods in the business layer, obviously this is very troublesome

In this case, we can improve the application by using static proxies. In order to take care of all our friends, let’s review an introduction to dynamic proxies and how to use them

(5) Review dynamic proxies

(1) What is dynamic proxy

Dynamic proxy, which provides a proxy object for an object to control access to that object

Simple example is: buy the train, plane tickets, etc., we can directly from the station to buy ticket Windows, this is the user directly on the official purchase, but we have a lot of local shop or some roadside scenery can train ticket booking, users can directly purchase tickets in the outlets, these places is a proxy object

(2) What are the benefits of using proxy objects?

  • The class provided by the function (train station ticket office) can be more focused on the implementation of the main functions, such as arranging train numbers, producing train tickets, etc
  • Agent classes (agents) can be added to the methods provided by the function provider classes to implement more functionality

The advantage of this dynamic proxy gives us a lot of convenience. It can help us to achieve non-invasive code extension, that is, to enhance the method without modifying the source code

Dynamic proxy can be divided into two types: (1) interface-based dynamic proxy and (2) subclass-based dynamic proxy

(3) Two ways of dynamic proxy

A: Dynamic proxy based on interfaces

A: Create official ticket office (class and interface)

RailwayTicketProducer interface

/** * Manufacturer's interface */
public interface RailwayTicketProducer {

    public void saleTicket(float price);

    public void ticketService(float price);

}
Copy the code

RailwayTicketProducerImpl class

In the implementation class, we only enhance the method of selling tickets later, and after-sales service is not involved

/** * Manufacturer specific implementation */
public class RailwayTicketProducerImpl implements RailwayTicketProducer{

    public void saleTicket(float price) {
        System.out.println("Sell train tickets and receive ticket money:" + price);
    }

    public void ticketService(float price) {
        System.out.println("After-sales service (change), received commission:"+ price); }}Copy the code

The Client class

This class is called the customer class, where the need to purchase tickets is realized by proxy objects

Let’s start with how to create a Proxy object: the answer is the newProxyInstance method in the Proxy class

Note: Since it is called an interface-based dynamic proxy, it is necessary that the class being represented, which is the class that sells the official ticket in this article, must implement at least one interface!

public class Client {

    public static void main(String[] args) {
        RailwayTicketProducer producer = new RailwayTicketProducerImpl();

        // Dynamic proxy
        RailwayTicketProducer proxyProduce = (RailwayTicketProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),new MyInvocationHandler(producer));

        // Customers buy tickets through agents
        proxyProduce.saleTicket(1000f); }}Copy the code

NewProxyInstance takes three parameters to explain:

  • ClassLoader: indicates the ClassLoader

    • Used to load the proxy object bytecode, using the same class loader as the proxy object
  • Class[] : array of bytecode

    • In order to make the proxy object and the proxy object have the same method, implement the same interface, can be regarded as fixed writing
  • InvocationHandler: How to delegate, that is, the way you want to enhance it

    • In other words, we need to new the InvocationHandler, and then write its implementation class. We can choose whether to write an anonymous inner class or not

    • New MyInvocationHandler(producer) instantiates a MyInvocationHandler class that I wrote myself. In fact, I can just write a new InvocationHandler and override its methods. Its essence is also enhanced by implementing the Invoke method of the InvocationHandler

MyInvocationHandler class

This invoke method has the interception function. Any method that is executed on the propped object passes through the Invoke

public class MyInvocationHandler implements InvocationHandler {

    private  Object implObject ;

    public MyInvocationHandler (Object implObject){
        this.implObject=implObject;
    }

    /** * Function: any interface method that executes a proxied object passes through the meaning of the method parameter *@paramProxy A reference to a proxy object *@paramMethod Specifies the currently executed method *@paramArgs Specifies the parameters * required for the current execution method@returnHas the same return value * as the proxied object method@throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnValue = null;
        // Get the parameters that the method executes
        Float price = (Float)args[0];
        // Determine whether the specified method (for example, ticket selling)
        if ("saleTicket".equals(method.getName())){
            returnValue = method.invoke(implObject,price*0.8 f);
        }
        returnreturnValue; }}Copy the code

Here, we get the amount of ticket purchased by the customer. Since we use an agent to purchase tickets, the agent will charge a certain commission, so the user submitted 1000 yuan, but the official received only 800 yuan. This is the implementation method of this agent, and the results are as follows

Sales of train tickets, received ticket money: 800.0

B: Subclass-based dynamic proxy

The above method is simple to implement and not very hard, but there is only one standard, by proxy objects must provide an interface, and now the interpretation of this one is a kind of can direct agent ordinary Java classes, at the same time at the time of presentation, I’ll write values within the proxy method directly, did not create the class alone, facilitate everybody as above

Add cglib dependent coordinates

<dependencies>
	<dependency>
		<groupId>cglib</groupId>
		<artifactId>cglib</artifactId>
        <version>3.2.4</version>
    </dependency>
</dependencies>
Copy the code

TicketProducer class

/** * Manufacturer */
public class TicketProducer {

    public void saleTicket(float price) {
        System.out.println("Sell train tickets and receive ticket money:" + price);
    }

    public void ticketService(float price) {
        System.out.println("After-sales service (change), received commission:"+ price); }}Copy the code

The create method in the Enhancer class is used to create the proxy object

The create method takes two parameters

  • Class: bytecode
    • Specifies the bytecode of the proxied object
  • Callback: Provides enhanced methods
    • The function of invoke is basically the same as that of invoke
    • This is usually written as a subinterface implementation class: MethodInterceptor
public class Client {

    public static void main(String[] args) {
        // Final is required here because of the anonymous inner class below
        final TicketProducer ticketProducer = new TicketProducer();

        TicketProducer cglibProducer =(TicketProducer) Enhancer.create(ticketProducer.getClass(), new MethodInterceptor() {

            /** * The first three parameters are the same as the parameters of the invoke method in an interface-based dynamic proxy *@param o
             * @param method
             * @param objects
             * @paramMethodProxy Proxy object * for the currently executing method@return
             * @throws Throwable
             */
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object returnValue = null;
                // Get the parameters that the method executes
                Float price = (Float)objects[0];
                // Determine whether the specified method (for example, ticket selling)
                if ("saleTicket".equals(method.getName())){
                    returnValue = method.invoke(ticketProducer,price*0.8 f);
                }
                returnreturnValue; }}); cglibProducer.saleTicket(900f);
    }
Copy the code

(6) dynamic proxy program to be improved

Here we write a factory for creating business layer objects

In this code, we use the dynamic proxy method based on the interface as previously reviewed. Before and after the execution method, transaction management methods such as opening transaction, committing transaction and rolling back transaction are respectively written. At this time, the business layer can delete the repeated code about business previously written

@Component
public class BeanFactory {
    @Autowired
    private AccountService accountService;
    @Autowired
    private TransactionManager transactionManager;

    @Bean("proxyAccountService")
    public AccountService getAccountService(a) {
        return (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        Object returnValue = null;
                        try {
                            // Start the transaction
                            transactionManager.beginTransaction();
                            // Execute the operation
                            returnValue = method.invoke(accountService, args);
                            // Commit the transaction
                            transactionManager.commit();
                            // Return the result
                            return returnValue;
                        } catch (Exception e) {
                            // Roll back the transaction
                            transactionManager.rollback();
                            throw new RuntimeException();
                        } finally {
                            // Release the connectiontransactionManager.release(); }}}); }}Copy the code

AccountServiceTest class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")

public class AccountServiceTest {
    @Autowired
    @Qualifier("proxyAccountService")
    private AccountService as;

    @Test
    public void testFindAll(a) {
        List<Account> list = as.findAll();
        for(Account account : list) { System.out.println(account); }}@Test
    public void testTransfer(a) {
        as.transfer("Bill"."Zhang".500f); }}Copy the code

Up to now, a relatively complete case on the transformation is completed, because we generally use the annotated way, and not all use XML configuration, if you use XML configuration, configuration is relatively cumbersome, so we pave the way so much content, in fact, is to introduce the concept of Spring AOP, From the root, step by step, according to the question elicits the techniques to learn

Let’s take a look!

Introduction to AOP (Spring Program)

In front of large space explaining what we were in a traditional program, is how to step by step, to improve and deal with the problem such as transaction, and Spring AOP in this technology, can help us to source in not on the basis of existing methods for enhancement, maintenance is very convenient, also greatly improve the efficiency of the development, Now that we’ve formally introduced AOP, with some groundwork in place, you can continue to improve on the previous program using AOP!

(I) AOP terminology

Any door technology, will have its specific terms, in fact is the specific name, in fact, I was at the time of learning, feeling some of AOP terminology are relatively abstract, is not very intuitive reflect the meaning of it, but these terms have been widely by developers, became in the related technology, Some of the concepts are known by default, and although it is more important to understand how AOP is thought and used, we need to introduce such a “consensus”

“Spring actual combat” there is such a sentence, picked out:

Before we enter a field, we must learn how to speak in that field

Advice

  • Have security, transaction, or logging defined and perform some notification, enhanced processing before and after a method
  • In other words, notification refers to what needs to be done after intercepting Joinpoint **
  • There are five types of notifications:
    • Before: called Before the target method is executed
    • After: Used After the target method is complete and the output is independent of it
    • After-returning notification: Called After successful execution of the target method
    • After-throwing: called After the target method has thrown an exception
    • Around advice: Advice wraps Around the notified method, performing custom behavior before and after the call to the notified method (this is evident in the annotations, which you can note later)

Joinpoint

  • Is a point at which sections can be inserted during application execution. This can be when a method is called, an exception is thrown, or even when a field is modified. Aspect code can use these points to insert itself into the normal flow of an application and add new behavior
  • For example, we added transaction management to the methods in the Service, and the methods in the transaction layer are all intercepted by the dynamic proxy. These methods can be regarded as the join point, and before and after these methods, we can add some notifications
  • In short: both the front and the back of the method can be considered join points

Pointcut

  • Sometimes there are many methods in a class, but we don’t want to add advice before and after all the methods, we just want to pass the method specified, and this is the concept of pointcuts
  • In short: A pointcut is a filter of join points that will eventually be used

Aspect

  • Pointcuts tell the program where to enhance or process, and advice tells the program what to do at that point and when to do it, so pointcuts + advice ≈ sections
  • An aspect is actually a scale-up of the repetitive parts of our business module. You can see this in comparison to the previous example where we added repetitive transaction code directly to each method in the business layer
  • In short: an aspect is a combination of pointcuts and advice

Introduction

  • It is a special kind of notification that allows you to dynamically add methods or properties to a class at runtime without modifying the source code

Weaving

  • The process of applying an aspect (enhancement) to a target object and creating a new proxy object
  • In effect, it is similar to the previous process of enhancing a method through dynamic proxies and adding transaction methods

(2) AOP introduction case

First of all, through a very simple case, to demonstrate how to execute a log printing method before several methods are executed, simple simulation for the output of a sentence, we are familiar with the previous steps, need to pay attention to the bean.xml configuration method, I will code the following detailed explanation

(1) XmL-based method

A: Depends on coordinates

Aspectjweaver, this dependency is used to support pointcut expressions, etc., which will be covered later in the configuration

<packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2. RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
Copy the code

B: Service layer

AccountService interface

public interface AccountService {
    /** * Save account */
    void addAccount(a);
    /** * Delete account *@return* /
    int  deleteAccount(a);
    /** * Update account *@param i
     */
    void updateAccount(int i);
}
Copy the code

AccountServiceImpl implementation class

public class AccountServiceImpl implements AccountService {
    public void addAccount(a) {
        System.out.println("This is the way to add.");
    }

    public int deleteAccount(a) {
        System.out.println("Here's the delete method.");
        return 0;
    }

    public void updateAccount(int i) {
        System.out.println("Here's the update method."); }}Copy the code

C: Log class

public class Logger {
    /** * is used to print the log: it is scheduled to be executed before the pointcut method executes (pointcut methods are business-layer methods) */
    public void printLog(a){
        System.out.println("The printLog method in the Logger class executes"); }}Copy the code

D: configuration file

bean.xml

<?xml version="1.0" encoding="UTF-8"? >
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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">
    
    <! -- configure Spring IOC, configure service to come in -->
    <bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl"></bean>

    <! -- Configure Logger in -->
    <bean id="logger" class="cn.ideal.utils.Logger"></bean>

    <! - the configuration of AOP -- -- >
    <aop:config>
        <! -- Configure sections -->
        <aop:aspect id="logAdvice" ref="logger">
            <! -- The type of advice and establishing the association between advice methods and pointcut methods -->
            <aop:before method="printLog" pointcut="execution(* cn.ideal.service.impl.*.*(..) )"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>
Copy the code

(2) ANALYSIS of XML configuration

A: Basic configuration

<?xml version="1.0" encoding="UTF-8"? >
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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">
</beans>
Copy the code

The first thing you need to introduce is this HEADER file of XML, and some of the constraints, you can either copy them directly here, or you can go to the website and find the constraints and so on, just like before

Next, the Service and Logger are configured through the bean label

B: AOP basic configuration

Aop :config: indicates the start of AOP configuration, and the configuration code is all written in this tag

Aop :aspect: Indicates the start of configuring an aspect

  • Id attribute: Provides a unique identification for a section
  • Ref property: reference the configured notification class bean. Fill in the id of the notification class

Aop: Within the aspect tag, the type of advice is configured through the corresponding tag

<aop:config>
	<! -- Configure sections -->
	<aop:aspect id="logAdvice" ref="logger">
    	<! -- The type of advice and establishing the association between advice methods and pointcut methods -->
	</aop:aspect>
</aop:config>
Copy the code

C: Four common advice configurations of AOP

In this case, we execute the notification before the method executes, so we use the pre-notification

Aop :before: used to configure pre-notification, specifying that enhanced methods execute before pointcut methods

Aop: After-returning: Used to configure post-returning notification, with exception notification only one of which can be executed

Aop :after-throwing: Used to configure exception notification. Only one exception notification can be executed

Aop: After: is used to configure the final advice, which will be executed after the pointcut method regardless of whether there were exceptions when it was executed

Parameters:

  • Method: Specifies the name of the enhanced method in the notification class, which is the printLog method in our Logger class above

  • Poinitcut: Used to specify which methods in the business layer are enhanced by the pointcut expression (the one used in this article)

  • Ponitcut-ref: Reference to the expression used to specify the pointcut (this is used more often when there are too many calls, reducing duplication of code)

To write a pointcut expression:

  • First, put the execution() keyword in quotation marks around the poinitcut property and write the expression in parentheses

  • Basic format: The access modifier returns the value package name. Package name. Package name… Class name. Method name (method parameter)

    • Note: The number of package names is determined by the package structure in which the class belongs

    • Full matching

      • public void cn.ideal.service.impl.AccountServiceImpl.addAccount()
    • Access modifiers such as public can be omitted, and return values can use wildcards to indicate any return value

      • void cn.ideal.service.impl.AccountServiceImpl.addAccount()
    • Package names can use wildcards to represent any package, and for every level of package, you need to write as many *’s.

      • * *.*.*.*.AccountServiceImpl.addAccount()
    • Package names can use.. Represents the current package and its subpackages

      • cn.. *.addAccount()
    • You can wildcard both class names and method names with *, and the following indicates the full wildcard

      • * *.. *. * (..)
  • The method parameters

    • You can write data types directly: for example, int

    • Reference type write package name. Class name is java.lang.string

    • You can use wildcards to represent any type, but you must have arguments

    • You can use.. Indicates that parameters can be of any type

For practical use, it is more recommended to write the same as in the above code, giving the package structure (usually enhanced for the business layer) and using wildcards for the rest

pointcut="execution(* cn.ideal.service.impl.*.*(..) )"

After the four notification types are given, we need to write this cut-in expression many times, so we can use the pointcut-ref parameter to solve the problem of duplicate code, which is essentially abstracted for later invocation

Ponitcut-ref: Reference to the expression used to specify the pointcut (this is used more often when there are too many calls, reducing duplication of code)

Place the position inside the Config, outside the aspect

<aop:pointcut id="pt1" 
expression="execution(* cn.ideal.service.impl.*.*(..) )"></aop:pointcut>
Copy the code

A call:

<aop:before method="PrintLog" pointcut-ref="pt1"></aop:before>
Copy the code

D: surround notification

Next, the Spring framework provides us with a way to manually control when enhancements are executed in our code, namely surround notifications

The configuration requires a statement that pT1 is the same as before

<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
Copy the code

Logger class

public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
    Object returValue = null;
    try {
        Object[] args = proceedingJoinPoint.getArgs();
        System.out.println("Here's the aroundPrintLog front method in the Logger class.");

        returValue = proceedingJoinPoint.proceed(args);

        System.out.println("Here's the aroundPrintLog method in the Logger class.");

        return returValue;
    } catch (Throwable throwable) {
        System.out.println("This is the aroundPrintLog exception method in the Logger class.");
        throw new RuntimeException();
    } finally {
        System.out.println("Here's the final aroundPrintLog method in the Logger class."); }}Copy the code

To explain:

Spring provides an interface: ProceedingJoinPoint [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] It’s a similar feeling to the dynamic proxy

(3) Annotation-based approach

Dependencies, and business layer methods, are we using the same as XML, but for the sake of illustration, we’ll just leave the add method here

A: Configuration file

One is to introduce new constraints in the configuration file, and the other is to enable scanning and annotation AOP support

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

    <! -- configure the packages to scan when Spring creates containers -->
    <context:component-scan base-package="cn.ideal"></context:component-scan>

    <! -- Configure Spring to enable annotate AOP support -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>
Copy the code

B: Add annotations

The first is to inject the Service in the business layer

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    public void addAccount(a) {
        System.out.println("This is the way to add."); }}Copy the code

Next to the final location in the Logger class, first inject the class as a whole via @Component(” Logger “)

Then use @Aspect to indicate that this is an Aspect class

Below I use the four notification types, and the wrap notification type, which is something to note in the annotations

The first time I tested four types of advice, I commented out the wrap advice first and left the first four uncommented

@Component("logger")
@Aspect// Indicates that the current class is an aspect class
public class Logger {

    @Pointcut("execution(* cn.ideal.service.impl.*.*(..) )")
    private void pt1(a){}


// @Before("pt1()")
    public void printLog1(a){
        System.out.println("The printLog method in the Logger class executes -front");
    }

// @AfterReturning("pt1()")
    public void printLog2(a){
        System.out.println("The printLog method in the Logger class executes -post");
    }

// @AfterThrowing("pt1()")
    public void printLog3(a){
        System.out.println("The printLog method in the Logger class executes - exception");
    }

// @After("pt1()")
    public void printLog4(a){
        System.out.println("The printLog method in the Logger class executes - finally.");
    }


    @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
        Object returValue = null;
        try {
            Object[] args = proceedingJoinPoint.getArgs();
            System.out.println("Here's the aroundPrintLog front method in the Logger class.");

            returValue = proceedingJoinPoint.proceed(args);

            System.out.println("Here's the aroundPrintLog method in the Logger class.");

            return returValue;
        } catch (Throwable throwable) {
            System.out.println("This is the aroundPrintLog exception method in the Logger class.");
            throw new RuntimeException();
        } finally {
            System.out.println("Here's the final aroundPrintLog method in the Logger class."); }}}Copy the code

Four notification types test results:

As you can see, a particularly weird thing happens, there’s a problem with the placement of the post notification and the final notification, as well as exceptions, and indeed that’s an issue here, so we usually use the wrap notification in our annotations

Surround notification test results:

(4) Pure annotation

Just add @enableAspectJAutoProxy

@Configuration
@ComponentScan(basePackages="cn.ideal")
@EnableAspectJAutoProxy// This is the main annotation
public class SpringConfiguration {}Copy the code

This is the end of the basic use of both XML and annotations. Now we will talk about how to implement transaction control completely based on Spring

(3) Fully Spring-based transaction control

Spring is a powerful framework for handling transactions in the business layer. It provides us with a set of interfaces for transaction control. On the basis of AOP, we can efficiently complete transaction control. In this part, we choose to use Spring, such as persistence layer unit tests, and so on. Pay special attention to: Persistence layer we use Spring’s JdbcTemplate, unfamiliar friends can go to a simple understanding, in this case, the focus is to learn the transaction control, here will not cause too much impact

(1) Prepare the code

Note: The first part of the code to be demonstrated is an XML-based form, so we did not use annotations when we prepared the code, and we will change them later when we discuss how to use annotations

A: Import the dependent coordinates

<packaging>jar</packaging>

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

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2. RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2. RELEASE</version>
        </dependency>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
Copy the code

B: Create account tables and entities

Create Account table

-- ----------------------------
-- Table structure for account
-- ----------------------------
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32),
  `balance` float,
  PRIMARY KEY (`id`))Copy the code

To create the Account class

Nothing to say, corresponding to our watch created entity

public class Account implements Serializable {
    private  Integer id;
    private String name;
    privateFloat balance; . Add get set toString methodCopy the code

C: Create Service and Dao

In order to reduce the space, I gave the implementation class, the interface is not pasted, it is very simple

The business layer

package cn.ideal.service.impl;

import cn.ideal.dao.AccountDao;
import cn.ideal.domain.Account;
import cn.ideal.service.AccountService;

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);

    }

    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("Transfer Method Execution");
        // Query the names of the accounts to be transferred to and from
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);

        // In/out account plus or minus
        source.setBalance(source.getBalance() - money);
        target.setBalance(target.getBalance() + money);
        // Update the transfer to/out account
        accountDao.updateAccount(source);

        int num = 100/0; accountDao.updateAccount(target); }}Copy the code

The persistence layer

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    public Account findAccountById(Integer accountId) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?".new BeanPropertyRowMapper<Account>(Account.class),accountId);
        return accounts.isEmpty()?null:accounts.get(0);
    }


    public Account findAccountByName(String accountName) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?".new BeanPropertyRowMapper<Account>(Account.class),accountName);
        if(accounts.isEmpty()){
            return null;
        }
        if(accounts.size()>1) {throw new RuntimeException("Result set not unique");
        }
        return accounts.get(0);
    }


    public void updateAccount(Account account) {
        super.getJdbcTemplate().update("update account set name=? ,balance=? where id=?",account.getName(),account.getBalance(),account.getId()); }}Copy the code

D: Create the bean. XML configuration file

One caveat: If you haven’t used JdbcTemplate, you might be wondering what’s the following DriverManagerDataSource, which is Spring’s built-in data source

<?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 the service layer -->
    <bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <! -- Configure the account persistence layer -->
    <bean id="accountDao" class="cn.ideal.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <! -- Configure data source -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/ideal_spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root99"></property>
    </bean>
</beans>
Copy the code

E: testing

/** * Using Junit unit tests: test our configuration */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService as;

    @Test
    public void testTransfer(a) {
        as.transfer("Zhang"."Bill".500f);
    }
Copy the code

(2) XmL-based method

The first thing to do is to modify the configuration file, where the aop and TX namespaces need to be introduced

Configure the business layer persistence layer and data source nothing to say, just copy over, here are our really important configuration

A: Configure the transaction manager

The object that actually manages transactions is already available to us in Spring

Using Spring JDBC or iBatis persistence data can be used when the org. Springframework). The JDBC datasource. DataSourceTransactionManager

When using Hibernate for persistence data can use org. Springframework. Orm. Hibernate5. HibernateTransactionManager

Where the data source is imported

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>
Copy the code

B: Configure transaction notification

Transaction notifications and attribute configuration require the introduction of transactional constraints, TX and AOP namespaces and constraints

This is where the transaction manager can be introduced

<! -- Configure notification for transactions -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">

</tx:advice>
Copy the code

C: Configure transaction attributes


allows you to configure the properties of the transaction. There are a few properties to familiarize yourself with. The isolation level of the transaction can be looked at briefly. What we’re going to focus on here is how do you configure it

  • Name: Specifies the name of the method you want to add to a transaction. You can use wildcards, such as * for all find methods. * for methods whose names start with find, the second has higher priority

  • Isolation: Specifies the isolation level of the transaction, indicating that the DEFAULT isolation level of the database is used, which is DEFAULT

    • Read Uncommitted

      • Spring identity: ISOLATION_READ_UNCOMMITTED

      • Dirty reads are allowed, but updates are not lost. That is, if one transaction has already started writing data, the other transaction is not allowed to write at the same time, but other transactions are allowed to read the data on the row

    • Read Committed

      • Spring identity: ISOLATION_READ_COMMITTED

      • Only committed data can be read, which solves the dirty read problem. A read transaction allows other transactions to continue accessing the row, but an uncommitted write transaction prevents other transactions from accessing the row

    • Repeatable Read (Repeatable Read)

      • Spring identity: ISOLATION_REPEATABLE_READ
      • Whether to read the modified data committed by other transactions, which solves the problems of non-repeatable read and dirty read, but sometimes magic read data may occur. A transaction that reads data disallows a write transaction (but allows a read transaction), and a write transaction disallows any other transaction
    • Serializable

      • Spring identity: ISOLATION_SERIALIZABLE.
      • Provides strict transaction isolation. It requires serialization of transaction execution, to solve phantom read problem, transactions can only be executed one after another, not concurrently.
  • Propagation: Specifies the propagation attribute of a transaction. The default value is REQUIRED, indicating that there will be a transaction. It is generally used to add, delete, and change the transaction

  • Read-only: Indicates whether the transaction is read-only. The default value is false for read and write. This parameter is set to true only for common query methods

  • Timeout: Specifies the timeout period for the transaction. The default value is -1, indicating that the transaction never times out. If a value is specified, in seconds, this property is not normally used

  • Rollback -for: Specifies an exception. If this exception is generated, the transaction is rolled back. If other exceptions are generated, the transaction is not rolled back. There is no default value. Indicates that any exception is rolled back

  • No-rollback -for: Specifies an exception. If this exception is generated, the transaction is not rolled back. If other exceptions are generated, the transaction is rolled back. There is no default value. Indicates that any exception is rolled back

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <! -- Configure transaction properties -->
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" read-only="false"/>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>
Copy the code

D: Configure AOP pointcut expressions

<! - the configuration of aop -- -- >
<aop:config>! -- Configure pointcut expressions --><aop:pointcut id="pt1" expression="execution(* cn.ideal.service.impl.*.*(..) )"></aop:pointcut>
</aop:config>
Copy the code

E: Mapping pointcut expressions to transaction advice

Do this step in

<! - the configuration of aop -- -- >
<aop:config>! -- Configure pointcut expressions --><aop:pointcut id="pt1" expression="execution(* cn.ideal.service.impl.*.*(..) )"></aop:pointcut>
    <! Establish correspondence between pointcut expressions and transaction advice -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
Copy the code

F: All configuration codes

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

    <! -- Configure the service layer -->
    <bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <! -- Configure the account persistence layer -->
    <bean id="accountDao" class="cn.ideal.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <! -- Configure data source -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/ideal_spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root99"></property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
	<! -- Configure notification for transactions -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <! -- Configure transaction properties -->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <! - the configuration of aop -- -- >
    <aop:config>
        <! -- Configure pointcut expressions -->
        <aop:pointcut id="pt1" expression="execution(* cn.ideal.service.impl.*.*(..) )"></aop:pointcut>
        <! Establish correspondence between pointcut expressions and transaction advice -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
    
</beans>
Copy the code

(3) Annotation-based approach

We inherited JdbcDaoSupport directly to simplify the configuration, but it only works with XML, annotations are not allowed, so we need to do it the old-fashioned way. That is, define the JdcbTemplate in the Dao

A: Modify the bean. XML configuration file

The normal operation of annotations, open annotations, here we also configure the data source and JdbcTemplate

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

    <! -- configure the packages to scan when Spring creates containers -->
    <context:component-scan base-package="cn.ideal"></context:component-scan>

    <! - configured JdbcTemplate -- -- >
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <! -- Configure data source -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/ideal_spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root99"></property>
    </bean>

</beans>
Copy the code

B: Add basic annotations to the business layer and persistence layer

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
    
    // The following is the same
}
Copy the code
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    // The following is basically the same
    // Just change the original super.getJdbctemplate ().xxx to execute directly with jdbcTemplate
}
Copy the code

C: Configure the transaction manager in bean.xml

<! -- Configure transaction Manager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
Copy the code

D: Enable support for annotation transactions in bean.xml

<! -- Enable Spring support for annotation transactions -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
Copy the code

E: The business layer adds the @Transactional annotation

This annotation can appear on interfaces, classes, and methods

  • Appears on an interface, indicating that all implementation classes of that interface have transactional support

  • Appears on a class to indicate that all methods in the class have transactional support

  • Appears on a method to indicate that the method has transaction support

For example, in the following example, our class specifies that the transaction is read-only, but the following transfer also involves a write operation, so we add an annotation to the method with a readOnly value of false

@Service("accountService")
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements AccountService {... omit@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
    public void transfer(String sourceName, String targetName, Float money) {... Omit}}Copy the code

F: Test code

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService as;

    @Test
    public void testTransfer(a) {
        as.transfer("Zhang"."Bill".500f); }}Copy the code

(4) Based on pure annotation

The following is a pure annotation approach that can be removed from bean.xml, which is not very difficult

A: Configuration class annotations

@Configuration
  • Specifies that the current class is a configuration class of Spring, equivalent to the bean.xml file in XML

The following form is used to get the container

private ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
Copy the code

If you use Spring’s unit tests

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= SpringConfiguration.class)
public class AccountServiceTest {... }Copy the code

B: Specify scan package annotations

@ComponentScan

The @configuration annotation has already created the bean. XML file for us. Following our usual steps, we should specify the package to scan

  • Specifies which packages spring will scan when initializing the container, which in XML is equivalent to:

  • <! <context:component-scan base-package="cn. Ideal "></context:component-scan>Copy the code
  • BasePackages specifies the package to scan, which is the same as the value attribute in this annotation

C: Configure the properties file

@PropertySource

In the past, when creating a data source, the configuration information is written out directly. If you want to configure the content using properties, you need to use the @propertysource annotation

  • Used to load the configuration in the.properties file
  • Value [] specifies the location of the properties file. In the classpath, you need to add the classpath

SpringConfiguration class (equivalent to bean.xml)

/** * Spring configuration class */
@Configuration
@ComponentScan("cn.ideal")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {}Copy the code

D: Creates an object

@Bean

In XML, we do this by writing the Bean tag. Spring provides us with the @bean annotation instead of the tag

  • Writing an annotation to a method (only a method) means creating an object with that method and putting it into Spring’s container
  • Give the method a name, which is the ID of the bean in our XML, through the name attribute
  • In this way, the data from the configuration file is read in

JdbcConfig (JDBC configuration class)

/** * configuration classes related to connecting to databases */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /** * Create JdbcTemplate@param dataSource
     * @return* /
    @Bean(name="jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    /** * Create data source object *@return* /
    @Bean(name="dataSource")
    public DataSource createDataSource(a){
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        returnds; }}Copy the code

jdbcConfig.properties

Configure the configuration file separately

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ideal_spring
jdbc.username=root
jdbc.password=root99
Copy the code

TransactionConfig

/** * and transaction related configuration classes */
public class TransactionConfig {
    /** * used to create the transaction manager object *@param dataSource
     * @return* /
    @Bean(name="transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        return newDataSourceTransactionManager(dataSource); }}Copy the code

Conclusion:

1) this article is written here, learning any one foreign technology, only learning, to get all of its course, a lot of people have been immersed in a technology for many years, naturally has the special thinking and understanding, with a powerful experience, nature also can quickly get started, but if in the outside the state, or of this contact is not much, More need to know the ins and outs of a technology, but what source code analysis, all kinds of design patterns, and it is the latter, our first essence is to use it to do things, want to let him run, what they think I am not too smart people, directly to learn a lot of configuration, a pile of notes and a pile of proper nouns, too empty, it is hard to understand.

(2) we are trapped in a, for the state of learning and learning, may everyone SSM I also learn, everybody says SpringBoot simple and comfortable, I also to learn it, of course, the need of a lot of time because of some work or study, there is no way, but still feel, privately again see a door technology, can use some articles or information, Or find some video resources to see what this course brings. The best part of this course is that it solves problems that we have encountered or failed to consider before. Such a gradual learning method can help us to have an overall concept of some technologies and understand the connections between them.

③ In this article, I refer to “Spring Practice”, a horse’s video, and some reference content on Baidu and Google. Starting from a very simple case of adding, deleting, modifying and checking, through analyzing its transaction problems, step by step from dynamic proxy to AOP has been improved for many times. It involves some knowledge, such as dynamic proxy or JdcbTemplate, which may not be familiar to some friends, but I also use some space to explain. Writing such a long article is really a lot of effort, if you want to know more about Spring AOP, you can take a look at it, or as a simple reference. Used as a reference book when the hand reference

I really hope it can help you. Thank you again for your support. Thank you!

Tips: In the meantime, you can check out my previous article if you need it

Spring Framework for Easy entry (IOC and DI)

Juejin. Im/post / 684490…

At the end

If there is any shortage in the article, welcome to leave a message and exchange, thank you for your support!

If it can help you, then pay attention to me! If you prefer the way of reading wechat articles, you can follow my official account

Here we do not know each other, but in order to their dreams and efforts

A adhere to push the original development of technical articles of the public number: the ideal of more than twenty