Spring旅程之面向切面(AOP原理)

如果您对此spring文章感兴趣请关注专栏:
Spring旅程

Spring之面向切面

面向切面编程AOP所要解决的问题是:
如何将应用中需要横切关注的点和他们所影响的对象之间解耦

AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果

通俗点讲就是在业务运行中将定义好的切面通过切入点绑定到业务中,以实现将一些特殊的逻辑绑定到此业务中

比如,若是需要一个记录日志的功能,首先想到的是在方法中通过log4j或其他框架来进行记录日志,但写下来发现一个问题,在整个业务中其实核心的业务代码并没有多少,都是一些记录日志或其他辅助性的一些代码。而且很多业务有需要相同的功能,比如都需要记录日志,这时候又需要将这些记录日志的功能复制一遍,即使是封装成框架,也是需要调用之类的。在此处使用复杂的设计模式又得不偿失
  所以就需要面向切面出场了

我们知道DI(依赖注入)有助于应用对象之间的解耦,而AOP可以将应用中需要横切关注的点和他们所影响的对象之间解耦

aop思想
横向重复,纵向抽取
《Spring旅程之面向切面(AOP原理)》

《Spring旅程之面向切面(AOP原理)》

Spring实现aop原理
动态代理(优先)
被代理对象必须要实现实现接口,才能产生代理对象。如果没有接口不能使用动态代理技术。
cglib代理(没有接口使用)
第三方代理技术,cglib代理,可以对任何类实现代理,代理的原理是对目标对象进行继承代理。如果目标对象被final修饰,该类无法被cglib代理

切面的原理:

使用代理类封装目标类,并拦截被通知方法对代理类的调用,再把调用转发给真正的目标bean,当代理拦截到方法调用的时候,在调用目标bean方法之前,会执行切面逻辑

SPring可以应用五种类型的通知

创建切点来定义切面所织入的连接点是AOP框架的基本功能

aop名词解释

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring
    AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。

  • 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。

  • 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。

  • 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

  • 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。

  • 目标对象(Target Object):被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

  • AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

  • 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

流程:

例如,当执行某个目标对象的特定名称的方法时,因为这个方法被声明为了一个连接点,并且这个连接点和某个通知的切入点表达式相吻合,于是在这个方法执行前后会调用对应的通知方法,我们可以在这个通知里面定义切面动作

通知

  • 前置通知 目标方法之前调用
  • 后置通知 (如果出现异常不会调用) 在目标方法运行之后
  • 环绕通知 在目标方法之前和之后都调用
  • 异常拦截通知 如果出现异常,就会调用
  • 后置通知 (无论是否出现异常都会调用) 在目标方法运行之后调用
    而且AspectJ是面向注解的AOP模型,我们可以使用少量AspectJ注解轻松定义切面

切点:

用来准确定位应该在什么地方应用切面的通知
例如AdminLogAspect就是用的@around环绕通知

AspectJ指示器:

重要的是注解@annotation的使用:


//声明匹配带有指定注解的连接点(如果有方法使用了adminMyLog注解,那么当次方法执行时会被执行此环绕通知)
@Around("@annotation(adminMyLog)")
public Object around(ProceedingJoinPoint pjp, AdminMyLog adminMyLog){

意思就是使用annotation注解之后,此处只能匹配使用了adminMyLog注解的切点,例如使用了adminMyLog注解的方法,Spring会在此方法执行过程中调用处理切面的通知方法

@Around声明的方法

使用@Around注解声明的方法表明其可以作为使用adminMyLog注解的地方声明的切点的环绕通知

这个方法中定义的是当程序执行到切点的时候,在切点之前和之后会执行的事情

方法必须要有一个ProceedingJoinPoint pip参数,因为我们只有通过他才可以调用 被通知的方法:pjp.proceed()

当然我们可以重复调用这个方法,也就是重复逻辑

代码示例:

//描述切面类,描述一个切面类,定义切面类的时候需要打上这个注解
@Aspect  
@Component  
public class LogAspect {  
    @Pointcut("execution(public * com.example.controller.*.*(..))")  
    public void webLog(){}  
  
    @Before("webLog()")  
    public void deBefore(JoinPoint joinPoint) throws Throwable {  
        // 接收到请求,记录请求内容  
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();  
        HttpServletRequest request = attributes.getRequest();  
        // 记录下请求内容  
        System.out.println("URL : " + request.getRequestURL().toString());  
        System.out.println("HTTP_METHOD : " + request.getMethod());  
        System.out.println("IP : " + request.getRemoteAddr());  
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());  
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));  
  
    }  
  
    @AfterReturning(returning = "ret", pointcut = "webLog()")  
    public void doAfterReturning(Object ret) throws Throwable {  
        // 处理完请求,返回内容  
        System.out.println("方法的返回值 : " + ret);  
    }  
  
    //后置异常通知  
    @AfterThrowing("webLog()")  
    public void throwss(JoinPoint jp){  
        System.out.println("方法异常时执行.....");  
    }  
  
    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行  
    @After("webLog()")  
    public void after(JoinPoint jp){  
        System.out.println("方法最后执行.....");  
    }  
  
    //环绕通知,环绕增强,相当于MethodInterceptor  
    @Around("webLog()")  
    public Object arround(ProceedingJoinPoint pjp) {  
        System.out.println("方法环绕start.....");  
        try {  
            Object o =  pjp.proceed();  
            System.out.println("方法环绕proceed,结果是 :" + o);  
            return o;  
        } catch (Throwable e) {  
            e.printStackTrace();  
            return null;  
        }  
    }  
}  

上面实现了一个全局请求日志处理切面类

程序执行流程:
上面的切面类里面声明了切点webLog,于是spring会将绑定了此切点的通知全部添加到一个拦截器链上,而此切点匹配的是com.example.controller包下的所有类的public方法

因此当com.example.controller包下的所有类的public方法执行的时候,因为符合LogAspect 切面类中声明的切点webLog

  @Pointcut("execution(public * com.example.controller.*.*(..))")  
    public void webLog(){}  

于是spring会去使用拦截器链拦截方法的执行,然后再次方法执行的前后周围的各个时机调用对应的动作(通知),于是就实现了对应用层透明的切面功能,而我们使用这些通知方法就可以实现指定的切面功能

自定义注解:

一般多用于某些特定的功能,比较零散的切面,譬如特定的某些方法需要处理,就可以单独在方法上加注解切面
首先定义一个注解:声明次注解的使用范围

@Target({ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface UserAccess {  
    String desc() default "无信息";  
} 

然后定义一个切面:

@Component  
@Aspect  
public class UserAccessAspect {  
  
    @Pointcut(value = "@annotation(com.example.aop.UserAccess)")  
    public void access() {  
  
    }  
  
    @Before("access()")  
    public void deBefore(JoinPoint joinPoint) throws Throwable {  
        System.out.println("second before");  
    }  
  
    @Around("@annotation(userAccess)")  
    public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {  
        //获取注解里的值  
        System.out.println("second around:" + userAccess.desc());  
        try {  
            return pjp.proceed();  
        } catch (Throwable throwable) {  
            throwable.printStackTrace();  
            return null;  
        }  
    }  

通知Before的使用和之前的类似,主要需要关注@Around注解这里,如果需要获取在controller注解中赋给UserAccess的desc里的值,就需要这种写法,这样UserAccess参数就有值了。

使用:

@RequestMapping("/second")  
    @UserAccess(desc = "second")  
    public Object second() {  
        return "second controller";  
    } 

AOP执行原理:
《Spring旅程之面向切面(AOP原理)》

我们可以把spring的aop理解为一个同心圆,要执行的方法为圆心,从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。
对于上面的例子就是,先外层的就是对所有controller的切面,内层就是自定义注解的。

Spring的AOP基本原理是利用的java中的动态代理技术,此技术在android retrofit中也有应用,详细参考:
https://www.cnblogs.com/lcngu/p/5339555.html
到此springAOP的使用基本介绍完成
如果您对此spring文章感兴趣请关注专栏:
Spring旅程

    原文作者:AOP
    原文地址: https://blog.csdn.net/wang2963973852/article/details/85298641
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