Spring AOP aspect oriented programming

Posted by Kaizard on Sat, 20 Nov 2021 04:22:37 +0100


dd

Starting from this section, you will enter a new stage to learn AOP aspect oriented programming of Spring.
dd
dd

1, Getting to know AOP

1. Introduce AOP

spring provides A pluggable component technology. It sounds tall, but we often encounter such scenes in our daily life. For example, we have developed two software modules, A and B. suppose software module A is the user management module of the system and software module B is the employee management module of the system. These two modules have their own business processing classes, and the processes they execute are also executed from top to bottom. Now I put forward A requirement for these two modules. In the process of business processing from top to bottom, I want to filter permissions. Only users with permissions can access the corresponding modules. You may add the corresponding business code for permission judgment before running the actual code. Add one in module A and one in module B. This is no problem. But one day, the project manager said that we don't need these two functions now. What should we do? At this point, you should open its corresponding code and remove all permission control codes. Is there A better way at this time? The answer is yes. Spring AOP aspect oriented programming can solve this problem well.

The so-called aspect oriented programming means that in the running process of our software, we can add corresponding extended functions before or after execution. This extended function is called facet.

Take the current example. For software module A and software module B, we first enter the permission aspect to judge the permission before the actual code runs. So we can call it permission aspect. The permission aspect judges the permission of the system user. If A system user has the permission to access module A or module B, it will be executed downward in turn. If the user does not have access rights, this aspect will block it out. This permission aspect plays the role of intercepting the application before execution. Then, as the program runs, after the program runs, we can add another log aspect. The function of log aspect is to record the running parameters and output results at several points in the current software running process. Facilitate the debugging and tracking of our program. Here, both the permission aspect and the log aspect are additional to the two software modules, and the two software modules will not perceive the existence of these two aspects at runtime. At the same time, if the business logic of our system changes one day, we don't need the permission aspect and log aspect, we can quickly remove these two aspects from the current system by simply adjusting them in the configuration file. After what I just described, is it A bit like all kinds of plug-ins installed in our browser. No matter what browser we use, these browsers support plug-in technology.

Spring AOP is aspect oriented programming. AOP's approach is to encapsulate general and business independent functional abstractions into faceted classes. The facet can be configured before or after the execution of the target method to truly achieve plug and play. Its ultimate goal is to extend the program behavior without modifying the source code.

2. Get to know Spring AOP

This section completes the project configuration of an AOP step by step through cases. First, learn what functions AOP can bring us emotionally.

First create a Maven project, then create dao package, service package and AOP package, and then create dao class and service class. Because I only study AOP now and haven't come to the actual development case, the methods in it are written schematically only to let us understand AOP. The code is as follows:

UserDao.java

package com.haiexijun.dao;

/**
 *User table Dao
 */
public class UserDao {
    public void insert(){
        System.out.println("Add user data");
    }
}

EmployeeDao.java

package com.haiexijun.dao;

/**
 * Employee table Dao
 */
public class EmployeeDao {
    public void insert(){
        System.out.println("New employee data");
    }
}

EmployeeService.java

package com.haiexijun.service;

import com.haiexijun.dao.EmployeeDao;

/**
 * Employee services
 */
public class EmployeeService {
    private EmployeeDao employeeDao;
    public void entry(){
        System.out.println("Execute employee enrollment business logic");
        employeeDao.insert();
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}

UserService.java

package com.haiexijun.service;

import com.haiexijun.dao.UserDao;

/**
 * User services
 */
public class UserService {
    private UserDao userDao;

    public void createUser(){
        System.out.println("Execute the business logic for creating users");
        userDao.insert();
    }

