03Spring AOP

0, traditional development in question - why the introduction of AOP

Before learning AOP, we look at a problem, the problem is to control database transactions:

[Image dump the chain fails, the source station may have security chain mechanism, it is recommended to save the picture down uploaded directly (img-hD0IQ5DG-1579010054247) (02.png)]
** in the transfer business, due to a variety of issues to be considered arise, it is necessary to add control transactions, and various methods of service repeat code must be added, resulting in redundant code, so consider whether we can duplicate code extracted, and at the right time to be inserted into the execution business code it? The above description can be abstracted as the following:
[Image dump the chain fails, the source station may have security chain mechanism, it is recommended to save the picture down uploaded directly (img-jn8S090E-1579010054249) (05.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VZLRivr1-1579010054250)(03.png)]

1. What is AOP

AOP is full of Aspect-Oriented Programmming, namely Aspect Oriented Programming (also called aspect-oriented programming), is a supplement to object-oriented programming (OOP), has now become a more mature programming.


In the traditional business process code, usually operates the transaction log records. While the use of OOP can be combined or inheritance code reuse way to achieve it, but if you want to achieve a certain function (such as logging), the same code will still be distributed to each process. In this way, if you want to turn off a feature, or to modify it, you must modify all related methods. This not only increases the workload of developers, but also improve the code error rate


To solve this problem, AOP ideas follow. AOP mechanism taken transversely drawn, dispersed in the code will be repeated each method extracted, then repeat the extracted application code to be executed where re-compile or run time, this extraction mechanism by way of lateral, conventional the OOP thinking clearly can not be done, because OOP can only achieve vertical reuse parent-child relationship. Although AOP is a new programming idea, but it is not a substitute for OOP, it is just an extension and supplement of OOP.


In AOP thought to be separately added to the transaction log, permissions and abnormal functions in a different class method by Aspect (section).

AOP allows developers to use when writing business logic can concentrate on their core business, without too much concern for other business logic, which not only improves the efficiency of development, but also enhances the maintainability of the code **


目前流行的AOP框架有两个,分别是Spring AOP和AspectJ.Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。AspectJ是一个基于Java语言的AOP框架。


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xsjlE0a0-1579010054250)(06.png)]


1.1、AOP的作用和优势

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zfJacOqm-1579010054251)(07.png)]



DI能够让相互协作的软件组件保持松散耦合,而面向切面编程(aspect-oriented programming,AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。

面向切面编程往往被定义为促使软件系统实现关注点的分离的一项技术。系统由许多不同的组件组成,每一个组件各负责一块特定的功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志,事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑组件中去,这些系统服务通常称为横切关注点,因为它们会跨越系统的多个组件。

如果将这些关注点分散到多个组件中去,你的代码将会带来多重的复杂性:

  • 实现系统关注点功能的代码将会重复出现在多个组件中。这意味着如果要修改这些关注点的逻辑,必须修改各个模块中的相关实现。即使把这些关注点抽象为一个独立的模块,其他模块只是调用它的方法,但方法调用还是会重复出现在各个模块中。
  • 组件会因为那些与自身核心业务无关的代码而变得混乱。一个向地址簿增加地址条目的方法应该只关注如歌添加地址,而不应该关注它是不是安全的或者是否需要支持事务。

AOP能够使这些服务模块化,并以声明的方式将他们应用到它们需要影响的组件中去。所造成的结果就是这些组件会具有更高的内聚性并且会更加关注自身的业务,完全不需要了解系统服务所带来的复杂性。总之,AOP能够确保POJO的简单性,

2、AOP原理:基于动态代理

在了解AOP是怎么来的,以及AOP的作用和优势之后我们再来看看AOP是怎么实现的,也就是AOP的底层原理是什么么?

查看官方的文档,我们看看AOP是怎么实现的?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-82r9Twk9-1579010054252)(08.png)]

通过以上分析,可以看出AOP的底层原理就是基于动态代理的,那么结合前面的IOC容器,可以看出 IOC容器使用了一种工厂模式,而AOP使用了代理模式。
AOP的代理模式分为两种,第一种是基于JDK动态代理,如果代理接口实现类使用这种,而如果是没有实现接口的类要实现代理采用CGLIB代理的方式。

2.1JDK代理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DwXfxMnS-1579010054252)(09.png)]


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uk0uW9in-1579010054253)(10.png)]

public class Client {

public static void main(String[] args) {

    //被代理对象
    final Producer producer = new Producer();

    /**
     * 动态代理特点:
     * 特点:随用随创建,随用随加载
     * 作用:不修该源码基础上对方法增强
     * 分类:
     *          基于接口动态代理
     *           基于子类动态代理
     * 基于接口动态代理:
     *          涉及的类:Proxy
     *          提供者:JDK官方
     *
     *          如何创建代理对象:
     *                  使用Proxy类中的newProxyInstance方法
     *
     *           创建代理对象的要求:
     *                  被代理类至少实现一个接口,如果没有则不能调用
     *
     *
     *
     *                  public static Object newProxyInstance(ClassLoader loader,
                                    Class<?>[] interfaces,
                                    InvocationHandler h)
     *  newProxyInstance 方法的参数:
     *              classLoader:类加载器,它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器:固定写法
     *              Class[]:字节码数组,它是用于让代理对象和被代理对象有相同的方法(有相同的接口即可):固定写法
     *              InvocationHandler:用于提供增强的代码;它是让我们写如何代理。
     *                                我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
     *                                此接口的实现类,一般谁用谁写
     *
     */
    //被代理对象
    Iproducer iproducer =

            (Iproducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
        /**
         * 作用执行被代理对象的任何接口方法都会经过该方法,该方法有拦截的功能
         * @param proxy   代理对象的引用 (一般不用)
         * @param method  当前执行的方法
         * @param args    当前执行方法所需要的参数
         * @return     和被代理对象有相同的返回值
         * @throws Throwable
         */

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //提供增强的代码
            Object returnValue = null;
            //1、获取方法执行的参数
            Float money = (Float)args[0];
            //2、判断当前方法是不是销售
            if("saleProduct".equals(method.getName()))
            returnValue = method.invoke(producer,money*0.8f);
            return returnValue;
        }
    });
       iproducer.saleProduct(10000f);

}

2.2CGLIB代理

import com.iyheima.Iproducer;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

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

/**
 * @Author Zhou  jian
 * @Date 2019 ${month}  2019/11/21 0021  16:30
 * 模拟一个消费者
 */
public class Client {

public static void main(String[] args) {
    final Producer producer = new Producer();

    /**
     * 动态代理特点:
     * 特点:随用随创建,随用随加载
     * 作用:不修该源码基础上对方法增强
     * 分类:
     *          基于接口动态代理
     *           基于子类动态代理
     * 基于子类动态代理:
     *          涉及的类:Enhancer
     *          提供者:第三方 Cglib
     *
     *          如何创建代理对象:
     *                  使用Enhancer类中的create方法
     *
     *           创建代理对象的要求:
     *                被代理类不能是最终类
     *
     *      create方法的参数:
     *                  class:字节码
     *                          它是指被代理对象的字节码
     *
     *                   callBack:用于提供增强的方法,我们一般写的都是该接口的子接口的实现类:MethodInterCeptor
     *                      特点:执行被代理对象的任何方法都会经过该方法
     *
     */

   Producer cglibProducer =(Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
        /**
         *特点:执行被代理对象的任何方法都会经过该方法
         * @param proxy:代理对象的引用 (一般不用)
         * @param method 当前执行的方法
         * @param args   当前执行方法所需要的参数
         * @param methodProxy  当前执行方法的代理对象 (用不上)
         * @return
         * @throws Throwable
         */
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            //提供增强的代码
            Object returnValue = null;
            //1、获取方法执行的参数
            Float money = (Float)args[0];
            //2、判断当前方法是不是销售
            if("saleProduct".equals(method.getName()))
                returnValue = method.invoke(producer,money*0.8f);
            return returnValue;
        }
    });
    cglibProducer.saleProduct(10000f);
}
	

3、AOP使用

在使用AOP之前声明一些术语:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ymiEThD5-1579010054254)(04.png)]


使用AspectJ实现AOP有两种方式:一种是基于XML的生明式;另一种是基于注解式的声明。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NY3BvXED-1579010054254)(13.png)]
在使用前导入相关的jar包:

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

    <!--  解析切入点表达式-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
	</dependencies>

在配置文件中,引入配置声明头文件:

	<?xml version="1.0" encoding="UTF-8"?>
	<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd">

3.1、基于XML的AOP使用

配置步骤总结:

	1、把通知Bean也交给spring来管理
    2、使用 aop:config标签 表明开始aop的配置
    3、使用 aop:aspect表明开始配置切面
            id:是给切面提供唯一标志
            ref:指定通知类的bean的id
    4、在  aop:aspect标签的内部使用对应的标签来配置通知的类型
            现在的示例是让logger方法printLogg在切入点之前执行,所以前置通知aop:before
            aop:before:表示配置前置通知
            method属性:用于指定logger类中哪个方法时前置通知

​ pointcut:用于指定切入点表达式,该表达式的含义是对业务层哪些方法增强

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在aop:config元素内。Spring配置文件中的元素下可以包含多个aop:config元素,一个aop:config元素中又可以包含属性和子元素,其子元素包括aop:pointcutaop:advisoraop:aspect.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uU0f2OMP-1579010054255)(11.png)]
(1)配置切面
在Spring的配置文件中,配置切面使用的是aop:aspect元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的SpringBean,定义完成之后,通过aop:aspecct元素的ref属性即可饮用该Bean.
(2)配置切入点
在Spring的配置文件中,切入点是通过aop:pointcut元素来定义的。当aop:pointcut元素作为aop:config元素的子元素定义时,表示该切入点是全局切入点,可以被多个切面所共享;当aop:pointcut元素作为aop:aspect元素的子元素时,表示该切入点只能对当前切面有效。

属性名称 描述
id 用于定义切入点的唯一标志名称
expression 用于指定切入点关联的切入点表达式
 切入点表达式的写法:
                    关键字: execution(表达式)
                    表达式:
                            访问修饰符 返回值 包名。包名.类名.方法名.(参数列表)
                            public     void   com.itheima.service.Impl.AccountServiceImpl. saveAccount()
                                    ①访问修饰符可以省略
                                            void   com.itheima.service.Impl.AccountServiceImpl. saveAccount()
                                    ②返回值可以使用通配符表示任意返回值
                                            *  com.itheima.service.Impl.AccountServiceImpl. saveAccount()
                                    ③包名可以使用通配符表示任意包,但是有几级包就需要些几个*
                                            * *.*.*.*.AccountServiceImpl.saveAccount()
                                    ③可以使用..表示当前包及其自爆
                                            * *..AccountServiceImpl.SaveAccount()
                                    ⑤类名和方法名都可以使用*实现通配
                                            * *..* *()
                                     ⑥参数列表可以直接书序类型:
                                                    基本类性可以直接写名称
                                                    引用类型可以写包名.类名的方式
                                      类型可以使用通配符,可以使用任意类型但必须有参数:*
                                      可以使用 ..表示有误参数均可
                            全通配写法:
                                        * *..*.*(..)
    实际开发中切入点表达式,切到业务层实现类下所有方法
      * com.itheima.Service.Impl.*.*(..)

(3)配置通知
在配置代码中,分别使用aop:aspect的子元素配置了5种常用通知,这些子元素不支持再使用子元素。但在使用时,可以指定一些属性:

属性名称 描述
pointcut 用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时植入该通知
pointcut-ref 指定一个已经存在的切入点名称,通常poincut和point-ref两个属性只需要使用其中之一
method 指定一个方法名,指将切面Bean中的该方法转换为增强处理
throwing

通知的种类:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PM0pTniP-1579010054256)(12.png)]
配置环绕通知的注意事项:

		/**
     * 环绕通知
     *
     * 问题:当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *          通过对比动态代理中的环绕通知代码,发现动态代理放入环绕通知有明确的切入点方法调用,而我们的代码中没有
     *
     *  Spring框架提供了一个接口:  ProceedingJoinPoint,该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
     *                              该接口可以作为环绕通知的方法参数,在程序执行时,Spring框架为我们提供改接口的实现类,为我们使用。

            Spring中的环绕通知:
                        它是Spring框架为我们提供的一种方式:可以在代码中手动控制增强方法何时执行的方式。
                            对应配置的方式;
 */
public Object aroundPrintLog(ProceedingJoinPoint pjp){
    Object rtValue = null;
    try {
        Object[] args = pjp.getArgs();//得到方法执行所需要的参数
        System.out.println("Logger类中的aroundPrintLog方法开始记录日志...前置");
        rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
        System.out.println("Logger类中的aroundPrintLog方法开始记录日志..后置");
        return rtValue;
    } catch (Throwable throwable) {
        throwable.printStackTrace();
        System.out.println("Logger类中的aroundPrintLog方法开始记录日志..异常");
        throw new RuntimeException("");
    }finally {
        System.out.println("Logger类中的aroundPrintLog方法开始记录日志..最终");
    }
}

3.2, annotation-based AOP use

XML-based declarative programming ASPECTJ achieve AOP although convenient, but there are some drawbacks that you want to configure a lot of code information in the file Spring, in order to solve this problem, AspectJ framework provides a set of annotations for the realization of AOP to replace Spring configuration file to achieve AOP functions configured bloated Code:

Notes name description
@Aspect It is used to define a section
@Pointcut Expression used to define the point, when using the method include the need to define a name and any parameters signatures to represent the entry point name. In fact, this method signature is a return value of void and the method body is empty of common method
@Before Is used to define pre-notification, BeforeAdvice corresponds, in use, generally need to develop a value attribute value, the attribute value specifies a pointcut expression (may be an existing entry points, entry points can be defined directly Expression formula)
@AfterReturing After notification is used to define opposed, corresponding to AfterReturingAdvice. When used to specify the pointcut.
@Around It is used to define around advice MethodInterceptor equivalent, when used to specify a Value attribute specifies the entry point notification is implanted
@AfterThrowing Exception handling is used to define the abnormality notification in untreated west
@After Is used to define the final final notice, regardless of whether there is an exception, the notice will be executed.

Need to open the following in the xml configuration file before using:

  <!--配置Spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!--配置spring开启注解Aop的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

	package com.itheima.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Author Zhou  jian
 * @Date 2019 ${month}  2019/11/21 0021  19:45
    用于记录日志的工具类,它里面提供了公共的代码
 */
@Component("logger")
@Aspect  //表述当前类是一个切面类
public class Logger {

@Pointcut("execution(* com.itheima.service.Impl.*.*(..))")
private void pt1(){}

    /**
     * 前置通知用于打印日志,计划让其在切入点方法之前执行(切入点方法就是业务层方法
     */
    @Before("pt1()")
    public void beforeprintLog(){
        System.out.println("前置通知Logger类中的printLog方法开始记录日志");
    }
    
        /**
     * 后置通知
     */
    @AfterReturning("pt1()")
    public void afterReturingprintLog(){
        System.out.println("后置通知Logger类中的afterprintLog方法开始记录日志");
    }
    
	    /**
     * 异常通知
     */
    @AfterThrowing("pt1()")
    public void afterThrowingprintLog(){
        System.out.println("异常通知Logger类中的afterReturingprintLog方法开始记录日志");
    }
    
    
        /**
     * 最终通知
     */
    @After("pt1()")
    public void afterprintLog(){
        System.out.println("最终通知Logger类中的afterprintLog方法开始记录日志");
    }

    /**
     * 环绕通知
     *
     * 问题:当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *          通过对比动态代理中的环绕通知代码,发现动态代理放入环绕通知有明确的切入点方法调用,而我们的代码中没有
     *
     *  Spring框架提供了一个接口:  ProceedingJoinPoint,该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
     *                              该接口可以作为环绕通知的方法参数,在程序执行时,Spring框架为我们提供改接口的实现类,为我们使用。

                Spring中的环绕通知:
                            它是Spring框架为我们提供的一种方式:可以在代码中手动控制增强方法何时执行的方式。
                                对应配置的方式;
     */
   // @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try {
            Object[] args = pjp.getArgs();//得到方法执行所需要的参数
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志...前置");
            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志..后置");
            return rtValue;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志..异常");
            throw new RuntimeException("");
        }finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志..最终");
        }
    }

4, the corresponding part of the official document reading

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-42lkOc7D-1579010054257)(14.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x8sVskuj-1579010054257)(15.png)]

Published 101 original articles · won praise 17 · views 10000 +

Guess you like

Origin blog.csdn.net/ZHOUJIAN_TANK/article/details/103980603