[Spring from entry to actual combat tutorial] Chapter 3 Spring AOP Detailed Explanation

3. Spring AOP

3.1 Prelude to AOP

3.1.1 Requirements

  • logs, to track the activities that are taking place during program execution;

  • Validation, processing of legitimate data during program execution;

3.1.2 Code

UserService interface and implementation class:

public interface UserService {
    
    void insertUser();

    void updateUser();

    void deleteUser(int id);

    List<String> selectUser();
}

public class UserServiceImpl implements UserService {
    @Override
    public void insertUser() {
        //在方法种主要业务逻辑代码开始之前,加入日志记录
        System.out.println("日志:用户新增...");

        System.out.println("用户新增的业务逻辑...");
    }

    @Override
    public void updateUser() {
        System.out.println("日志:用户修改...");

        System.out.println("用户修改的业务逻辑...");
    }

    @Override
    public void deleteUser(int id) {
        System.out.println("日志:用户删除...");

        System.out.println("用户删除的业务逻辑...");
    }

    @Override
    public List<String> selectUser() {
        System.out.println("日志:用户查询...");

        System.out.println("用户查询的业务逻辑...");
        return null;
    }
}

test:

public class ProxyTest {
    @Test
    public void test() {
        UserService userService = new UserServiceImpl();

        userService.insertUser();
        System.out.println("----------------------------------");

        userService.updateUser();
        System.out.println("----------------------------------");

        userService.deleteUser(10);
        System.out.println("----------------------------------");

        List<String> list = userService.selectUser();
        System.out.println(list);
    }
}

3.1.3 Problems

  • Code confusion: After more and more non-business requirements (logging and verification, etc.) are added, the original business methods expand dramatically. Each method must also take care of multiple other concerns while handling the core logic;

  • Code dispersion: Take the log requirement as an example, just to meet this single requirement, you have to repeat the same log code multiple times in multiple modules (methods). If logging requirements change, all modules must be modified;

3.2 Proxy mode

Use the proxy mode to solve the above problems:

     The principle of the proxy design pattern: use a proxy to wrap the object, and then replace the original object with the proxy object. Any call to the original object must pass through the proxy. The proxy object decides whether and when to transfer the method call to the original object.
    
    Usually, the proxy mode is used to deal with two problems:
        1. Control access to the base object;
        2. Add additional functions when accessing the base object;

3.2.1 Static proxy

    Each business interface needs to have a corresponding proxy class and implement the business interface;

/**
 * 静态代理
 * 要求:代理类与目标类必须实现同一个接口
 */
public class UserServiceProxy implements UserService {
    private UserService userService;

    /**
     * 目的:将代理类和目标类绑定
     * 在创建代理类的同时传入目标类对象
     */
    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void insertUser() {
        //1、代理功能
        System.out.println("日志记录:用户新增...");
        //2、调用目标类的方法,让核心业务逻辑执行
        userService.insertUser();
    }

    @Override
    public void updateUser() {
        System.out.println("日志记录:用户修改...");
        userService.updateUser();
    }

    @Override
    public void deleteUser(int id) {
        System.out.println("日志记录:用户删除..." + id);
        userService.deleteUser(id);
    }

    @Override
    public List<String> selectUser() {
        System.out.println("日志记录:用户查询...");
        return userService.selectUser();
    }
}

Remove the log code of the business layer and test:

@Test
public void testStaticProxy() {
    UserService userService = new UserServiceProxy(new UserServiceImpl());

    userService.insertUser();
    System.out.println("----------------------------------");

    userService.updateUser();
    System.out.println("----------------------------------");

    userService.deleteUser(10);
    System.out.println("----------------------------------");

    List<String> list = userService.selectUser();
    System.out.println(list);
}
  • Advantages: simple and easy to understand;

  • Disadvantages: After the project is huge, there are too many business interfaces, resulting in too many proxy classes;

3.2.2 JDK dynamic proxy

    JDK dynamic proxy (Dynamic Proxy API), which is a feature introduced in JDK1.3, the core API is the Proxy class and the InvocationHandler interface. Its principle is to use the reflection mechanism to generate the bytecode of the proxy class at runtime.

  • JDK dynamic proxy: use the dynamic proxy in the Java reflection mechanism to implement the InvocationHandler interface;

  • Requirements: The target class must be in the form of interface + implementation class;

/**
 * JDK动态代理
 * 必须实现InvocationHandler接口
 */