    public String generateRandomPassword(String type,Integer length){
        System.out.println("Press"+type+"Mode generation"+length+"Bit random password");
        return "abcdeffdasf";
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

These classes can't be more common. If I put forward a new requirement at this time, whether it's a service method or a dao method. I want to print out their respective execution time in the console before execution. Through these time information, I can understand the specific time of day when our application load is the highest. At this point, you might think, isn't it simple? Just add a sout to each of our methods, and then print out the time of the current system? Of course, there is no problem in doing so, but have you found that there are still many classes in our system, and there are a large number of methods in each class. Even if you copy and paste this sentence many times, it is a very troublesome thing? At the same time, one day your project manager told you that we no longer need the output of these time. What should I do? Do you want to open each line of code and delete it? Such work is obviously very low-level and error prone. If you are an architect and know spring very well, you can use spring AOP technology to intercept these methods before running, print the time, and then execute the code inside the method. Spring AOP is a technology that extends the original program behavior without modifying the source code.

I'll give a step-by-step demonstration, and then explain each detail in detail in the following sections.

First, introduce the required dependencies in pom.xml. Although the aop module is included in the spring context dependency, another dependency aspectjweaer should be introduced. The function of aspectjweaer module will be explained in detail later. Just bring it in first. So we can write pom.xml.

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

        <!--aspectjweaer yes Spring AOP Underlying dependencies-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.8.RC2</version>
        </dependency>

    </dependencies>

Then create applicationContext.xml under the resources directory. First configure it as follows. This configuration is different from the previous configuration, with xmlns: aop, etc
Some of the following constraints are https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#xsd-schemas-aop You can find it in 10.1.

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

</beans>

Then configure the bean in the current container

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

    <bean id="userDao" class="com.haiexijun.dao.UserDao"/>
    <bean id="employeeDao" class="com.haiexijun.dao.EmployeeDao"/>
    <bean id="userService" class="com.haiexijun.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="employeeService" class="com.haiexijun.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>

</beans>

Next, create an entry class to test:

package com.haiexijun;

import com.haiexijun.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService= context.getBean("userService", UserService.class);
        userService.createUser();
    }
}

After operation, see the following figure:

Add a new package aspect under the aop package, which creates a class of MethodAspect, which is an aspect for methods. We define a section method to print the execution time. The facet method must add an additional parameter of JoinPoint type (connection point), through which the information of the target class / method can be obtained. What are target classes and target methods? In fact, the method we really want to execute is called the target method, and the class to which the target method belongs is the target class. When our program is outputting, we must print what time, which class and what method are running.

package com.haiexijun.aspect;

import org.aspectj.lang.JoinPoint;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Section class
 */
public class MethodAspect {
    /**
     * Slice method, print execution time
     * @param joinPoint The facet method must add an additional parameter of JoinPoint type (connection point)
     *                  The information of the target class / method can be obtained through the connection point
     */
    public void printExecutionTime(JoinPoint joinPoint){
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String now= sdf.format(new Date());
        String ClassName= joinPoint.getTarget().getClass().getName();//Gets the name of the target class
        String MethodName=joinPoint.getSignature().getName();//Gets the name of the target method
        System.out.println("---->"+now+":"+ClassName+"."+MethodName);

    }

}

After writing this class, spring doesn't know which methods the printExecutionTime method executes before it runs. Therefore, we still need to configure aop in ah applicationContext to explain the scope of my aspect class.
Specific configuration method:

    <!--AOP to configure-->
    <bean id="methodAspect" class="com.haiexijun.aspect.MethodAspect"/>
    <aop:config>
        <!--pointcut It means tangent point execution The expression describes the scope of the slice-->
        <!--Below execution The expression states that the slice acts on com.haiexijun On all methods of all classes under the package-->
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*(..))"/>
        <!--Define facet class-->
        <aop:aspect ref="methodAspect">
            <!--before notice Adviceļ¼ŒRepresents that the methods in the facet class are executed before the target method runs-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

The configuration will be explained later.

Let's go back to the main program and run it directly:

Our previous requirements for time printing have been completed. If you don't need this function one day, just comment out this aop configuration information in applicationContext. Here, I believe you have a certain understanding of Spring AOP. In the next section, you will learn more about the configuration.

2, AOP related concepts

1.AOP key concepts

Relationship between Spring AOP and AspectJ
Eclipse AspectJ is an aspect oriented programming language based on the Java platform. AspectJ has a complete system, which can realize AOP aspect oriented programming at run time. However, as spring AOP, it does not use AspectJ completely. As spring AOP, AspectWeaver is used to realize class and method matching. Spring AOP uses proxy mode to expand the function of object runtime.

