AOP编程

AOP编程

 

目录

一:什么是AOP

二:相关概念

三:实例演示

四:AOP底层实现

1.Cglib

一:什么是AOP

Aspect Oriented Programming(AOP),面向切面编程,是OOP编程绕不开的一部分,AOP的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。又是耦合性,有没有发现不管是设计模式还是框架设计还是代码风格等等,说来说去最后都是降低耦合,因为耦合性真的是一个代码是否可拓展、可维护的重要衡量标准。AOP将我们编程中经常遇到的例如安全、事物、日志等工作,而且在很多服务中可能都要用到数据验证、打印日志等工作,如果在每个服务实现类都需要实现日志等功能,那代码耦合程度都太高了。事物、日志的工作其实就是一个横向的切面(相对于纵向的业务逻辑)在多个类多个服务中都要用到,我们把它提取出来,作为一个切面,单独实现其功能,就是AOP了。

二:相关概念

  • 切面(Aspect):

    是通知、切点的结合,定义了何时、何地以及做什么工作

  • 通知(Adcice)

    定义了切面是什么以及合适使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。Spring切面有5中类型的通知:

    • Before:在目标方法被调用之前调用通知功能

    • After:   在目标方法被调用之后调用此通知功能,无论方标方法调用是否成功

    • After-returning:在目标方法成功执行之后调用通知

    • After-throwing: 在目标方法抛出异常后调用通知

    • Around: 通知包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的行为。使用Around通知,该通知接收ProceedingJoinPoint作为参数,这个参数是必须的。当要把控制权交给被通知的方法是,需要调用ProceedingJoinPoint的proceed()方法,不然会阻塞。

  • 切点(Pointcut)

    切点会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名来指定这些切点。

  • 织入(Weaving):

    织入就是把切面应用到目标对象并创建新的代理对象的过程。Aspectj是在编译期被织入切面的。而SpringAOP是在运行期织入的。

