I. Dependencies between methods

As we said in the Spring overview, coupling in programs generally involves coupling between classes, and coupling between methods. We reduce the dependencies between classes through the Ioc container provided by Spring. Today we look at dependencies between methods, using a simulated business code case to analyze problems in the program. Procedure is as follows

Persistence layer interface

public interface AccountMapper {
    // Query all methods
    List<Account> selectAll(a);
    // find the account by id
    Account selectById(Integer id);
    // Update the account
    int update(Account account);
}
Copy the code

Business layer implementation code

package com.zxwin.service.impl;

@Service("accountService")
public class AccountServiceImplOld implements AccountService {
    private AccountMapper accountMapper;
    // The utility class for transaction management is closed, and the implementation class is not given
    private TransactionManage transactionManage;
    @Autowired
    public void setTransactionManage(TransactionManage transactionManage) {
        this.transactionManage = transactionManage;
    }
    @Autowired
    public void setAccountMapper(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }
    
    @Override
    public List<Account> findAllAccount(a) {
        List<Account> accounts = null;
        // Start the transaction
        transactionManage.beginTransaction();
        try {
            accounts = accountMapper.selectAll();
            // Commit the transaction
            transactionManage.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            // Rollback if an exception occurs
            transactionManage.rollback();
        }finally {
            // The operation to release resources
            transactionManage.release();
        }
        return accounts;
    }
    // Transfer operation
    @Override
    public void transfer(Integer sourceId, Integer targetId, double money) {
        transactionManage.beginTransaction();
        try {
            Account source = accountMapper.selectById(sourceId);
            Account target = accountMapper.selectById(targetId);
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            accountMapper.update(source);
            accountMapper.update(target);
            transactionManage.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            transactionManage.rollback();
        }finally{ transactionManage.release(); }}}Copy the code

Through the above example, we can find that each method in the business code needs to control transactions through the TransactionManage class, which causes a lot of redundancy in the code and reduces our development efficiency. To make matters worse, if methods in the TransactionManage class are modified (such as parameters) we need to change the code in the entire business layer. This is the dependency that exists between methods.

To reduce this dependency, Spring provides our solution, AOP (aspect oriented programming). Before we get to AOP, we can modify the above example code using the techniques we already know.

The problem with the business in the above example is that there is a lot of duplicate transaction control code and the transaction control code is fixed. We can enhance business classes through dynamic proxy techniques, so that business classes only need to perform normal business operations, and transactional operations are enhanced through proxy classes. Let’s take a look at the dynamic proxy application.

Encapsulating classes for dynamic proxy implementations

@Component
public class ServiceAop {
    // The proxy class needs to hold a reference to the proxy class
    private AccountService accountService;
    private TransactionManage transactionManage;
    @Autowired
    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }
    @Autowired
    public void setTransactionManage(TransactionManage transactionManage) {
        this.transactionManage = transactionManage;
    }

    // Get the enhanced business proxy class
    @Bean("accountServiceProxy")
    public AccountService getAccountService(a){
        // Use the third tool class to implement the dynamic proxy method through the subclass
        return (AccountService) Enhancer.create(accountService.getClass(),
                new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                        Object result = null;
                        // Enhancements: enable transactions
                        transactionManage.beginTransaction();
                        try {
                            // Execute the proxied method
                            result = method.invoke(accountService,args);
                            // Enhancements: Commit transactions
                            transactionManage.commit();
                        } catch (Exception e) {
                            e.printStackTrace();
                            // Enhancements: rollback transactions
                            transactionManage.rollback();
                        }finally {
                            // Enhancement: Release resources
                            transactionManage.release();
                        }
                        returnresult; }}); }}Copy the code

The business layer code only needs to implement the business function

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    private AccountMapper accountMapper;
    private TransactionManage transactionManage;
    @Autowired
    public void setTransactionManage(TransactionManage transactionManage) {
        this.transactionManage = transactionManage;
    }
    @Autowired
    public void setAccountMapper(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    @Override
    public List<Account> findAllAccount(a) {
        List<Account> accounts = null;
        accountMapper.selectAll();
        return accounts;
    }
    @Override
    public void transfer(Integer sourceId, Integer targetId, double money) {
        Account source = null;
        Account target = null; source = accountMapper.selectById(sourceId); target = accountMapper.selectById(targetId); source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); accountMapper.update(source); accountMapper.update(target); transactionManage.commit(); }}Copy the code