Several key concepts:

Related conceptsexplain
AspectAspect, a specific pluggable component function class. Usually, one aspect only implements one general function
Target Class/MethodTarget class and target method refer to the methods related to business to be executed
PiontCutThe pointcut uses the execution expression to specify which classes of the system the facet should act on
JoinPiontConnection point, whether the object containing the metadata of the target class / method is included in the running process
AdviceNotification, which specifies the execution timing of specific aspects. Spring contains five different types of notifications.

2.JoinPiont core method

methodexplain
Object getTarget()Gets the target object in the IoC container
Signature getSignature()Get target method
Object[] getArgs()Gets the parameters of the target method

I have already demonstrated the above two methods. Let's demonstrate the use of getArgs() method.
Add the following code to the method of the facet class

        Object[] args=joinPoint.getArgs();
        System.out.println("---->Number of parameters:"+args.length);
        for (Object arg:args){
            System.out.println("---->Parameters:"+arg);
        }

The meaning of this code is to get the parameters passed in by the called method
We run the portal directly because userService.createUser(); No parameters are passed in, so the digit of args is 0

We change the method calling code of the entry to

        userService.generateRandomPassword("MD5",10);

Then run the program:

3.PointCut tangent expression


Public can not be written because it is public by default.
If you want the program to match the class name at the end of Service:

<aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*Service.*(..))"/>

public can be removed

<aop:pointcut id="pointcut" expression="execution( * com.haiexijun..*Service.*(..))"/>

If you only want to capture the method starting with create:

<aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.create*(..))"/>

If you only want to capture methods that do not pass in parameters, directly remove the two points in ():

<aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*())"/>

If you only want to capture a method with only 2 parameters, use * to represent the parameters:

<aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*(*,*))"/>

You can also specify the type of parameters for the method:

<aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*(String,*))"/>

In actual development, it is often used to match the specified classes. It usually does not interfere with the number of types and return values of parameters. It is good to use wildcards.

3, AOP notification

1. Five notification types

noticeexplain
Before AdvicePre notification, executed before the target method runs
After Returning AdviceNotify after return, and execute after the target method returns data
After ThrowingException notification, which is executed after the target method throws an exception
After AdvicePost notification, executed after the target method runs
Around AdviceSurround notification, the most powerful notification, customize the execution time of notification, and determine whether the target method can run

There is also a special "notification" - Introduction enhancement, which is not an official notification provided by Spring It is an enhancement to a class, not a method. It has nothing to do with notification. It is essentially an interceptor. Other notifications sit on the method, while the introduction enhancement acts on the class. Introduction enhancement allows you to add new properties or methods to the target class at runtime. Introduction enhancement allows you to change the behavior of the class at runtime and make the class change dynamically with the running environment Just let's have a look. We use less for daily development.

The following focuses on these five notification types.

We added a new method doAfter() to the facet class written earlier. You can see from the name. This is the processing method for post notification. As a post notification, we also need to add the JoinPiont connection point parameter. As the method content, we can print it casually and know its execution time.

    /**
     * Post notification
     * @param joinPoint
     */
    public void doAfter(JoinPoint joinPoint){
        System.out.println("<----Trigger post notification");
    }

Then configure the following in xml:

    <aop:config>
        <!--pointcut It means tangent point execution The expression describes the scope of the slice-->
        <!--Below execution The expression states that the slice acts on com.haiexijun On all methods of all classes under the package-->
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*Service.*(..))"/>
        <!--Define facet class-->
        <aop:aspect ref="methodAspect">
            <!--before Notification, which means that the method in the aspect class is executed before the target method runs-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
            <!--after adopt-->
            <aop:after method="doAfter" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

After operation:

Let's try the post return notification. The target method returns data and executes. The post return notification needs to pass in two parameters. The first is JoinPoint, and the second is Object, which represents the return value of the target method.

    /**
     * Notify on return
     * @param joinPoint Connection point
     * @param ret Return value of the target method
     */
    public void doAfterReturning(JoinPoint joinPoint,Object ret){
        System.out.println("<----Notify on return:"+ret);
    }

