程序猿
微录

详解Spring的切面编程【AOP】机制 【1:基础概念】

程序猿微录 发布于: 2020-04-25 12:39 283 0 0 0
首页
文章
专栏
问答
寄语
公告
  • 前往登录

详解Spring的切面编程【AOP】机制 【1:基础概念】

程序猿微录 发布于 2020-04-25 12:39 283 0 0 0
所属文册: Spring AOP 文章标签: Spring 、AOP 、切面编程

Spring AOP 基础概念

个人描述


  • 编程:
    • 可以理解为沿着一条线从上往下顺序执行的代码,代码的执行是存在多条直线的。
  • 切面编程:
    • 就像对顺序执行的代码按照一定的切面规则横切一刀,对切到的每条直线产生两个断点(这里区分为:上、下)
  • 为了使原有的顺序执行的代码能顺利执行下去,需要对断点进行续接
    • 续接的过程就是面向切面编程!
    • 续接的代码就是通知,前接上断点,后接下断点

术语


  • Aspect
    • 切面,对要产生切面编程相关定义的模块化,Spring中体现为可以定义切入点、连接点、通知的类
  • JoinPoint
    • 连接点:横切后一个直线被切为为2断点,续接时就是接着上断点编程连接到下断点(因为编程都是从上往下顺序写的),这个上部分点就是连接点
  • Pointcut
    • 切入点,产生连接点的规则集合,Spring中体现为定义产生连接点的切入规则
  • Advice
    • 通知:围绕连接点产生的编程内容,Spring中分为:前置通知,后置通知,环绕通知,异常通知,返回通知等
  • target
    • 目标对象,采用代理方式实现AOP时,被代理的对象为target
  • proxy
    • 代理对象,AOP的实现方式,代理为其中一种,pointcut中使用this替代
  • weaving
    • 织入,将通知内容应用到目标的过程,分为静态织入与动态织入,Spring采用动态织入

概念


  • POP:面向过程编程
  • OOP:面向对象编程
  • BOP:面向Bean编程
  • AOP:面向切面编程

切面编程简易图示


Image [42].png

注解式编程

  • 切面类
    • 用@Aspect在类上面,声明该类是一个切面类,内部可以定义切入点,连接点,通知
@Aspect类实例化方式
value 描述 模式
单例模式,默认 单例
perthis 为每个切面的连接点对应的代理对象 都创建一个新的切面实例 多例,配合@Scope("prototype")使用



pertarget 为每个切面的连接点对应的代理目标对象 都创建一个新的切面实例
percflow 不支持(5.2.6)
percflowbelow 不支持(5.2.6)
pertypewithin 不支持(5.2.6)
  • 切入点
    • 用@Pointcut在void方法上,声明该方法是一个切入点,可以绑定参数,value为产生连接点的规则
    • 在具体的通知中定义
连接点产生方式
根据表达式来匹配某方法来产生连接点 execution 表达式最终匹配的是方法
根据对象来产生连接点 this 匹配的是代理后的代理对象
target 匹配的是代理前的目标对象
within 匹配的是代理前的对象
args 匹配的是方法的入参类型
根据注解来产生连接点 @target 匹配的是代理前的目标对象上使用的注解
@args 匹配的是方法的入参中使用的注解
@within 匹配的是代理前的对象上使用的注解
@annotation 匹配的是方法上使用的注解
根据Bean名称来产生连接点 bean 匹配的是IOC中注册的Bean名称
组合模式 &&、||、! 以上产生方式组合使用

  • 通知
通知类型
@Before 前置通知 在方法执行之前执行
@After 后置通知 在方法执行之后执行
@Around 环绕通知 可选择方法何时执行
@AfterThrowing 异常通知 产生指定的异常时执行
@AfterReturning 返回通知 方法执行完毕后通知
@DeclareParents 引介增强 给目标类新增方法

  • 通配符