This allows us to separate the business function from the transaction function through dynamic proxy, and even if the transaction function code changes we only need to change it in the enhancement section.

This is the solution to method dependency, and the above example is very helpful in understanding AOP technology in Spring.

Second, AOP concept

AOP(Aspect Oriented Programming) is a technology that realizes unified maintenance of program through precompilation and dynamic proxy at run time. Using AOP, each part of logic business can be separated, so as to reduce the degree of coupling between each part of logic business and improve the reusability of program. Improve development efficiency.

The purpose of AOP is to implement enhancements to existing functionality without modifying the source code while the program is running;

The advantages of AOP include reduced duplication of code; Improve development efficiency; Maintenance is convenient

The implementation of AOP relies on dynamic proxies, so we must first have some understanding of dynamic proxies, in order to better learn AOP

Terminology in AOP

JoinPoint: Join points are the points that are intercepted, which in Spring are methods (Spring only supports join points of method types). That is, all methods in the proxied object

Pointcut: Pointcut is the definition of which JoinPoints we want to intercept. Methods that need to be enhanced

Advice: Advice is what you do when you are intercepted in JoinPoint. Types of notifications: pre-notification, post-notification, exception notification, final notification, surround notification

Introduction: A special notification allows you to dynamically add methods or fields to a class at run time without modifying the code

Target: The Target object of the agent

Proxy: The resulting Proxy class when a class is woven and enhanced by AOP

Weaving is the process of applying enhancements to target objects to create new proxy objects

Aspect: Is a combination of a pointcut and an introduction

Iv. Classification of notices

Notifications are simply operations enhanced by proxied object methods

There are five main types of notification mentioned in the term above. What does each type of notification mean? Let’s look at the first four.

We know that AOP uses dynamic proxy techniques to implement enhancements to existing functionality while the program is running without modifying the program. Let’s look at an example

// This code implements enhancements in the dynamic proxy
InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try{
                    System.out.println("Advance notice");
                    // Execute the pointcut method
                    result = method.invoke(arithmetic, args);
                    System.out.println("Post notification");
                }catch (Exception e){
                    e.printStackTrace();
                    System.out.println("Exception notification");
                }finally {
                    System.out.println("Final notice");
                }
                returnresult; }};Copy the code

The above example explicitly gives the location of the four types of notification

Pre-notification is the action that takes place before a pointcut method; A post-advice is an action that is executed after the pointcut method has normally executed; Exception notification is an action performed after an exception occurs in a pointcut method; The final notification is executed whether the pointcut method executes correctly or not.

Surround notifications are a way that Spring gives us to manually control when notifications are executed in our code. More on that later.

Five, learning springAOP to clear things

The development phase

Programmers write the core business code

Extract common code and make it into notifications (at the end of development)

In a configuration file, the relationship between declared pointcuts and advice is the aspect

Operation phase

The Spring framework monitors the implementation of the pointcut method. Once the pointcut method is monitored to be executed, the proxy mechanism is used to dynamically create the proxy object of the target object, and the corresponding function of the notification is woven into the corresponding position of the proxy object according to the notification category to complete the complete code logic

Six, AOP case

We simulate the usual development of log printing requirements, the first requirements of Serivce layer work through AOP to achieve log printing function

Dependencies to import


      
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>SpringTest</artifactId>
    <version>1.0 the SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.13. RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.8. RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <! Parsing pointcut expressions -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>
</project>
Copy the code

Let’s start by looking at the business layer code and custom notification classes (classes use methods to implement enhancements to methods in the business layer)

package com.zxwin.service.impl;
// Simulate business layer code
public class UserServiceImpl implements UserService {
    @Override
    public int updateUser(a) {
        System.out.println("Update method executed");
        return 0;
    }
    @Override
    public void save(a) {
        System.out.println("Save method executed");
    }
    @Override
    public void delete(int i) {
        System.out.println("Delete method executed"); }}Copy the code