public class LoggerProxy implements InvocationHandler {
    /**
     * 执行目标(最终要执行业务逻辑类)方法
     *
     * @param proxy  代理类对象
     * @param method 目标方法对象
     * @param args   目标方法的参数值列表
     * @return 目标方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //日志记录的代码
        System.out.println("代理日志:目标方法--前置" + method.getName() + "....");
        
        //执行目标方法(业务逻辑方法)
        Object result_val = null;
        try {
            result_val = method.invoke(targetClass.newInstance(), args);
            System.out.println("代理日志:目标方法--返回" + method.getName() + "....");
        } catch (Exception e) {
            System.out.println("代理日志:目标方法--异常" + method.getName() + "....");
        }
        
        System.out.println("代理日志:目标方法--后置" + method.getName() + "....");
        return result_val;
    }

    /**
     * 目标对象的Class类型
     */
    private Class targetClass;

    /**
     * 获取代理对象:就是动态实现目标类接口的实现类
     * newProxyInstance(): 获取实现目标接口的代理对象
     * 注意:JDK中的动态代理,要求业务类必须是接口+实现类的形式
     */
    public Object getProxy(Class targetClass) {
        this.targetClass = targetClass;
        return Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(), this);
    }
}

PersonService interface and implementation class:

public interface PersonService {
    void insertPerson();
}

public class PersonServiceImpl implements PersonService {
    @Override
    public void insertPerson() {
        System.out.println("新增Person的业务逻辑...");
    }
}

test:

public class ProxyTest {
    @Test
    public void testDynamicProxy() {
        //获取代理对象
        UserService userService = (UserService) new LoggerProxy().getProxy(UserServiceImpl.class);
        System.out.println(userService);

        //通过代理对象调用指定的方法:具体执行流程,代理类调用指定的方法,然后通过invoke调用目标类的方法
        userService.insertUser();
        System.out.println("----------------------------------");

        userService.updateUser();
        System.out.println("----------------------------------");

        userService.deleteUser(10);
        System.out.println("----------------------------------");

        List<String> list = userService.selectUser();
        System.out.println(list);
        System.out.println("----------------------------------");

        PersonService personService = (PersonService) new LoggerProxy().getProxy(PersonServiceImpl.class);
        personService.insertPerson();
    }
}
  • Advantages: One class can represent all services;

  • Disadvantages: It is implemented using the reflection mechanism, which is difficult to understand and not easy to maintain; JDK dynamic proxy: the business class must be in the form of interface + implementation class;

3.2.3 Cglib dynamic proxy

    Cglib dynamic proxy implements the MethodInterceptor interface under the cglib package. Currently, Spring is using the Cglib dynamic proxy.

public class CglibProxy implements MethodInterceptor {
    /**
     * @param obj    CGLib动态生成的代理类对象
     * @param method 目标方法对象
     * @param args   目标方法的参数值列表
     * @param proxy  代理类对方法的代理对象
     * @return 目标方法的返回值
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Cglib动态代理...");
        
        //执行目标方法(业务逻辑方法)
        Object result_val = null;
        try {
            result_val = method.invoke(targetClass.newInstance(), args);
        } catch (Exception e) {
        }
        return result_val;
    }

    /**
     * 目标对象的Class类型
     */
    private Class targetClass;

    /**
     * 获取代理对象
     * <p>
     * 注意:JDK中的动态代理,要求业务类必须是接口+实现类的形式
     */
    public Object getProxy(Class targetClass) {
        //为目标对象target赋值
        this.targetClass = targetClass;
        Enhancer enhancer = new Enhancer();
        //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(targetClass);
        //设置回调
        enhancer.setCallback(this);
        //创建并返回代理对象
        Object result = enhancer.create();
        return result;
    }
}

test:

public class ProxyTest {
   @Test
    public void testCglibProxy() {
        UserService userService = (UserService) new CglibProxy().getProxy(UserServiceImpl.class);
        System.out.println(userService);

        userService.insertUser();
        System.out.println("----------------------------------");

        userService.updateUser();
        System.out.println("----------------------------------");

        userService.deleteUser(10);
        System.out.println("----------------------------------");

        List<String> list = userService.selectUser();
        System.out.println(list);
    }
}

