019-Spring Aop

作者: 郭艺宾 | 来源:发表于2018-07-03 18:39 被阅读7次

    spring最核心的两个功能是aop和ioc,即面向切面,控制反转。这里我们探讨一下如何在spring boot中使用aop。

    aop全称Aspect Oriented Programming,面向切面,AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。其与设计模式完成的任务差不多,是提供另一种角度来思考程序的结构,来弥补面向对象编程的不足。

    通俗点讲就是提供一个为一个业务实现提供切面注入的机制,通过这种方式,在业务运行中将定义好的切面通过切入点绑定到业务中,以实现将一些特殊的逻辑绑定到此业务中。

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

    本来spring就自带一套aop实现,我们直接使用此实现即可,本来使用aop还需要定义一些xml文件,但由于我们使用的是spring-boot框架,这一步就省略掉了。也就是说,在spring-boot中,我们可以直接使用aop而不需要任何的配置

    先介绍一些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框架一样,在运行时完成织入。

    其中重要的名词有:切面,切入点

    接下来看一个简单例子,首先写一个简单的控制器类:

    可以看到是一个简单的Controller,一个正常的test接口和一个一定会报错的error方法,每个方法内部都有一个简单的打印语句。

    接下来写一个aop类,

    @Aspect注解:作用是把当前类标识为一个切面供容器读取

    然后定义一个切入点:

    @Pointcut注解:声明一个切入点,切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。

    execution切点函数:execution函数用于匹配方法执行的连接点,语法为:execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选)) 

    定义一个在方法执行前执行的方法:

    注意注解的参数就是切入点的方法名。重启项目,访问 /aop/test,可以看到结果:

    可以看到,在方法执行前,执行了@Before注解的方法内容。

    定义一个在方法执行后执行的方法:

    重启访问,可以看到结果:

    接下来定义一个获取方法返回值并打印的方法:

    重启访问,可以看到结果:

    接下来定义一个环绕通知的方法:

    重启访问,可以看到结果:

    注意:从上面的返回结果可以看到这几个方法在切入方法时的执行顺序。

    最后定义一个方法异常时切入执行的方法:

    注释掉环绕方法,重启查看执行结果:

    可以看到异常时执行了哪些内容。

    各方法参数说明:

    除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。@Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。@AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值,@AfterThrowing方法里,可以加throwing = "XXX",供读取异常信息,如本例中可以改为:

    @AfterThrowing(throwing ="ex", pointcut ="aopMethods()")  

    publicvoid throwss(JoinPoint jp, Exception ex){  

            System.out.println("方法异常时执行.....");  

    }  

    关于切面PointCut的切入点

    execution函数用于匹配方法执行的连接点,语法为:

    execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选)) 

    参数部分允许使用通配符:

    *  匹配任意字符,但只能匹配一个元素

    .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用

    +  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类

    参考:http://blog.csdn.net/autfish/article/details/51184405

    除了execution(),Spring中还支持其他多个函数,这里列出名称和简单介绍,以方便根据需要进行更详细的查询

     @annotation()

    表示标注了指定注解的目标类方法

    例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示标注了@Transactional的方法

    args()

    通过目标类方法的参数类型指定切点

    例如 args(String) 表示有且仅有一个String型参数的方法

    @args()

    通过目标类参数的对象类型是否标注了指定注解指定切点

    如 @args(org.springframework.stereotype.Service) 表示有且仅有一个标注了@Service的类参数的方法

    within()

    通过类名指定切点

    如 with(examples.chap03.Horseman) 表示Horseman的所有方法

    target()

    通过类名指定,同时包含所有子类

    如 target(examples.chap03.Horseman)  且Elephantman extends Horseman,则两个类的所有方法都匹配

    @within()

    匹配标注了指定注解的类及其所有子类

    如 @within(org.springframework.stereotype.Service) 给Horseman加上@Service标注,则Horseman和Elephantman 的所有方法都匹配

    @target()

    所有标注了指定注解的类

    如 @target(org.springframework.stereotype.Service) 表示所有标注了@Service的类的所有方法

     this()

    大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配

    /****************************分割线****************************/

     

    逻辑运算符

    表达式可由多个切点函数通过逻辑运算组成

     &&

    与操作,求交集,也可以写成and

    例如 execution(* chop(..)) && target(Horseman)  表示Horseman及其子类的chop方法

     ||

    或操作,求并集,也可以写成or

    例如 execution(* chop(..)) || args(String)  表示名称为chop的方法或者有一个String型参数的方法

    !

    非操作,求反集,也可以写成not

    例如 execution(* chop(..)) and !args(String)  表示名称为chop的方法但是不能是只有一个String型参数的方法

    execution常用于匹配特定的方法,如update时怎么处理,或者匹配某些类,如所有的controller类,是一种范围较大的切面方式,多用于日志或者事务处理等。

    其他的几个用法各有千秋,视情况而选择。

    下面来看annotation的。

    自定义注解

    一般多用于某些特定的功能,比较零散的切面,譬如特定的某些方法需要处理,就可以单独在方法上加注解切面。

    我们来自定义一个注解:

    注解里提供了一个value的方法,供被切面的地方传参,如果不需要传参可以不写。

    在Controller里方法上加注解:

    可以看到,有aop注解的方法被切入了,下面的方法么有标识。

    运行结果与上面的例子一样,略。。。

    spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。

    对于上面的例子就是,先外层的就是对所有controller的切面,内层就是自定义注解的。

    那不同的切面,顺序怎么决定呢,尤其是同格式的切面处理,譬如两个execution的情况,那spring就是随机决定哪个在外哪个在内了。

    所以大部分情况下,我们需要指定顺序,最简单的方式就是在Aspect切面类上加上@Order(1)注解即可,order越小最先执行,也就是位于最外层。像一些全局处理的就可以把order设小一点,具体到某个细节的就设大一点。

    本文参考:https://www.cnblogs.com/lic309/p/4079194.html

    代码地址: https://gitee.com/blueses/spring-boot-demo

    相关文章

      网友评论

        本文标题:019-Spring Aop

        本文链接:https://www.haomeiwen.com/subject/dpmjuftx.html