package com.zxwin.advice;
// Define the notification class
public class Logger {
    // The notification method
    public void printLog(a){
        System.out.println("Logger started printing logs."); }}Copy the code

Let’s look at the specific configuration of AOP


      
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <! --> < span style = "box-sizing: border-box! Important;
    <bean id="userService" class="com.zxwin.service.imple.UserServiceImpl"/>
    <! -- Give the notification class to Spring management -->
    <bean id="logger" class="com.zxwin.advice.Logger"/>
    <! -- Configure AOP 1- use the tag < AOP :config> to start configuring AOP 2- use the tag < AOP :config> under the tag < AOP :config> to start configuring the aspect ID property to uniquely identify the aspect ref property used to execute the notification bean 3- Under the tag < AOP :aspect> use tags of different advice types such as < AOP :before> to start configuring the advice method property to identify which method in the advice class to execute the advice. The pointcut property identifies pointcuts (which methods in the business class need to be enhanced by the advice) The value of pointcut requires a pointcut expression format: the access modifier method returns the package name of the value. Package name Package name Class name Method name (parameter) -->
    <aop:config>
        <aop:aspect id="loggerAdvice" ref="logger">
            <aop:before method="printLog" pointcut="execution( public void com.zxwin.service.imple.UserServiceImpl.save())"/>
        </aop:aspect>
    </aop:config>
</beans>

Copy the code

The test class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TestAOP {
    @Autowired
    private UserService userService;
    @Test
    public void testLoggerAdvice(a){ userService.save(); }}Copy the code

The execution result

Logger starts printing logs and executes the save method

You can see that the print log method is executed before the save() method, indicating that our AOP is configured correctly.

We can also write pointcut expressions as wildcards in the configuration above, as follows

  • Access modifiers can be omitted, and the expression of equivalent to the above execution (void com. Zxwin. Service. Imple. UserServiceImpl. The save ())

  • The method’s return value can use wildcards said any return value type, and amend the expression to execution (* com. Zxwin. Service. Imple. UserServiceImpl. The save ())

  • Package names can use wildcards to represent any package. Note that there are several levels of package that require several *

 * *.*.*.*.UserServiceImpl.save()
Copy the code

Package names can also be used as.. Represents any package and subpackage, and can be rewritten as

* *.. UserServiceImpl.save()Copy the code
  • Both class and method names can also be represented with wildcards
* *.. *. * ()Copy the code

This writing matches the two methods updateUser() and save() in the UserServiceImpl class.

  • The list of parameters

    • You can write the type name directly

      • Primitive types can be written directly to the type name
      • Reference types need to be written to the full class name
    • You can also use the wildcard * to indicate any type

      * *.. UserServiceImpl.*(*) // Matches the delete(int I) methodCopy the code
    • Can use.. Indicates whether the parameter is valid or not

      * *.. UserServiceImpl.*(*) // All three methods will be matchedCopy the code

Through the above expression writing learning, we can write the expression * *.. *. * (..) This expression is an all-wildcard character that matches all methods of the current project and is not recommended.

Our usage principle is to match all methods * com.zxwin.service.impl.*.*(..).

7. Configure other types of notification

In the AOP example above, we only configured pre-notification. We know that there are five types of notification in SpringAOP: pre-notification, post-notification, exception notification, final notification, and surround notification. We will then complete the other notifications based on the above example

Notification class modification

public class Logger {
    // Pre-notification
    public void beforeLog(a){
        System.out.println("Pre-notification beforeLog");
    }
    // post notification
    public void afterReturningLog(a){
        System.out.println("AfterReturningLog");
    }
    // Exception notification
    public void afterThrowingLog(a){
        System.out.println("Exception Notification afterThrowingLog");
    }
    // Final notification
    public void afterLog(a){
        System.out.println("Final notification afterLog"); }}Copy the code

Modify section configuration