三:实例演示

  1. 首先,创建一个Maven工程。

     

    然后在pom.xml文件中加入相应的依赖。要使用AOP编程,必须引入SpringAOP或者Aspectj的依赖,图中两个引入任意一个即可。

  2. AspectJ切点指示器
    正式切面编程之前还需要了解如何编写切点。下表列出了SpringAOP所支持的AspectJ切点指示器

    AspectJ 指示器

    描 述

    arg()

    限制连接点匹配参数为指定类型的执行方法

    @arg()

    限制连接点匹配参数由指定注解标注的执行方法

    execution()

    用于匹配是连接点的执行办法

    this()

    限制连接点匹配AOP代理的bean饮用为指定类型的类

    target()

    限制连接点匹配目标对象为指定类型的类

    @target()

    限制连接点匹配特定的执行对象,这些对象对应的类要有指定类型的注解

    within()

    限制连接点匹配指定的类型

    @within()

     

    限制连接点匹配指定注解所标注的类型

    @annotation

    限制匹配指定注解所标注的的连接点

    接下来我们以execution()和@annotation两种方式举例。

    情景:某位同学下载了一款游戏,所以要先进行注册。 在正式注册之前,填写信息时需要对信息进行正则表达式校验等工作,注册成功后又需要提醒注册成功,并通知送了新人一个皮肤。如果注册失败,则通知失败原因。    在玩游戏之前会有广告,推销你买新皮肤。玩游戏两个小时之后会提醒你合理安排时间。  注册使用annotation切点指示器,玩游戏用execution指示器。

  3. 定义注解

    要使用注解的方式进行AOP拦截,就需要先定义一个注解

     

    创建一个Validator.java文件,定义一个名叫Validator的注解,用于校验注册。

  4. 切面编程

     

    定义切面,需要加上@Aspect注解。 @annotation(local.learning.core.myannotation.Validator)表示定义的切点是所有有@Validator注解的地方。Validator具体路径是local.learning.core.myannotation包下面的Validator类,即步骤3的那个。 然后分别定义了@Before,@AfterReturning,@AfterThrowing三个不同的通知。 

  5. 配置

    要让AOP正常工作,还需要在applicationContext中配置

    <aop:aspectj-autoproxy proxy-target-class="true"/>

    声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。

  6. 调用

     

    在controller里定义了一个register()函数,该函数上加了@Validator注解,就能调用步骤4的图中的相应通知了。

  7. 运行结果如图

    在postman中发送一个GET请求:localhost:8080/aop。 可以看到console打印出消息:
     

    可以看到,我们的前置和后置通知已经生效了,打印了相应的信息。如果我们主动抛出一个异常,就会调用AfterThrowing的通知,结果如图:


     

  8. 如果我们用execution()定义切点,切面定义如下图:


    在这里我们很清晰的可以看到,一个切面Aspect=切点(Pointcut)+通知(Advice)
    用execution()方法定义的切点匹配的是方法,

    * local.learning.core.controller.AOPController.play(..)

    * 表示返回值是任意类型  

    local.learning.core.controller.AOPController.play表示切点匹配的方法是local.learning.core.controller.AOPController包下面的play()方法

    (..)表示参数为任意类型

    当然,我们可以用我注释掉的那种方法把切点和通知写在一起,

    @Before("execution(* local.learning.core.controller.AOPController.play())"

    效果是一样的。

    不过用这种方式,在我们需要定义多个通知时,每次都要输入execution(...)那一长串。

  9. 调用

    在controller写了一个玩游戏的方法

     

    需要注意的是,这个函数的包名和函数名需要和local.learning.core.controller.AOPController.play()一致,切面才能起效。由于直接找到了相应的方法,不需要步骤6中的@Validatora注解。

  10. 运行结果如图

     

    在play()之前和之后分别调用的前置通知和后置通知。

 

 AOP编程就是这样,了解了以后发现原来就是这么简单!

四:AOP底层实现

 

AOP的底层实现其实就是动态代理,AOP动态代理有两种实现:JDK动态代理和cglib。SpringBoot默认用的是cglib。

1.Cglib

jdk中的动态代理通过反射类ProxyInvocationHandler回调接口实现,要求委托类必须实现一个接口,只能对该类接口中定义的方法实现代理,这在实际编程中有一定的局限性。Cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。

  • cglib封装了asm,可以在运行期动态生成新的class(子类)。

  • cglib用于AOP,jdk中的proxy必须基于接口,cglib却没有这个限制。

Cglib代理有几个重要的类涉及到:

Enhancer:动态的生成一个类,这个类可以继承指定的一个类,实现指定的一些接口。

Enhancer调用firstInstance方法来返回一个对象:

 

代码块

java

protected Object firstInstance(Class type) throws Exception {
    return this.classOnly ? type : this.createUsingReflection(type);
}
整个调用链为:
  1. Enhancer:firstInstance

  2. Enhancer:createUsingReflection

  3. Enhancer:setThreadCallbacks

  4. Enhancer:setCallbacksHelper

  5. Target$$EnhancerByCGLIB$$788444a0 : CGLIB$SET_THREAD_CALLBACKS

 CGLIB$SET_THREAD_CALLBACKS通过反编译可以发现调用了intercept方法。

代码块

java

public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

第一个参数表示代理对象本身;第二个参数表示被拦截的方法对象;第三个参数表示方法入参;第四个参数表示被拦截方法的方法代理对象。每个被代理的方法都对应一个MethodProxy对象,methodProxy.invokeSuper方法最终调用委托类的add方法,实现如下:

 

 

 

代码块

java

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        this.init();
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}

 

在MethodProxy里我们见到了另一个很重要的类:FastClass

 

FastClass其实就是对Class对象进行特殊处理,提出下标概念index,通过索引保存方法的引用信息,将原先的反射调用,转化为方法的直接调用,从而体现所谓的fast。FastClass类部分方法如图所示:

 

 

在FastTest中有两个方法,getIndex中对Test类的每个方法根据hash建立索引,invoke根据指定的索引,直接调用目标方法,避免了反射调用。所以当调用methodProxy.invokeSuper方法时,实际上是调用代理类的CGLIB$add$0方法,CGLIB$add$0直接调用了委托类的add方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值