3.3 Overview of AOP

    The full name of AOP is "Aspect Oriented Programming", translated as "Aspect-Oriented Programming", similar to OOP (Object-Oriented Programming), it is also a programming idea.
    
    Usually, we use OOP (object-oriented) thinking according to the business, and divide the application into multiple different business modules. The core functions of each module only provide services for specific business areas, such as orders in the e-commerce system The module, commodity module, and inventory module serve to maintain the order information, commodity information, and inventory information of the e-commerce system respectively.

    But in addition, there are often some non-business general functions in the application, such as log management, authority management, transaction management, exception management, etc. Although these general functions have nothing to do with the business of the application, almost all business modules will use them, so these general function codes can only be embedded in multiple different business modules in a horizontally distributed manner. This will undoubtedly generate a lot of repetitive code, which is not conducive to the reuse of various modules.

    You may think, can these repetitive codes be encapsulated into public functions, and then explicitly called in business modules, can it also reduce repetitive codes? Yes, doing so can indeed reduce repetitive code to a certain extent, but it also increases the coupling between business code and public functions. Any modification to public functions will affect all related business codes.

    Unlike the vertical parent-child inheritance relationship in OOP, AOP is realized through a horizontal extraction mechanism. It extracts some non-business common functions in the application and maintains them separately, and defines in a declarative way (such as configuration files, annotations, etc.) how these functions should be used in that application, rather than in the code of the business module call directly.

    Although the design of public functions is somewhat similar, traditional public functions have no other means other than direct hard calls in the code. AOP provides a set of flexible and diverse implementation methods for this problem (such as proxy proxy, interceptor, bytecode translation technology, etc.), which can complete the call and modification of these general functions without modifying any business code .

    The goals of AOP programming and OOP programming are the same, both are to reduce the repetitive code in the program, so that developers can focus more on the development of business logic, but the implementation methods of the two are quite different.

    OOP is like an "embroidery needle", which is a graceful choice. It uses inheritance and composition to carefully compile a set of classes and objects for all modules involving common functions, so as to reduce repetitive code. The goal. AOP, on the other hand, is more like a "hatchet knife", a bold and unrestrained choice, with drastic regulations, all methods under a certain package and category are processed together.

    AOP is not used to replace OOP, but an extension of OOP to solve the problems encountered in OOP programming.

  • The main programming object of AOP is aspect, and aspect is a modular cross-cutting concern;

    • Focus: Where the enhancement code is placed. For example, adding log records to the Service layer of student information management, then the Service layer is the focus;

    • Crosscutting: The business process of a regular module is Web -> Service -> Dao. If we want to log in the Service layer of all modules, then this is crosscutting;

    • Modularization: Extract the enhanced code that was previously scattered in each module and concentrate it in a certain module or class, then this is modularization;

  • When applying AOP programming, you still need to define public functions, but you can clearly define where and how this function is applied, and you don't have to modify the affected classes. In this way, cross-cutting concerns are modularized into special objects (aspects);

  • The benefits of AOP: the logic of each thing is located in one place, the code is not scattered, and it is easy to maintain and upgrade. The business module is more concise and only contains the core business code;

3.4 AOP Terminology

name illustrate
Joinpoint The core concept of AOP refers to a well-defined point during program execution, such as method invocation, class initialization, object instantiation, etc. In Spring, a join point is a method of a target class that can be intercepted by a dynamic proxy.
Pointcut (entry point) Also known as cut point, it refers to which Joinpoints are to be intercepted, that is, the intercepted connection points.
Advice Refers to the code to be executed after intercepting the Joinpoint, that is, the enhanced content of the entry point.
Target Refers to the target object of the agent, also known as the advised object.
Weaving Refers to the process of applying the enhanced code to the target object to generate a proxy object.
Proxy Refers to the generated proxy object.
Aspect Advice and pointcuts together make up an aspect.

Example:

Notification type:

notify illustrate
before (pre-notification) Advice method executes before target method call
after (post notification) The advice method is called after the target method returns or an exception
after-returning (notify after return) The notification method will be called after the target method returns
after-throwing (throwing exception notification) The notification method will be called after the target method throws an exception
around (around notification) The advice method will wrap the target method

3.5 AOP development steps

3.5.1 Add the corresponding jar package

Method 1: Import jar package

 Method 2: maven dependency configuration

<dependencies>
    <!-- spring核心包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.3.18.RELEASE</version>
    </dependency>
    <!-- springbean包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>4.3.18.RELEASE</version>
    </dependency>
    <!-- springcontext包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.18.RELEASE</version>
    </dependency>
    <!-- spring表达式包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>4.3.18.RELEASE</version>
    </dependency>
    <!-- springAOP包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.3.18.RELEASE</version>
    </dependency>
    <!-- springAspects包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>4.3.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

