总结记录一下AOP常用的应用场景及使用方式,如有错误,请留言.
1. 讲AOP之前,先来总结web项目的几种拦截方式
A: 过滤器
使用过滤器可以过滤URL请求,以及请求和响应的信息,但是过滤器是只是针对J2EE规范实现的,无法判断ServletRequest请求是由哪个controller方法处理
B: 拦截器
拦截器可以获取到URL的请求和响应信息,以及处理请求的controller方法信息,但是无法获取方法的参数,要使用spring 提供的拦截器,具体的做法如下:
a. 实现spring 提供的拦截器接口
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和其他纯JavaAOP框架一样,在运行时完成织入。
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;
} }