Spring AOP(1)
1.AOP Spring有两大核心思想,IoC和AOP.前面已经介绍过IoC了,这里接着介绍AOP,AOP:Aspect Oriented Program(面向切面编程),面向切面编程,切面就是指某一类特定问题,所以AOP也可以理解为面向特定方法编程.面向特定方法编程?在前面介绍的拦截器,可以实现登录拦截,即是对"登录校验"这一类问题的统一处理,所以拦截器是AOP思想的一种实现,同样的统一数据返回和统一异常处理也是AOP思想的一种实现.简单来说:AOP是一种思想,是对某一类事情的集中处理.Spring AOP是AOP思想的一种实现方式.当然Spring AOP的应用远不止于此.现在有一个项目,项目中有很多的业务功能:现在发现有一些业务的执行效率比较低,耗时较长,现在需要找出这些耗时较长的业务方法,进行优化.传统的方法是,统计出每一个业务方法的耗时时间,计较得出.可以通过下面的方法,粗略算出时间,并进行比较:
但是一个项目中通常有很多方法,如果对于每个方法都添加这写重复的代码,这样不是十分合理.对于同一类问题的处理,AOP就可以做到在不改动原始方法的基础上,针对特定的方法进行功能的增强.
2.Spring AOP介绍(为了方便介绍,这里在一个图书管理系统中进行演示)
首先需要引入下面的依赖接着编写AOP程序,计算程序运行的时间.这里计算Controller层各个方法的运行时间:
观察日志:
可以看到这里显示了方法的耗时时间.不用重复编码.这里:@Aspect:标识这是一个切面类 @Around:环绕通知,在目标方法的前后都会被执行,后面的表达式表示对哪些方法进行增强.ProceedingJoinPoint.proceed()让原始方法执行.通过上面这个简单的程序可以感受到AOP面向切面编程的优势:代码无入侵,减少了重复代码,提高了开发效率,维护方便.
3.Spring AOP详解
3.1Spring AOP核心概念
Pointcut(切点) 切点,也称之为"切入点",Pointcut的作用就是提供一组规则,告诉程序对哪些方法进行功能增强.上面的表达式就是切点表达式. Join Point(连接点) 满足切断表达式规则的方法,就是连接点.也就是可以被AOP控制的方法.那么上面的切点表达式中所有 com.example.demo.controller路径中的方法,都是连接点.
上面/user和/book下的所有方法,都是连接点.连接点是满足切点表达式的元素,切点可以看作是保存了众多连接点的集合. Advice(通知):通知就是具体要做的工作,那些重复的代码.
上面计算方法执行时间的代码就是通知. Aspect(切面):切面(Aspect)=切点(Pointcut)+通知(Advice),通过切面就能够知道当前AOP程序要对那些方法执行什么样的操作.切面即包含了通知逻辑的定义,也包含了连接点的定义.
这个整体就是切面,切面所在的类,称之为切面类(被@Aspect注解标识的类)
3.2通知类型:Spring中的通知类型有下面的几种 @Around:环绕通知,此注解标识的通知方法在目标方法前后都会执行 @Before:前置通知,此注解标识的通知方法在目标方法前执行 @After:后置通知,此注解标识的通知方法在目标方法之后执行,且无论是否发生异常都会执行. @AfterReturning:返回后通知,此注解标识的通知方法在目标方法后被执行,有异常不会执行. @AfterThrowing:异常后通知,此注解标识的通知方法发生异常后执行.下面是一个例子:在对应目录下写一些测试:
这里执行正常的方法:
可以看见,正常运行时,@AfterThrowing表示的通知方法不会执行.正常情况下,不同类型的通知,执行顺序如下:
执行会发生异常的方法:
这里可以看到,@AfterReturning标识的通知方法不会执行,@AfterThrowing标识的通知方法执行了.@Around环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会再执行了(因为原始方法调用出异常了)出现异常时的执行顺序.
注意事项:
@Around环绕通知需要调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行. @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值.否则原始方法执行完毕,是获取不到返回值的. 3.3@PointCut:上述代码存在一个问题,就是存在大量重复的切点表达式"execution(* com.example.demo.controller.*.*(..))",Spring提供了@PointCut注解,把公共的切点表达式提取出来,可以避免重复代码的编写.如果想在其他切面类中使用当前定义的切点时,除了需要把private改为public,还要注意引用方式,引用方式为:全限定类名.方法名().
3.4切面优先级@Order,当在一个项目中,定义了多个切面类时,并且这些切面类的多个切点都匹配到了同一个方法.当目标方法执行的时候,这些切面类中的方法都会执行,那么这些方法的执行顺序是怎样的呢?下面通过一个例子使用@Before和@After来进行验证:存在多个切面类同时执行时,默认按照类名的字母顺序降序执行,@Before:字母顺序在前面的先执行@After:字母顺序在前面的后执行.这种方法还是不方便一眼看出执行顺序,可以通过@Order注解来控制这些切面通知的执行顺序.
这里的执行结果如下:
可以看到order中定义数字越小的通知,before越先执行,after越后执行.@orderko控制切面的有限级,先执行优先级较高的切面,后执行优先级较低的切面,最终执行目标方法.
3.5切点表达式
上面的表达式中,使用的是execution(....),根据方法签名来匹配方法.execution(...)的语法为: execution(<访问修饰符><返回类型><包名.类名.方法(方法参数)><异常>),其中的返回类型和异常可以省略.上面这个表达式使用的是execution,省略了访问修饰符和异常,返回类型为*,<包名.类名.方法(方法参数)>,一一对应.切点表达式支持通配符表达,*:匹配任意字符,只能匹配一个元素.类名使用*时,表示匹配任意类,包名使用*时表示匹配任意包,方法名使用*时,表示匹配任意方法.参数使用使用*时,表示一个任意类型的参数. ..:匹配多个连续的符号,可以通配任意层级的包,或任意类型,任意个数的参数.使用..配置报名,表示此包和此包下的所有子包 可以使用..配置参数,表示匹配任意个类型的参数.下面这个例子表示:
这个例子表示,com.example.demo.controller包下的TestController类下的t1方法,参数为无,访问修饰符为public 返回值类型为String,并且省略了异常. 如果省略访问修斯符:
匹配所有返回值类型
匹配TestController下的所有无参方法:
匹配TestController下的所有方法:
匹配controller包下的所有方法:
其他情况依次匹配就好. 上面介绍的是使用execution表达式来进行匹配方法,但是exectuion也有不适合的情景,比如我们要匹配TestController下的t1()方法和TestController下的t2()方法,这时候再使用execution这种切点表的是就显得不太方便了.这种情况可以使用@annotation来描述这一类的切点.实现步骤: 1.编写自定义注释 2.使用@annotation表达式来描述切点 3.在连接点的方法上添加自定义注解.
对这些代码进行测试:首先自定义注解@MyAspect.
@Target标识了Annotation所修饰的对象范围,即该注解可以作用在什么地方.ElementType.METHOD:用来描述方法. @Retention:指Annotation被保留的时间长短,标明注释的生命周期.RetentionPolicy.RUNTIME.RUNTIME:运行时注解,表明注释存在于源代码,字节码和运行时中.意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息,通常用于一些需要在运行时处理的注解,比如@Controller,@ResponseBody. 定义切面类:使用@annotation切点表达式定义切点,只对@MyAspect生效.
在测试方法上添加自定义的注解:这里如果想在u1和t1方法上添加注解,使用execution()是不易实现的,这里使用@MyAspect自定义注解.这里分别对test下的两个方法和user下的两个方法都发出请求,结果如下: