Spring源码深度解析,Spring源码核心技术,Aop (七)(附代码示例:)

七,Spring的 核心技术,Aop

一,Aop简介:(来自官网 :https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop.html)

     面向切面编程(AOP), 通过提供另一种思考程序结构的方式来补充面向对象程序(OOP)。OOP中模块化的关键单元是类,而在AOP中,模块化单元是切。切面实现了诸如跨越多种类型和对象的事务管理之类的关注点的模块化。

  然而看了官网感觉有点绕口。

   在软件业,AOP Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 (来自百度百科 https://baike.baidu.com/item/AOP/1332219?fr=aladdin);

通俗一点讲: 面向切面编程, 一般的web项目结构层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面,每一个对象与对象之间,是一个切面。方法与方法与之间,模块与模块之间,都是一个切面。 在项目中我们实现它们, 主要是在做日志记录,性能统计,安全控制,事务处理,异常处理。(来自百度百科 https://baike.baidu.com/item/AOP/1332219?fr=aladdin);

如对程序的运行,记录到 传入方法的参数,返回的参数,抛出的异常,以及,什么时候结束,对数据对象 进行 锁处理。

我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。(来自百度百科 https://baike.baidu.com/item/AOP/1332219?fr=aladdin);

按照正常的逻辑,我们可以这么做。  (此处解释,来自于:https://blog.csdn.net/q982151756/article/details/80513340)

Spring源码深度解析,Spring源码核心技术,Aop (七)(附代码示例:)_第1张图片

这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。

Spring源码深度解析,Spring源码核心技术,Aop (七)(附代码示例:)_第2张图片

 

同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。

Spring源码深度解析,Spring源码核心技术,Aop (七)(附代码示例:)_第3张图片

二,Aop 概念 (来自官网 :https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop.html

通过上面的一些例子,应该对 Spring Aop 有了一个大致的印象, 让我们看一下 Aop 的专业术语和概念。

  • Aspect (切面): 一般声明在 java类上,@Aspect 注解的常规类来实现切面 ,如何启用?如何声明

     要在Spring配置需要启用配置基于 @AspectJ 方面的Spring AOP和Spring支持使用 @AspectJ 切面;

     通过在spring  xml 配置中包含以下元素来启用@AspectJ支持:

 

< aop:aspectj-autoproxy />

    如果正在使用DTD,则仍可以通过向应用程序上下文添加以下定义来启用@AspectJ支持:

< bean  class = “org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator” />

  注解 通过: 

@EnableAspectJAutoProxy 标记在类上

 如何声明 一个 切面?

注解通过: 

@Aspect

xml 方式通过:

< bean  id = “myAspect”  class = “org.xyz.NotVeryUsefulAspect” > 
   < ! - 正常配置方面的属性 - > 
< / bean >

 

  • Join point (连接点): 程序执行期间的一个点,例如方法的执行或异常的处理。在Spring AOP中,连接点 始终表示方法执行。
  • Advice (增强):Advice 定义了在  Pointcut  里面定义的程序点具体要做的操作,它通过 beforeafter around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

        相应的代码示例:请看 3 代码示例:

  • Pointcut切点) : 指定的方法地址,标识相应 Advice 发生的位置 

  暂时只说一种

   切点的:表达式:    execution 表示执行,   public int  表示 切点的返回值,   下面的 包名,类名,方法名 括号里的参数 用(..) 表示多个参数。 *  表示  匹配任何返回值,  该包下面的所有的类

@Pointcut("execution(public int com.enjoy.Cap10.bean.Calculator.subtraction(..))")
@Pointcut("execution(public * com.enjoy.Cap10.bean.* (..))")

 

  • Introduction 代表类型声明其他方法或字段。Spring AOP允许您向任何建议的对象引入新接口(以及相应的实现),例如,您可以使用简介使 bean 实现 IsModified  接口,以简化缓存
  • Target object  (目标对象) :织入 Advice 的对象
  • AOP proxy 由AOP框架创建的对象,用于实现方面契约(建议方法执行等)。在Spring Framework中,AOP代理将是JDK 动态代理 或CGLIB代理。 通过源码,我们发现  使用的反射代理, CGLIB代理 以及动态代理

Spring源码深度解析,Spring源码核心技术,Aop (七)(附代码示例:)_第4张图片

  • Weaving (织入) : 将 Aspect  和其他对象连接起来, 并创建 Adviced object 的过程 ,是加载时间或在运行时完成。Spring AOP在运行时执行编织。
  • Befor  (前置通知): 在执行 方法前  通知
  • After (后置通知) :在执行 方法后通知
  • AfterReturning (返回通知) : 在执行 方法 没有出现异常的状态下,通知
  • AfterThrowing (异常通知) : 在执行 方法 出现异常状态下 通知
  • Around  (环绕通知) : 动态代理,需要手动执行, joinpoint.procced() (其实就是调用  切点方法) ,执行该方法前, 前置通知,执行之后 后置通知。 如果, 环绕通知和 后置通知 同时存在时, 先执行,环绕通知,后执行我们声明的前置通知,同理,环绕通知的后置通知 也是先执行的。

三,代码示例: 源码  CapTest10, 每一行都有注释,相信应该也能大致明白,在实际的应用中我们是怎么使用的。

package com.enjoy.Cap10.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Arrays;

/**日志切面类
 * LogAspects  aop类  必须加注解 @Aspect
 *
 * 前置通知, 在方法之前运行。
 *   @before 注解 参数 execution()  返回值( public int), 切入方法地址(com.enjoy.Cap10.bean.Calculator.Subtraction(int,int)) , 有几个参数,什么类型,必须要写上去
 *
 * @Before("execution(public int com.enjoy.Cap10.bean.Calculator.Subtraction(int,int))")
 *public void befor(){}
 *
 *
 *
 *@After("execution(public int com.enjoy.Cap10.bean.Calculator.Subtraction(int,int))")
 *public void after(){}
 *
 *
 *
 * @author wuchao
 * @date 2019/7/3
 */
@Aspect
public class LogAspects {

    /**
     * 此注解的作用是切点,如果没有我们需要在下面 @Befor @After @AfterReturning @AfterThrowing @Around  注解后面都跟上指定的方法,
     * 当我们用此注解,指定路径之后,可以在 下面 注解后面直接 指点指定此方法,效果一样,来减少代码的冗余,
     * 但是 这样指定 execution(public int com.enjoy.Cap10.bean.Calculator.Subtraction(int,int))  写死了, 我们用什么方法,来讲代码写的更加优雅,和
     *     execution(public int com.enjoy.Cap10.bean.*(..))      用* 代替的意思不管该包下面有多少类,全部实现 Aop,  参数不管有多少,用 (..)  代替,
     * 就可以 直接使用 @Before("pointcut()")  其他注解也是一样
     */
    @Pointcut("execution(public int com.enjoy.Cap10.bean.Calculator.subtraction(..))")
    public void pointcut(){
        //此方法仅做 指定地址
    }

    /**前置通知, 在方法之前运行。
     * @before 注解 参数 execution()  返回值( public int), 切入方法地址(com.enjoy.Cap10.bean.Calculator.Subtraction(int,int)) , 有几个参数,什么类型,必须要写上去
     * @param point  该JoinPoint参数就代表了织入增强处理的连接点。JoinPoint里包含了如下几个常用的方法:
     * Object[] getArgs:返回目标方法的参数
     *
     * Signature getSignature:返回目标方法的签名
     *
     * Object getTarget:返回被织入增强处理的目标对象
     *
     * Object getThis:返回AOP框架为目标对象生成的代理对象
     */
    @Before("pointcut()")
    public void befor(JoinPoint point){

        System.out.println("@Before:模拟权限检查...");
        //获得方法参数,转换成String;
        System.out.println("计算机减法运行....参数列表是:{"+Arrays.toString(point.getArgs())+"}");
        //获得 aop 切入的方法名
        System.out.println("@Before:目标方法为:" + point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());
        System.out.println("@Before:被织入的目标对象为:" + point.getTarget());
    }

    /**
     * 后置通知, 在方法之后运行。
     */

    @After("pointcut()")
    public void after(){
        System.out.println("计算机减法结束......");
    }

    /**
     * 返回通知,在方法没有出现异常,正常放回值的状态下运行。
     * @param point    连接点
     * @param returnValue   返回值
     */
    @AfterReturning(pointcut = "pointcut()",returning = "returnValue")
    public void afterRetruning(JoinPoint point,Object returnValue){
        System.out.println("@AfterReturning:模拟日志记录功能...");
        System.out.println("@AfterReturning:目标方法为:" +
                point.getSignature().getDeclaringTypeName() +
                "." + point.getSignature().getName());
        System.out.println("@AfterReturning:参数为:" +
                Arrays.toString(point.getArgs()));
        System.out.println("@AfterReturning:返回值为:" + returnValue);
        System.out.println("@AfterReturning:被织入的目标对象为:" + point.getTarget());

        System.out.println("计算机减法正常返回......运行结果是:{}");
    }


    /**
     * 异常通知,在方法出现异常,进行通知
     */
    @AfterThrowing(pointcut = "pointcut()",throwing = "e")
    public void afterThrowing(JoinPoint point,ArithmeticException e){
        System.out.println("计算机减法出现异常......异常是:{"+point.getSignature().getName()+"--------"+e+"}");
    }

    /**
     * 环绕通知, 在方法前通知,在方法后通知。
     */
    @Around("pointcut()")
    public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        System.out.println("@Arount:执行目标方法之前...");
        //手动执行 proceed(), 其实该方法调用的就是 Subtraction();
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("@Arount:执行目标方法之后...");

        return  proceed;
    }
}

 

package com.enjoy.Cap10.config;

import com.enjoy.Cap10.aop.LogAspects;
import com.enjoy.Cap10.bean.Calculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/** 配置类
 * Cap10MainConfig
 *
 * @Configuration 声明为配置类,让Spring扫描,加入到容器
 * @EnableAspectJAutoProxy  只有声明了此注解启用 AOP,Spring才知道我们声明定义的Aop,并且去扫描。也就是说是个开关,声明了注解,打开了aop的开关
 *
 * @Befor  前置通知,在我们执行 com.enjoy.Cap10.bean.Calculator.Subtraction() 方法之前运行,
 * @After  后置通知,在我们执行 com.enjoy.Cap10.bean.Calculator.Subtraction() 方法之后运行,
 * @AfterReturning  返回通知, 在我们执行 com.enjoy.Cap10.bean.Calculator.Subtraction() 方法之后, 该方法没有出现异常的状态下 运行。
 * @AfterThrowing   异常通知, 在我们执行 com.enjoy.Cap10.bean.Calculator.Subtraction() 方法出现异常状态下 运行
 * @Around  环绕通知, 动态代理,需要手动执行,joinPoint.procced()(其实就是调用 Subtraction() 方法),执行该方法前 前置通知,执行之后后置通知。如果,
 *  环绕通知 和 前置通知,后置通知 同时存在时, 先执行,环绕通知的 前置通知,后执行我们声明的前置通知,  同理,环绕通知的 后置通知也是先执行的、
 * @author wuchao
 * @date 2019/7/3
 */
@Configuration
@EnableAspectJAutoProxy
public class Cap10MainConfig {

    @Bean
    public Calculator calculator(){

        return  new Calculator();
    }

    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

 

import com.enjoy.Cap10.bean.Calculator;
import com.enjoy.Cap10.config.Cap10MainConfig;
import org.aspectj.lang.annotation.Aspect;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 测试类
 * Cap10Test
 *
 * @author wuchao
 * @date 2019/7/4
 */
public class Cap10Test {
    @Test
    public void Test(){
        /*初始化容器*/
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Cap10MainConfig.class);

        Calculator calculator= (Calculator) applicationContext.getBean("calculator");

        calculator.subtraction(0,0);


      /* Advice  a modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented using regular classes
                (the schema-based approach) or regular classes annotated with the @Aspect annotation*/
    }
}

四,项目Demo 地址。

Spring源码深度解析,(附代码示例 码云地址: https://gitee.com/Crazycw/SpringCode.git)

参考资料:  https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop.html

参考资料:https://blog.csdn.net/q982151756/article/details/80513340

请看下篇: Spring源码深度解析,SpringAOP源码跟踪(八)(附代码示例:)
 

你可能感兴趣的:(Spring)