For post return notification, our xml configuration is also somewhat different.

    <aop:config>
        <!--pointcut It means tangent point execution The expression describes the scope of the slice-->
        <!--Below execution The expression states that the slice acts on com.haiexijun On all methods of all classes under the package-->
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*Service.*(..))"/>
        <!--Define facet class-->
        <aop:aspect ref="methodAspect">
            <!--before Notification, which means that the method in the aspect class is executed before the target method runs-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
            <!--after adopt-->
            <aop:after method="doAfter" pointcut-ref="pointcut"/>
            <!--Notify on return-->
            <aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

Run, because the createUser method we wrote has no return value, the ret notified after return is null

The last is the exception notification, which also needs to pass in two parameters. The first is the same, and the second is Throwable, which is the exception thrown for the method.

    /**
     * Exception notification
     * @param joinPoint Connection point
     * @param th Method
     */
    public void doAfterThrowing(JoinPoint joinPoint,Throwable th){
        System.out.println("<----Notification after exception:"+th.getMessage());
    }

Configuration xml file

    <!--AOP to configure-->
    <bean id="methodAspect" class="com.haiexijun.aspect.MethodAspect"/>
    <aop:config>
        <!--pointcut It means tangent point execution The expression describes the scope of the slice-->
        <!--Below execution The expression states that the slice acts on com.haiexijun On all methods of all classes under the package-->
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*Service.*(..))"/>
        <!--Define facet class-->
        <aop:aspect ref="methodAspect">
            <!--before Notification, which means that the method in the aspect class is executed before the target method runs-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
            <!--after adopt-->
            <aop:after method="doAfter" pointcut-ref="pointcut"/>
            <!--Notify on return-->
            <aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
            <!--Exception notification-->
            <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="th"/>
        </aop:aspect>
    </aop:config>

We simulate an exception

    public void createUser(){
        if (1==1){
            throw new RuntimeException("User already exists");
        }
        System.out.println("Execute the business logic for creating users");
        userDao.insert();
    }

Run the following code:

2. Circular notice

This section uses a case to explain the use of surround notifications.
In our actual work, you may encounter this situation. Due to the continuous accumulation of data, the number of users is increasing. It may cause the system in our production environment to become slower and slower. How can we locate which method is slow? The actual development is not so easy, because there may be thousands of classes and methods in a large system. Should we add corresponding code for each method to capture their execution time? This is too inefficient. At this point, you must immediately reflect that AOP can solve this problem very well. We only need to capture the start time of the method before the method is executed, and the end time of the method after the method is executed. Don't you know how long the method has been executed by subtracting the two? If the time exceeds the specified range, we will save the output in the log. Then a new problem extends here. As the five notification types in AOP, do you use pre notification or post notification? In fact, I can't, because I have to save a time before running and a time after running. This is certainly impossible for a single notification. But fortunately, spring provides us with one of the most powerful options - surround notification. Using surround notification, we can control the complete operation cycle of the target method. We will explain it through examples below.

First, let's go back to the previous project:
For the two Daos and two service s, we need to check the time on each of their methods. If a single method takes more than one second, we think that the execution of this method is too slow and needs to be optimized. Let's do it based on spring AOP.

Let's write the facet method of the facet class:
Instead of JoinPoint, the surrounding notification uses the procedure JoinPoint, which is a special connection point. It is an upgraded version of JoinPoint. In addition to the original functions, it can also control whether the target method is executed. ProceedingJoinPoint has a processed () method to execute the target method. The processed () method will return an Object object Object. This Object object is the return value of the target method after execution.

package com.haiexijun.aspect;


import org.aspectj.lang.ProceedingJoinPoint;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Section class
 */
public class MethodAspect {