3.5.2 Namespace and label specification

Add the aop namespace and aop tag specification to the spring configuration file:

xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd

3.5.3 Writing aspect classes

Define an ordinary Java class and add functional methods to it:

/**
 * 日志切面类:日志记录功能
 */
public class LogAspect {

    /**
     * 前置通知:在目标方法执行之前实现的功能
     */
    public void beforeMethod() {
        System.out.println("AOP日志记录:前置通知......");
    }
}

Sectional complete code:

/**
 * 日志切面类:日志记录功能
 */
public class LogAspect {

    /**
     * 前置通知:在目标方法执行之前实现的功能
     * 参数:JoinPoint连接点对象,可获取当前要执行的目标方法的信息
     */
    public void beforeMethod(JoinPoint joinPoint){
        //通过连接点对象获取方法的名称
        String methodName = joinPoint.getSignature().getName();
        //方法的参数值列表
        Object[] args = joinPoint.getArgs();
        System.out.println("AOP日志记录:前置通知......" + methodName + Arrays.toString(args));
    }
    /**
     * 后置通知:在目标方法执行之后实现的功能
     */
    public void afterMethod(JoinPoint joinPoint){
        //通过连接点对象获取方法的名称
        String methodName = joinPoint.getSignature().getName();
        //方法的参数值列表
        Object[] args = joinPoint.getArgs();
        System.out.println("AOP日志记录:后置通知......" + methodName + Arrays.toString(args));
    }

    /**
     * 返回通知:在目标方法有返回值之后实现的功能
     * 通过方法的参数获取目标方法的返回值数据
     */
    public void afterReturnMethod(JoinPoint joinPoint, Object resultValue){
        //通过连接点对象获取方法的名称
        String methodName = joinPoint.getSignature().getName();
        //方法的参数值列表
        Object[] args = joinPoint.getArgs();
        System.out.println("AOP日志记录:返回通知......" + methodName + Arrays.toString(args));
        System.out.println("方法的返回值为:" + resultValue);
    }

    /**
     * 异常通知:在目标方法抛出异常之后实现的功能
     * 通过方法参数获取目标方法抛出的异常对象
     */
    public void afterThrowMethod(JoinPoint joinPoint, Exception ex){
        //通过连接点对象获取方法的名称
        String methodName = joinPoint.getSignature().getName();
        //方法的参数值列表
        Object[] args = joinPoint.getArgs();
        System.out.println("AOP日志记录:异常通知......" + methodName + Arrays.toString(args));
        System.out.println("方法抛出的异常对象:" + ex);
    }

    /**
     * 环绕通知:综合以上所有的通知
     * 环绕通知必须配置ProceedingJoinPoint参数
     */
    public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("AOP日志记录:环绕通知之前置......");
        try {
            //手动执行目标方法
            //proceed()就是在执行目标方法,其返回值为目标方法的返回值
            Object resultValue = proceedingJoinPoint.proceed();
            System.out.println("AOP日志记录:环绕通知之返回......" + resultValue);
        } catch (Throwable throwable) {
            System.out.println("AOP日志记录:环绕通知之异常......" + throwable);
            throwable.printStackTrace();
        }
        System.out.println("AOP日志记录:环绕通知之后置......");
    }
}

