总结记录一下AOP经常使用的应用场景及使用方式,若有错误,请留言.java
1. 讲AOP以前,先来总结web项目的几种拦截方式web
A: 过滤器 正则表达式
使用过滤器能够过滤URL请求,以及请求和响应的信息,可是过滤器是只是针对J2EE规范实现的,没法判断ServletRequest请求是由哪一个controller方法处理spring
B: 拦截器编程
拦截器能够获取到URL的请求和响应信息,以及处理请求的controller方法信息,可是没法获取方法的参数,要使用spring 提供的拦截器,具体的作法以下:缓存
a. 实现spring 提供的拦截器接口tomcat
b. 实现拦截器接口的3个方法,方法的参数 handler 实际上就是处理请求的 controller的方法声明, 可强转为 HandlerMethod 对象,HandlerMethod对象能够获得方法所在控制器类名,方法名称,可是没法获取方法的参数安全
--若是想获取参数信息,使用切面Aspect(spring核心功能 AOP的实现)性能优化
ps: 拦截器会拦截全部控制器的方法的调用,不光是咱们本身写的,spring 框架提供的控制器也会被拦截框架
C: 切面
切面能获取处处理请求的方法,方法所属的类实例,方法的返回值、全类名、参数类型,方法的上注解等信息
ps: spring AOP 是基于 切面(AspectJ) 实现的
关于以上几种拦截方式执行顺序的说明:
过滤器优先于拦截器执行,拦截器优先于切面执行,可是晚于切面结束,最后过滤器晚于拦截器结束
具体顺序以下:
过滤器DoFilter方法开始->拦截器preHandle方法开始执行->切面方法开始->controller方法->切面方法结束->拦截器postHandle,afterCompletion方法开始执行,执行后拦截器结束->过滤器DoFilter方法结束
程序出现异常的处理顺序:
若是 controller方法 出现异常,最早捕获到异常的是的切面,若是切面抛出异常,由控制器异常处理器处理,若是控制器异常处理器不处理,异常会抛到拦截器,若是拦截器未处理,异常会抛到过滤器,若是过滤器未处理会抛到tomcat返回给用户
2. 关于AOP的介绍
有一篇博客已经介绍的很详细了 这里直接引用过来,你们也能够访问原地址获取更多: https://blog.csdn.net/wuruijiang/article/details/78970720
-- 什么是AOP
AOP(Aspect-OrientedProgramming,面向方面编程),能够说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来创建一种对象层次结构,用以模拟公共行为的一个集合。当咱们须要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP容许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码每每水平地散布在全部对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其余类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它致使了大量代码的重复,而不利于各个模块的重用。
而AOP技术则偏偏相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减小系统的重复代码,下降模块间的耦合度,并有利于将来的可操做性和可维护性。AOP表明的是一个横向的关系,若是说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以得到其内部的消息。而剖开的切面,也就是所谓的“方面”了。而后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特色是,他们常常发生在核心关注点的多处,而各处都基本类似。好比权限认证、日志、事务处理。Aop 的做用在于分离系统中的各类关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法建立“方面”,从而使得编译器能够在编译期间织入有关“方面”的代码。
-- AOP使用场景
AOP用来封装横切关注点,具体能够在下面的场景中使用:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
-- AOP相关概念
方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用spring的 Advisor或拦截器实现。
链接点(Joinpoint): 程序执行过程当中明确的点,如方法的调用或特定的异常被抛出。
通知(Advice): 在特定的链接点,AOP框架执行的动做。各类类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器作通知模型,维护一个“围绕”链接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice
切入点(Pointcut): 指定一个通知将被引起的一系列链接点的集合。AOP框架必须容许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,能够经过名字很清楚的理解, MethodMatcher是用来检查目标类的 方法是否能够被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上
引入(Introduction): 添加方法或字段到被通知的类。 Spring容许引入新的接口到任何被通知的对象。例如,你可使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有经过DelegatingIntroductionInterceptor来实现通知,经过 DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
目标对象(Target Object): 包含链接点的对象。也被称做被通知或被代理对象。POJO
AOP代理(AOP Proxy): AOP框架建立的对象,包含通知。 在Spring中,AOP代理能够是JDK动态代理或者CGLIB代理。
织入(Weaving): 组装方面来建立一个被通知对象。这能够在编译时完成(例如使用AspectJ编译器),也能够在运行时完成。Spring和其余纯Java AOP框架同样,在运行时完成织入。
3. spring AOP的一些注解及API 说明
在开发中一般咱们会使用 AspectJ 注解实现.
A: 经常使用的切面有如下几种,它决定了切入方法的位置:
前置:@Before
后置:@After
返回值:@AfterReturing
异常:@AfterThrowing
环绕:@Around
B: 链接点
JoinPoint、ProceedJoinPoint 链接点 其实就是切面表达式覆盖的方法,根据该链接点能够获取多个信息,例如 处理请求的方法,方法所属的类实例,方法的返回值、全类名、参数类型,方法的上注解等信息,经常使用的方法以下:
getSignature():获取链接点方法的返回值、全类名、参数类型
getTarget():获取链接点方法所属的类实例
getArgs():获取链接点方法的参数列表
getThis() :获取代理对象自己
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行链接点方法的方法 :
proceed() : 经过反射执行目标对象的链接点处的方法
proceed(java.lang.Object[] args) : 经过反射执行目标对象链接点处的方法,不过使用新的入参替换原来的入参
JoinPoint 通常用在除了@Around 注解的方法上,ProceedJoinPoint 通常用在@Around 中(由于须要使用 ProceedJoinPoint 的 proceed()方法进行目标方法的执行)
B: 切点表达式
切点表达式就是方法(链接点)的匹配表达式,用于说明切点要匹配的方法路径:
例如 : * com.qxl.web.controller.userController.*(..)
表示此方法在 com.qxl.web.controller.userController下的任何方法,方法包含任何参数,任何返回值 的方法上都能执行
第一个 * : 表示方法任意权限,返回值为任意值都能执行此加强逻辑
第二个 * : userController 下的任何方法都能执行此加强逻辑
(..) : 表示方法里的参数为任意值 (不管参数多少,大小,类型)均可以执行此加强逻辑
3. spring AOP 的具体例子, 具体可见代码
package com.qxl.web.aspect; import java.lang.reflect.Method; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import com.qxl.web.controller.userController; /** * 使用切面实现url拦截: * 切片的实现分为2个步 A: 切入点(使用 @Before @After @Around 等注解实现 : 用于表示 在那些方法上(注解参数)起做用 ,在何时(注解类型)起做用) * B: 加强 (起做用的时候执行的业务逻辑) * 具体以下: * 1. 定义一个类,添加 @Aspect 注解,代表这是一个切片类,同时加上@Component注解将这个类在spring容器中声明 * 2. 在你的加强逻辑的方法上使用 @Before @After @Around 等注解,能够传递参数 ProceedingJoinPoint 对象, * ProceedingJoinPoint对象相似于拦截器的 handler,可获取加强的方法的信息,类的名称和方法名称以及方法参数等等 * 3. 编写逻辑代码完善要加强方法的逻辑,以下的handleControllerMethod方法 * * @author qxl * */ @Aspect // spring 切面类须要添加此注解 @Component public class AspectDemo { //@Before //在要加强方法以前执行 //@After //在要加强方法以后执行 /** * 在要加强方法上环绕执行(先后均可以执行) * 注解参数: execution(* com.qxl.web.controller.userController.*(..)) * 表示此方法在 com.qxl.web.controller.userController下的任何方法,方法包含任何参数,任何返回值 的方法上都能执行 * 第一个 * : 表示方法返回值为任意值都能执行此加强逻辑 * 第二个 * : userController 下的任何方法都能执行此加强逻辑 * (..) : 表示方法里的参数为任意值 (不管参数多少,大小,类型)均可以执行此加强逻辑 * @throws Throwable */ @Around("execution(* com.qxl.web.controller.userController.*(..))") public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("Aspect start"); //getTarget() 得到链接点所属类的实例 Object target = pjp.getTarget(); if (target instanceof userController) { System.out.println("当前链接点方法是userController类中的方法"); } //获得链接点方法的参数 Object[] args = pjp.getArgs(); for (Object arg : args) { System.out.println("arg is "+ arg); } //getSignature():signature是信号,标识的意思,获取被加强的方法相关信息 Signature signature = pjp.getSignature(); //获得方法名称 System.out.println("Aspect 拦截到了" + signature.getName() +"方法..."); //获得方法所在的包名 System.out.println("方法所在的包名:"+ signature.getDeclaringTypeName()); //获得方法上的注解--若是 方法上存在一个 @Permission 注解 MethodSignature methodSignature = (MethodSignature)signature; Method method = methodSignature.getMethod(); if(method.isAnnotationPresent(Permission.class)){ //获得这个注解 Permission permission = method.getAnnotation(Permission.class); //.....other operation } //获取方法参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); // 获得方法的返回值类型 Class<?> returnType = method.getReturnType(); //proceed()方法用来调用ProceedingJoinPoint对象获取到的那个的方法(即执行切片要加强的那个方法),proceed()方法返回的Object就是加强方法的返回值 // 若是proceed()方法传递了参数,会替换原来方法的参数 Object object = pjp.proceed(); System.out.println("Aspect end"); return object; } }