    /**
     * Around Advice 
     * @param pjp ProceedingJoinPoint Is a special connection point. It is an upgraded version of JoinPoint
     *            In addition to the function, you can also control whether the target method is executed.
     */
    public Object printExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        try {
            //Get start time
            Long startTime=new Date().getTime();
            Object obj= pjp.proceed();//Execution target method
            //Get end time
            Long endTime=new Date().getTime();
            //execution time
            long runTime=endTime-startTime;

            //If the running time of the target method exceeds 1 second, the log is output
            if (runTime>1000){
                String className=pjp.getTarget().getClass().getName();
                String methodName=pjp.getSignature().getName();
                SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String now =sdf.format(new Date());
                System.out.println("======"+now+":"+className+"."+methodName+" ( "+runTime+" ms) ====== ");
            }

            return obj;

        } catch (Throwable e) {
            //Around the notification, it usually only catches the generation of the corresponding exception
            //However, if the target method generates an exception, it is regarded as the generated exception
            //In most cases, it will be thrown out.
            //Exceptions are thrown out because in our current system, future runtime may
            //There is not only one notification. If this exception is digested in the current surround notification
            //That means that other subsequent processes will not catch this exception, which may cause some unexpected problems
            System.out.println("Exception message:"+e.getMessage());
            throw e;
        }

    }

}

Then configure the surround notification in xml:

    <!--AOP to configure-->
    <bean id="methodAspect" class="com.haiexijun.aspect.MethodAspect"/>
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*(..))"/>
        <aop:aspect ref="methodAspect">
            <!--Around Advice -->
            <aop:around method="printExecutionTime" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

Then, for the test, let createUser sleep for a few seconds:

    public void createUser(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Execute the business logic for creating users");
        userDao.insert();
    }

Then, write the entry for program execution:

package com.haiexijun;

import com.haiexijun.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService= context.getBean("userService", UserService.class);
        userService.createUser();
    }
}

When running, the log of the timeout method will be output:

4, Configuring Spring AOP based on annotations

I still use the previous project to demonstrate, removing both the bean and AOP of applicationContext. It is then configured through annotations, and then xml annotation scanning is set.

<?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/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!--Turn on Component annotation scanning-->
    <context:component-scan base-package="com.haiexijun"/>
    <!--Enable Spring IoC Annotation mode for-->
    <aop:aspectj-autoproxy/>
    
</beans>

Add notes for dao and service:
UserDao.java

package com.haiexijun.dao;

import org.springframework.stereotype.Repository;

/**
 *User table Dao
 */
@Repository
public class UserDao {
    public void insert(){
        System.out.println("Add user data");
    }
}

EmployeeDao.java

package com.haiexijun.dao;

import org.springframework.stereotype.Repository;

/**
 * Employee table Dao
 */
@Repository
public class EmployeeDao {
    public void insert(){
        System.out.println("New employee data");
    }
}

EmployeeService.java

package com.haiexijun.service;

import com.haiexijun.dao.EmployeeDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * Employee services
 */

@Service
public class EmployeeService {

    @Resource
    private EmployeeDao employeeDao;
    public void entry(){
        System.out.println("Execute employee enrollment business logic");
        employeeDao.insert();
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}

UserService.java

package com.haiexijun.service;

import com.haiexijun.dao.UserDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

/**
 * User services
 */

@Service
public class UserService {

    @Resource
    private UserDao userDao;

    public void createUser(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Execute the business logic for creating users");
        userDao.insert();
    }

