一、AOP概念
AOP(Aspect Oriented Programming)是一种编程范式。请看大佬的总结 AOP的简介
常用术语
切面(Aspect):
一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。
连接点(Joinpoint):
程序执行过程中某个特定的点,比如某方法调用或者处理异常
通知(Advice):
切面连接点的处理逻辑,也就是向连接点注入的代码。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
@Before: 标识一个前置增强方法,相当于BeforeAdvice的功能.
@After: final增强,不管是抛出异常或者正常退出都会执行.
@AfterReturning: 后置增强,似于AfterReturningAdvice, 方法正常退出时执行.
@AfterThrowing: 异常抛出增强,相当于ThrowsAdvice.
@Around: 环绕增强,相当于MethodInterceptor.
切入点(Pointcut):
JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式。
引入(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框架一样,在运行时完成织入。
二、AOP的实现
1、定义切面类
① 在类上使用 @Component 注解 把切面类加入到IOC容器中
② 在类上使用 @Aspect 注解 使之成为切面类
@Component
@Aspect
public class WebLogAspect {
}
2、定义切入点
定义方式:
execution:用于匹配方法执行的连接点;
within:用于匹配指定类型内的方法执行;
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
@within:用于匹配所以持有指定注解类型内的方法;
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
@annotation:用于匹配当前执行方法持有指定注解的方法;
建议使用@annotation,配合自定义注解,实现拦截
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface WebLogAspect {
}
@Pointcut("@annotation(com.zgn.blog.annotation.WebLogAspect)")
public void webLog(){}
三、AOP的通知
1、前置通知@Before
除非抛出异常,否则这个通知不能阻止连接点之前的执行流程
1)通过JoinPoint可以获得通知的签名信息,如目标方法名、目标方法参数信息等
2)通过RequestContextHolder来获取请求信息,Session信息
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable{
//获取目标方法的参数信息
Object[] obj = joinPoint.getArgs();
//AOP代理类的信息
joinPoint.getThis();
//代理的目标对象
joinPoint.getTarget();
//用的最多 通知的签名
Signature signature = joinPoint.getSignature();
//代理的是哪一个方法
logger.info("代理的是哪一个方法"+signature.getName());
//AOP代理类的名字
logger.info("AOP代理类的名字"+signature.getDeclaringTypeName());
//AOP代理类的类(class)信息
signature.getDeclaringType();
//获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
//如果要获取Session信息的话,可以这样写:
//HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
//获取请求参数
Enumeration<String> enumeration = request.getParameterNames();
Map<String,String> parameterMap = Maps.newHashMap();
while (enumeration.hasMoreElements()){
String parameter = enumeration.nextElement();
parameterMap.put(parameter,request.getParameter(parameter));
}
String str = JSON.toJSONString(parameterMap);
if(obj.length > 0) {
logger.info("请求的参数信息为:"+str);
}
}
2、后置返回通知
1)如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
2) 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
3) returning:限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,
4) 对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
@AfterReturning(value = "webLog()",returning = "keys")
public void doAfterReturning(JoinPoint joinPoint,Object keys){
log.info("第一个后置返回通知的返回值:"+keys);
}
@AfterReturning(value = "webLog()",returning = "keys",argNames = "keys")
public void doAfterReturning(String keys){
log.info("第二个后置返回通知的返回值:"+keys);
}
3、后置异常通知
定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
@AfterThrowing(value = "webLog()",throwing = "exception")
public void doAfterThrowing(JoinPoint joinPoint,Throwable exception){
if(exception instanceof NullPointerException){
logger.info("空指针异常");
}
}
4、后置最终通知
后置最终通知(目标方法只要执行完了就会执行后置通知方法)
@After(value = "webLog()")
public void doAfter(JoinPoint joinPoint){
log.info("doAfter success");
}
5、环绕通知
1)第一个参数必须是ProceedingJoinPoint类型。
2)在通知体内调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行
3)proceed()方法也可能会被调用并且传入一个Object[]对象,该数组中的值将被作为方法执行时的入参。
@Around(value = "webLog()")
public Object doAround(ProceedingJoinPoint joinPoint){
try {
Object obj = joinPoint.proceed();
return obj;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}