<aop:config>
	<aop:aspect id="loggerAdvice" ref="logger">
            <! Configure pointcut expressions that can only be called on the current aspect when placed in the < AOP :aspect> tag. This tag can also be configured under the < AOP :config> tag and can be used by all facets -->
            <aop:pointcut id="servicePointcut" expression="execution( * com.zxwin.service.impl.*.*(..) )"/>
            <! -- Pre-notification -->
            <aop:before method="beforeLog" pointcut-ref="servicePointcut"/>
            <! -- Post-notification -->
            <aop:after-returning method="afterReturningLog" pointcut-ref="servicePointcut"/>
            <! -- Exception notification -->
            <aop:after-throwing method="afterThrowingLog" pointcut-ref="servicePointcut"/>
            <! -- Final notice -->
            <aop:after method="afterLog" pointcut-ref="servicePointcut"/>
    </aop:aspect>
</aop:config>
Copy the code

The test class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TestAOP {
    @Autowired
    private UserService userService;

    @Test
    public void testLoggerAdvice(a) { userService.save(); }}Copy the code

BeforeLog After the save method is executed afterReturningLog Final notification afterLog

8. Surround notification

Circular notifications in Spring are a way that Spring gives us to manually control when notifications are executed in our code.

Let’s take a look at spring’s configuration for surround notification

<aop:config>
        <aop:aspect id="loggerAdvice" ref="logger">
            <! Configure pointcut expressions that can only be called on the current aspect when placed in the < AOP :aspect> tag. This tag can also be configured under the < AOP :config> tag and can be used by all facets -->
            <aop:pointcut id="servicePointcut" expression="execution( * com.zxwin.service.impl.*.*(..) )"/>
            <! -- Surround notification -->
            <aop:around method="aroundLog" pointcut-ref="servicePointcut"/>
        </aop:aspect>
</aop:config>
Copy the code

We mentioned above that surround notification is actually a way that Spring gives us to manually control notification execution in our code

public class Logger {
    // Pre-notification
    public void beforeLog(a){
        System.out.println("Pre-notification beforeLog");
    }
    // post notification
    public void afterReturningLog(a){
        System.out.println("AfterReturningLog");
    }
    // Exception notification
    public void afterThrowingLog(a){
        System.out.println("Exception Notification afterThrowingLog");
    }
    // Final notification
    public void afterLog(a){
        System.out.println("Final notification afterLog");
    }
    Spring provides us with the ProceedingJoinPoint interface, which calls the proceed() method of the interface, which quite explicitly calls the pointcut method. Complete the wrap notification */ around the proceed() method
    public void aroundLog(ProceedingJoinPoint pjp){
        try {
            beforeLog();
            pjp.proceed(pjp.getArgs());
            afterReturningLog();
        } catch (Throwable throwable) {
            afterThrowingLog();
            throwable.printStackTrace();
        }finally{ afterLog(); }}}Copy the code

AOP annotation configuration

<! Add @enableAspectJAutoProxy annotations to the config class if you use a pure annotation configuration.
<aop:aspectj-autoproxy/>
Copy the code
@Aspect // Identifies the class as a facet class
@Component("logger")
public class Logger {
    // Section expression
    @Pointcut("execution(* com.zxwin.service.impl.*.*(..) )"
    public void pointcut(a){}
    
    // Pre-notification
    @Before("pointcut()")
    public void beforeLog(a){
        System.out.println("Pre-notification beforeLog");
    }
    // post notification
    @AfterReturning("pointcut()")
    public void afterReturningLog(a){
        System.out.println("AfterReturningLog");
    }
    // Exception notification
    @AfterThrowing("pointcut()")
    public void afterThrowingLog(a){
        System.out.println("Exception Notification afterThrowingLog");
    }
    // Final notification
    @After("pointcut()")
    public void afterLog(a){
        System.out.println("Final notification afterLog");
    }
    // @around ("pointcut()") does exactly what the other four do
    public void aroundLog(ProceedingJoinPoint pjp){
        try {
            beforeLog();
            pjp.proceed(pjp.getArgs());
            afterReturningLog();
        } catch (Throwable throwable) {
            afterThrowingLog();
            throwable.printStackTrace();
        }finally{ afterLog(); }}}Copy the code

Note: Configuring the other four notifications when using annotation configuration can cause problems with the order of invocation. Surround notification configured with annotations works fine.