    public String generateRandomPassword(String type,Integer length){
        System.out.println("Press"+type+"Mode generation"+length+"Bit random password");
        return "abcdeffdasf";
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

The above is IoC configuration, and the following is AOP configuration

Open the section class and configure it as follows:

package com.haiexijun.aspect;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Section class
 */

@Component//Mark the current class as an IoC component
@Aspect //Indicates that the current class is a faceted class
public class MethodAspect {

    /**
     * Around Advice 
     * @param pjp ProceedingJoinPoint Is a special connection point. It is an upgraded version of JoinPoint
     *            In addition to the function, you can also control whether the target method is executed.
     */
    //Surround notification with PointCut tangent expression as parameter
    @Around("execution(public * com.haiexijun..*.*(..))")
    public Object printExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        try {
            //Get start time
            Long startTime=new Date().getTime();
            Object obj= pjp.proceed();//Execution target method
            //Get end time
            Long endTime=new Date().getTime();
            //execution time
            long runTime=endTime-startTime;

            //If the running time of the target method exceeds 1 second, the log is output
            if (runTime>1000){
                String className=pjp.getTarget().getClass().getName();
                String methodName=pjp.getSignature().getName();
                SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String now =sdf.format(new Date());
                System.out.println("======"+now+":"+className+"."+methodName+" ( "+runTime+" ms) ====== ");
            }

            return obj;

        } catch (Throwable e) {
            //Around the notification, it usually only catches the generation of the corresponding exception
            //However, if the target method generates an exception, it is regarded as the generated exception
            //In most cases, it will be thrown out.
            //Exceptions are thrown out because in our current system, future runtime may
            //There is not only one notification. If this exception is digested in the current surround notification
            //That means that other subsequent processes will not catch this exception, which may cause some unexpected problems
            System.out.println("Exception message:"+e.getMessage());
            throw e;
        }

    }



}

Of course, there are other notes for notifications, such as @ After, @ Before, @ AfterReturning, etc. There is no demonstration here.

Finally, we open the program entry class and run it. There is no problem:

5, Application of agent mode in AOP

1. Implementation principle of spring AOP

In the future, when we look for a job, there is a question that the interviewer will often mention. That is, please tell me what is the underlying implementation principle of Spring AOP?
This problem is too common. What is the underlying implementation principle of Spring AOP? As follows:

Spring implements dynamic function expansion based on proxy mode, including the following two forms:
The first is that if the target class implements the interface, the function expansion will be realized through JDK dynamic agent. The second is that if the target class does not implement the interface, the Spring AOP bottom layer implements the function extension through CGLib, a third-party component.

At this point, it involves a core problem, what is the agent model?

2. Static proxy

Proxy mode refers to the function expansion of the original object through the proxy object.

What is the proxy object? In fact, it can be seen everywhere in our daily life. For example, you go to a new city and want to rent a house. What would you do at this time? Is it that I read these rent seeking information from telegraph poles everywhere? Certainly not. Most people's first choice is to find an intermediary company and query the houses nearby that meet my requirements through the database of the intermediary system to see if the price is appropriate. If you think it's OK, the intermediary will take me to a field trip to see if I'm satisfied. If you're satisfied, deal. If you're not satisfied, keep looking. In turn, landlords also rely on intermediaries. Because the landlord often has his own job, it is impossible to open the door to the renter with the key every day, right? At this time, the landlord can entrust the intermediary. The landlord gives the key to the intermediary and asks the intermediary to take the renter to see the house. You can see that the staff of the intermediary is a typical agent. This case is called proxy mode when it is put into our program.

The core idea of the so-called agent model is that we should create an agent class and hold the most original delegate class in the agent class. As proxy classes and delegate classes, they should implement the same interface together. The customer completes the functions required by the customer through the agent class. According to the example just now, the customer class is the renter. The agent class is the agent, while the principal class is the landlord. As intermediaries and landlords, their purposes are the same. To rent out the house. It is precisely because they have the same purpose that they have implemented a common interface. This interface provides a method for renting a house. Both the agent class and the delegate class implement the logic of renting a house. As a proxy class, it internally holds the object of the delegate class, so after the proxy class is instantiated, that is, during the execution of the proxy object, additional behavior can be generated on the original logic. For example, this intermediary agent class, after seeing the house for the customer, in addition to delivering the original rent to the original landlord, it also charges the customer an agency fee, which is the additional extension logic. It's the same in the program.

As an agent model, how can we implement it? Let's demonstrate it with code.

Let's create a new Maven project:
Add a service package and an interface in the service package. As emphasized earlier, both proxy and delegate classes should implement the same interface, which is called UserService. Let's simulate the real environment. In this user service interface, we provide a createUser() method, and all implementation classes need to implement this method. At the same time, create a new implementation class under the service package, named UserServiceImpl, which implements the UserService interface and the methods inside. Let's write schematically.

UserService.java

package com.haiexijun.service;

public interface UserService {
    public void createUser();
}

UserServiceImpl.java

package com.haiexijun.service;

public class UserServiceImpl implements UserService {

    @Override
    public void createUser() {
        System.out.println("Execute the business logic for creating users");
    }
}

As this code, our call is actually very simple. Add an Application class, add the Main method in the class, and then write the code:

package com.haiexijun.service;

public class Application {
    public static void main(String[] args) {
        UserService userService=new UserServiceImpl();
        userService.createUser();
    }
}

Then run, of course.

But I put forward a new request to print out the execution time of this method. What should I do? We have met this requirement before when learning Spring AOP. But it is done by developing aspect classes. But what if we put it in our agent mode? If you need to implement the extension of this function, you must create the corresponding proxy class based on the UserService interface. At the same time, the corresponding concrete implementation is held in the proxy class.

Let's take a look at the specific methods:
Create a new class named UserServiceProxy under the service package. The English word of proxy is proxy. As the current proxy class, its core feature is to hold the object of the delegate class. Define a private UserService type property. Next, the key point is to define a construction method with parameters. The parameter is UserService. This parameter is passed in from the outside when our proxy class is instantiated, and the internal UserService is assigned at the same time. Does this mean that when we create a proxy object, we assign a value to the UserService of the internal class through the implementation class of a UserService passed in from the outside, which is equivalent to holding the object of the delegate class. At the same time, don't forget that both proxy and delegate classes should implement the same interface, that is, UserService, and then implement the createUser() method. In the current proxy class method, because the object of the delegate class has been held before, we can initiate the specific responsibilities of the delegate class in the createUser method, such as the createUser. At the same time, before this method is executed, we can also extend other code, such as the running time of the current time. Is this the expansion of functions?

UserServiceProxy.java

package com.haiexijun.service;

import java.text.SimpleDateFormat;
import java.util.Date;

public class UserServiceProxy implements UserService {
    //The object that holds the delegate class
    private UserService userService;
    public UserServiceProxy(UserService userService){
        this.userService=userService;
    }

    @Override
    public void createUser() {
        System.out.println("======"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
        userService.createUser();
    }
}

At the same time, as our customers, that is, users, instead of directly facing UserServiceImpl, we use UserServiceProxy to call this proxy class. When the proxy class is called, a specific UserServiceImpl needs to be passed in.

application.java

package com.haiexijun.service;

public class Application {
    public static void main(String[] args) {
        UserService userService=new UserServiceProxy(new UserServiceImpl());
        userService.createUser();
    }
}

As a result, this is an additional extension of the functionality implemented through the proxy class

Proxy patterns can be nested.
For another example, you have never seen such a situation. A tenant rents the whole house and then rents the room to many other tenants. This form is very common in places with high house prices such as Beijing, Shanghai and Shenzhen. We also call this kind of tenant the second landlord.

That is also supported in proxy mode. Because both the delegate class and the proxy class implement the same interface. At the same time, when creating an object, it is allowed to pass in the implementation class of the corresponding interface. Therefore, we can create a new proxy UserServiceProxy1. The specific method is the same as before, and implement the UserService interface and method. Then we can extend some other business code in create and execute an output statement after the createUser method of UserService. At this time, two proxy classes appear in the system, one is executed before the createUser method, and the other is executed after the createUser method.

package com.haiexijun.service;

public class UserServiceProxy1  implements UserService{

    private UserService userService;
    public UserServiceProxy1(UserService userService){
        this.userService=userService;
    }