3.5.4 Configure AOP

  • Configure both the target class and the aspect class into the IOC container;

  • Configure AOP: configure pointcut expressions, configure aspect classes, and configure notifications;

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

    <!--1.配置bean-->
    <bean id="userService" class="com.newcapec.service.impl.UserServiceImpl"/>
    <bean id="personService" class="com.newcapec.service.impl.PersonServiceImpl"/>
    <bean id="log" class="com.newcapec.aspect.LogAspect"/>

    <!--2.配置AOP-->
    <aop:config>
        <!--
            公用切点表达式
                id: 切点表达式的名称
                expression: 切点表达式的内容
            注:
                aop:pointcut定义在aop:config中,是公用级别的切点表达式
                aop:pointcut定义在aop:aspect中,是切面级别的切点表达式
     -->
        <aop:pointcut id="exp1" expression="execution(* com.newcapec.service.impl.*Impl.*(..))"/>
        <aop:pointcut id="exp2" expression="execution(public void com.newcapec.service.impl.UserServiceImpl.insertUser())"/>
        <aop:pointcut id="exp3" expression="execution(* com.newcapec.service.impl.UserServiceImpl.selectUser(..))"/>
        <aop:pointcut id="exp4" expression="execution(* com.newcapec.service.impl.PersonServiceImpl.insertPerson(..))"/>

        <!--2.1 配置切面类-->
        <aop:aspect ref="log">
            <!--2.2 配置通知-->
            <!--
                通知类型:
                    <aop:before>:表示前置通知
                    <aop:after>:表示后置通知
                    <aop:after-returning>:表示返回通知
                    <aop:after-throwing>:表示异常通知
                    <aop:around>:表示环绕通知
                通知类型属性:
                    method: 切面对象中执行该通知的方法,通知的方法名称
                    pointcut: 切点表达式
                        告知spring,通知应用在哪些方法(目标方法,业务逻辑方法)上
                    pointcut-ref: 引用公共切点表达式
                    returning: 在返回通知中接收方法返回值的参数名称
                    throwing: 在异常通知中接收方法抛出的异常对象的参数名称
            -->
            <!--2.3 配置切点表达式-->
            <!--
                编写规则:以execution()包裹起来的内容
                通配符:
                    1. 星号 *
                    2. 双句号 ..
                        使用在包中表示该包下以及子包下
                        使用在参数类型列表中表示不限制方法的参数类型
                示例:
                    1.单个方法,具体方法
                    语法:execution(方法访问修饰符 返回值类型 方法所在的包名.类名.方法名(参数类型,...))
                    例如:execution(public void com.newcapec.dao.UserDaoImpl.insertUser())
                    2.多个方法,批量方法:*符号作为通配符
                    语法:execution(* 方法所在的包名.类名.*(..))
                    例如:execution(* com.newcapec.dao.UserDaoImpl.*(..))
                    例如:execution(* com.newcapec.*.dao.*.*(..))
                    3.通配,所有方法
                    语法:execution(* *.*.*(..))
            -->

            <!--精准匹配-->
            <!--<aop:before method="beforeMethod" pointcut="execution(public void com.newcapec.service.impl.UserServiceImpl.insertUser())" />-->
            <!--模糊匹配-->
            <!--<aop:before method="beforeMethod" pointcut="execution(* com.newcapec.service..*Impl.*(..))"/>-->

            <aop:before method="beforeMethod" pointcut-ref="exp1"/>
            <aop:after method="afterMethod" pointcut-ref="exp2"/>
            <aop:after-returning method="afterReturnMethod" pointcut-ref="exp3" returning="resultValue"/>
            <aop:after-throwing method="afterThrowMethod" pointcut-ref="exp4" throwing="ex"/>
            <!--<aop:around method="aroundMethod" pointcut-ref="exp1"/>-->
        </aop:aspect>
    </aop:config>
</beans>

3.5.5 Testing

public class AOPTest {

    @Test
    public void test() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = ac.getBean("userService", UserService.class);

        userService.insertUser();

        System.out.println("----------------------------------");

        userService.updateUser();
        System.out.println("----------------------------------");

        userService.deleteUser(1);
        System.out.println("----------------------------------");

        userService.selectUser();
        System.out.println("----------------------------------");

        PersonService personService = ac.getBean("personService", PersonService.class);
        personService.insertPerson();
    }
}

Note: After an exception occurs in the target method, the return notification will not be executed. However, when no exception occurs in the target method, the exception notification will not be executed.

3.5.6 Aspect priority

<aop:aspect>The order attribute is configured in the tag, and the attribute value is a number. The smaller the number, the higher the priority, and the earlier the aspect function is executed

/**
 * 另一个切面类
 */
public class OtherAspect {

    public void beforeM(){
        System.out.println("OtherAspect的beforeM方法.....");
    }
}
<bean id="other" class="com.newcapec.aspect.OtherAspect"/>

<aop:config>
    <!--
        切面优先级:决定哪个切面中通知先执行,哪个后执行
        在前置通知中:优先级高的先执行,优先级低的后执行
        在后置通知中:优先级高的后执行,优先级低的先执行
        <aop:aspect>标签中的order属性决定了优先级的高低,其值越小优先级越高
    -->
    <aop:aspect ref="log" order="2">
    </aop:aspect>
    <aop:aspect ref="other" order="1">
        <aop:before method="beforeM" pointcut-ref="exp1"/>
    </aop:aspect>
</aop:config>

Guess you like

Origin blog.csdn.net/ligonglanyuan/article/details/124787652