通配符说明
* 模糊匹配:表示任意数量的字符
.. 模糊匹配:表示多个/多级,一般用于指定任意层级的包、任意个数的参数
+
模糊匹配:表示按类型匹配指定类的所有类,包含子类,仅能跟在类名后面

通配符支持
execution()、within() * .. +
args()、this()、target() +
@args()、@within()、@target()、@annotation() 不支持

切入点定义

execution :匹配方法执行产生连接点(通过表达式匹配到某方法,然后产生连接点,这是主要的使用方式)

// execution(<修饰符模式>?     <返回类型模式>    <类路径模式>           <方法名模式>(<参数模式>)<异常模式>?)
   execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

示例

@Pointcut("execution(* cn.tinyice.demo..AspectJTargetInterface.sayHello())")
public void pointCut() {
}

execution(参数说明)
modifier-pattern? 修饰符匹配,可选
ret-type-pattern 返回值匹配,可以为*表示任何返回值,全路径的类名等
declaring-type-pattern? 类路径匹配,可选
name-pattern 方法名匹配,可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
param-pattern 参数匹配,可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“*”来表示匹配任意类型的参数
throws-pattern? 异常类型匹配,可选
? 表示可选

常见用法:注意最少2部分组成(中间一个空格隔开)

// 匹配多级子包中的所有方法
execution(* cn.tinyice..service.*(..))

// 匹配当前包中的所有public方法
execution(public * DemoService.*(..))

// 匹配指定包中的所有public方法,并且返回值是int类型的方法
execution(public int cn.tinyice.demo.service.*(..))

// 匹配指定包中的所有public方法,并且第一个参数是String,返回值是int类型的方法
execution(public int cn.tinyice.demo.service.*(String name, ..))

  • within(Type、id),支持类型表达式
@Pointcut("within(cn.tinyice..DemoService)")
public void pointCut() {
}

within(参数说明)
Type 支持类型表达式匹配,包括Class、Interface,支持类型表达式,不支持继承
id XML配置中Bean的Id

常见用法

// 匹配指定包中的所有方法,但不包括子包
within(cn.tinyice.demo.service.*)

// 匹配指定包中的 所有方法,包括子包
within(cn.tinyice.demo.service..*)

// 匹配当前包中的指定类中的方法
within(DemoService)

// 匹配一个接口的所有实现类中的实现的方法
within(IDemoService+)
  • this(Type、id)、target(Type、id),Type支持类全限定名称
@Pointcut("this(cn.tinyice.demo.service.DemoService)")
public void pointCut() {
}

@Pointcut("target(cn.tinyice.demo.service.DemoService)")
public void pointCut() {
}

This Target Within 区别
this 匹配AOP代理对象的类型(CGLIB、JDK 代理之后的类型)
target 匹配AOP目标类的类型(类或接口的原始类型),支持继承关系
within 匹配AOP目标类的类型(类或接口的原始类型),支持类型表达式、不支持继承关系(可以通过+来模糊匹配实现)
  • args(Type、id)
@Pointcut("args(java.lang.String, java.lang.String,java.lang.Integer)")
public void pointCut() {
}
  • @annotation 、@within 、 @target 、@args : 将注解对象注入到增强方法中
@Pointcut("@annotation(cn.tinyice.demo.aspect.aspects.TyAnnotation))")
public void pointCut() {
}

@Pointcut("@within(cn.tinyice.demo.aspect.aspects.TyAnnotation)")
public void pointCut() {
}

@Pointcut("@target(cn.tinyice.demo.aspect.aspects.TyAnnotation)")
public void pointCut() {
}

@Pointcut("@args(cn.tinyice.demo.aspect.aspects.TyAnnotation)")
public void pointCut() {
}
  • bean(name、id):根据IOC中的bean来产生连接点,支持*匹配
@Pointcut("bean(demoService)")
public void pointCut() {
}

@Pointcut("bean(*Service)")
public void pointCut2() {
}
  • 组合模式:可以使用 或(||)、且(&&),非(!) 来处理以上结果集
@Pointcut("bean(demoService)")
public void pointCut() {
}

@Pointcut("bean(*Service)")
public void pointCut2() {
}

@Pointcut("bean(*Service) && pointCut()")
public void pointCut3() {
}


@Pointcut("bean(*Service) && bean(*Service)")
public void pointCut4() {
}

访问连接点信息

默认参数支持
org.aspectj.lang.JoinPoint 支持任何通知
org.aspectj.lang.ProceedingJoinPoint 只支持环绕通知 @Around

JoinPoint方法
方法 说明
Object[] getArgs() 获取连接点方法运行时的入参列表。
Signature getSignature() 获取连接点的方法签名对象。
Object getTarget() 获取连接点所在的目标对象。
Object getThis() 获取代理对象。

ProceedingJoinPoint 新增方法
方法 说明
Object proceed() throws Throwable 通过反射执行目标对象连接点处的方法。
Object proceed(Object[] var1) throws Throwable 使用新的入参(替换掉原来的入参),通过反射执行目标对象连接点处的方法。

参数传递

支持方式:this、target、@within、@target、@annotation、@args,当需要在通知编码中使用某些对象时,可以通过参数名称传递来获取对象

参数传递时通过反射无法获取参数具体名称,需要通过argNames属性配置参数名称,如果第一个参数是的JoinPoint,ProceedingJoinPoint或 JoinPoint.StaticPart类型可以不指定该从参数,其它参数必须指定

传递示例

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
}

排序

  • 如果在同一个切面类中声明的增强,则按照增强在切面类中定义的顺序进行织入;
  • 如果增强位于不同的切面类中,通过Order排序,无Order的顺序不固定

简单示例

接口

public interface IDemoService {

   void print(String str);
}

要增强的方法

@Service
public class DemoService implements IDemoService {

    @Override
    @TyAnnotation
    public void print(String str) {
        System.out.print(str);
    }
}

增强切面类

@Aspect
// @Aspect 只能修饰类
@Component
public class PointCutGroup {


    @Pointcut(value = "args(str) && @annotation(cn.tinyice.demo.aspect.aspects.TyAnnotation)) ", argNames = "str")
    // 保证参数能正确连接上
    public void pointCut(String str) {
    }

    @Before(value = "pointCut(str)", argNames = "str")
    public void beforeAdvice(String str) {
        System.out.println("------------ beforeAdvice PointCutGroup args=" + str + "-------------------");
    }

    @After(value = "pointCut(str)", argNames = "str")
    public void afterAdvice(String str) {
        System.out.println("------------ afterAdvice PointCutGroup args=" + str + "-------------");
    }

    @Around(value = "pointCut(str)", argNames = "joinPoint,str")
    // ProceedingJoinPoint  参数为@Around增强独有,表示连接点
    public Object aroundAdvice(ProceedingJoinPoint joinPoint, String str) throws Throwable {
        System.out.println("------------ aroundAdvice PointCutGroup args=" + str + "----------------");
        Object obj = joinPoint.proceed();
        System.out.println();
        System.out.println("------------ aroundAdvice PPointCutGroup args=" + str + "-----------------");
        return obj;
    }

    @AfterReturning(pointcut = "pointCut(str)", argNames = "str,retVal", returning = "retVal")
    // value 可以自定义匿名切入点 或者pointCut引入具名切入点
    // returning 为方法返回值对象参数名,参数在afterReturning中定义
    // @AfterReturning 发生在@After之前
    public void afterReturning(String str, Object retVal) {
        System.out.println("------------ afterReturning PPointCutGroup retVal=" + retVal + "-------------");
    }

    @AfterThrowing(value = "args(str) && execution(* cn.tinyice..DemoService.*(..) )", argNames = "str,ex", throwing = "ex")
    // throwing 为要增强的异常类型的参数名称,参数在afterThrowing中定义
    public void afterThrowing(String str, RuntimeException ex) {
        System.out.println("------------ afterThrowing PPointCutGroup exception=" + ex + "-------------");
    }
}