    @Override
    public void createUser() {
        userService.createUser();
        System.out.println("======Post extension function=====");
    }
}

The tenant must face the second landlord. The following is new UserServiceProxy 1, which is passed to the first landlord UserServiceProxy. It feels like a little dolls.

package com.haiexijun.service;

public class Application {
    public static void main(String[] args) {
        UserService userService=new UserServiceProxy1(new UserServiceProxy(new UserServiceImpl()));
        userService.createUser();
    }
}

After running, it is found that the additional expansion functions of the front and rear are printed out.

This is the subtlety of the agent model. It can realize the infinite level expansion of functions. But here, every time we expand our functions, we have to create a proxy class ourselves. This has a disadvantage. With the continuous expansion of our functions, each specific implementation class must have at least one proxy class. The proxy class should write itself according to this rule. In this way, if there are hundreds of specific business implementation classes in our system, it means that there are also hundreds of specific agent classes to extend responsibilities for specific implementation classes. This will make our system extremely bloated. For this way of using the proxy class that must be created manually, we call it static proxy. Static proxy is the simplest way to use proxy mode, but it is also the most troublesome way to use it.

When it comes to manual creation, there is the corresponding automatic creation. After JDK 1.2, the introduction of reflection mechanism makes it possible for us to automatically create proxy classes. Let's learn the dynamic agent corresponding to the static agent.

3.AOP underlying principle - JDK dynamic agent

Next, create a new Maven project. Because the function of JDK dynamic agent does not need to be applied to other third-party components, we do not need to add any dependencies in Maven. Then, take the UserService interface and UserServiceImpl implementation class of the previous case.

To realize the function extension of UserServiceImpl based on JDK dynamic proxy, first create an additional class ProxyInvocationHandler under the service package, which needs to implement a crucial interface InvocationHandler. We need to implement its invoke method. Did you meet this invoke there? Yes, I have encountered it in learning reflection before. I call the target method through invoke. The same goes for invoke here.

ProxyInvocationHandler implements InvocationHandler, and its functions are very clear. InvocationHandler is a reflection class provided by JDK, which is used to enhance the target Method in JDK dynamic proxy. The InvocationHandler implementation class is similar to the surround notification of the aspect class of Spring AOP. We enhanced the target Method in the invoke Method. The invoke Method contains three parameters. The first parameter Object represents the proxy class Object, which is usually automatically generated by our JDK dynamic proxy. The second parameter Method is the target Method Object, which describes the information of the target Method, including Method name, etc. The third parameter is an Object array, which represents the arguments of the target Method. This Method returns an Object representing the return value of the target Method after it is run. Finally, throwing Throwable means throwing the target Method exception.

package haiexijun.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ProxyInvocationHandler implements InvocationHandler {

    private Object target;//Target object

    public ProxyInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * Enhance the target method in the invoke method
     * @param proxy Proxy class object
     * @param method Target method
     * @param args Arguments to the target method
     * @return The return value after the target method runs
     * @throws Throwable Exception thrown by target method
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("======"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date())+" ========");
        Object ret =method.invoke(target,args);//Call target method
        return ret;
    }
}

Then write code in the Application class using:

package haiexijun.service;

import java.lang.reflect.Proxy;

public class Application {
    public static void main(String[] args) {
        //UserService is the target object
        UserService userService=new UserServiceImpl();
        ProxyInvocationHandler invocationHandler= new ProxyInvocationHandler(userService);
        //The invoke method needs to pass in Proxy from the Proxy class Proxy
        //Create a dynamic proxy class,
        // Create through the newProxyInstance method, pass in the class loader, the interface to be implemented by the class, and the InvocationHandler that extends the target method
        UserService userServiceProxy  = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),invocationHandler);

        userServiceProxy.createUser();

    }
}

However, the dynamic agent must implement an interface of an implementation class before it can run. If there is no implementation interface, the reflection process will inevitably report an error. However, in our actual situation, there are a large number of classes that do not implement interfaces. What should we do? At this time, spring provides us with another solution. CGLib, a third-party component that relies on spring, implements class enhancements.

4.CGLib implementation proxy class

CGLib is a runtime bytecode enhancement technology. The full name is Code Generation Library
When a class does not implement an interface, Spring AOP will extend it by generating the bytecode of the target inheritance class at runtime.

Here is the logic to generate the bytecode of the target inheritance class:

There is a Service class above, which has a findById query method by id number. Write the specific business code inside. You can see that the Service inside does not implement any interface. Obviously, the Jdk dynamic agent cannot extend it. When Spring sees that this class does not implement an interface, it will automatically use CGLib to extend the class through inheritance. This inherited class is automatically generated during the running of the JVM. Its generation rule is that the original name of the class is preceded, followed by two $$symbols, and then EnhancerByCGLIB (EnhancerBySpringCGLIB after Spring 5). Then inherit from the Service parent class. The findById method can be rewritten. In the method, super points to the business code of the parent class, and extended pre code and post code can be added. When the client calls, it faces this enhanced subclass.
Summary:
When the target class to be enhanced implements the interface, the AOP bottom layer calls the JDK dynamic agent. When the interface is not implemented, the AOP bottom layer calls the CGLib agent

Topics: Java JavaEE Spring Back-end