一、理论
AOP(Aspect-Oriented Programming), 即 面向切面编程
Spring中默认使用JDK动态代理来实现AOP,被代理的类至少实现了一个接口,我俗称接口代理。它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
CGLIB代理,我俗称继承代理,在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。
如果需要用CGLIB来实现AOP,需要在application配置文件中配置:
spring.aop.proxy-target-class=true
# Whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false).
或者在启动类上添加注解
@EnableAspectJAutoProxy(proxyTargetClass = true)
最主要的作用是:为分散的对象引入公共行为
AOP的通俗解释就是:调用A方法的时候要额外搞事情 !!!!!
二、注解解释
1.@Aspect注解声明该类是一个切面
切面就是一个类,这个类里面定义了:
1.在调用A方法的哪个时候要搞事情?是做A方法之前要搞事情?还是在做A方法之后要搞事情?还是在做A方法出错了要搞事情?
2.搞什么事情!
3.这个“调用A方法的时候要额外搞事情 !!!!!”的A方法到底是哪个方法? 到底是调用哪个方法要搞额外的事情?
这个切面类就是定义这些的!
2.@Component让此切面成为Spring容器管理的Bean
3.@Pointcut注解声明切点
切点定义的就是上述“做A方法的时候要额外搞事情”的A方法到底是哪个方法?
比如
@Pointcut("execution(public * com.sid.controller.TestAopController.aop(..))") public void webLog(){} @Pointcut("@annotation(com.sid.aop.MyAnnotation)") public void useMyAnnotation(){}
就是在说在做 com.sid.controller.TestAopController.aop方法的时候要额外的搞事情
在调用注解@MyAnnotation修饰的方法的时候要额外搞事情
execution():用于匹配方法执行的连接点
例如:匹配com.sid.controller.TestAopController类下所有public方法
@Pointcut("execution(public * com.sid.controller.TestAopController.*(..))")
args() 用于匹配当前执行的方法传入的参数为指定类型的执行方法;
target() 用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
within() 用于匹配指定类型内的方法执行
例如
// 匹配指定包中的所有的方法, 但不包括子包
within(com.sid.controller.*)
// 匹配指定包中的所有的方法, 包括子包
within(com.sid.controller..*)
// 匹配当前包中的指定类中的方法
within(TestAopController)
// 匹配一个接口的所有实现类中的实现的方法
within(UserMapper+)
this() 用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
bean() 匹配 Bean 名字
// 匹配以指定名字结尾的 Bean 中的所有方法
bean(*Service)
切点表达式组合
// 匹配以 Service 或 ServiceImpl 结尾的 bean
bean(*Service || *ServiceImpl)
// 匹配名字以 Service 结尾, 并且在包 com.xys.service 中的 bean
bean(*Service) && within(com.xys.service.*)
@args() 用于匹配当前执行的方法传入的参数持有指定注解的执行;
@target() 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@within() 用于匹配所以持有指定注解类型内的方法
@annotation() 用于匹配当前执行方法持有指定注解的方法;
4.Joint point:表示在程序中明确定义的点
“调用A方法的时候要额外搞事情 !!!!!”,Joint point表示的就是这个被调用的A方法,里面有A方法的一些调用信息,包括名字、方法入参。
Joint point通常作为@Advice增强注解修饰的方法的入参
如:
@Before("webLog()||useMyAnnotation()") public void deBefore(JoinPoint joinPoint) throws Throwable { System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs())); }
这样在“在A方法执行前做deBefore方法”的逻辑中,通过join point可以得到A方法的信息,比如A方法的实际入参,方法名字。
5.order(X)指定拦截顺序,数字越小越先执行
标识切面的优先级
6.增强@advice
增强注解Advice主要是定义“什么时候搞事情”,
而增强注解修饰的方法中的逻辑,则是“搞什么事情?”
不同的增强@注解,表达的搞事情的时期不一样,有如下5个
1.@Around:在切入点前后切入内容,并自己控制何时执行切入点自身的内容
2.@Before在切入点开始处切入内容
3.@After在切入点结尾处切入内容,类似于final增强,不管是抛出异常或者正常退出都会执行
4.@AfterReturning在切入点return之后切入内容(返回值回调,可以用来对处理返回值做一些加工处理)
5.@AfterThrowing:当切入内容部分抛出异常之后的处理逻辑
执行顺序 图来自Spring AOP @Before @Around @After 等 advice 的执行顺序
1.正常情况
2.异常情况
3.同一个方法被多个Aspect类拦截
在表示什么时候搞事情的增强注解的入参表示哪些PointCut关联
比如:这表示切点webLog()和切点useMyAnnotation()都要走这个Before增强。
@Pointcut("execution(public * com.sid.controller.TestAopController.aop(..))") public void webLog(){} @Pointcut("@annotation(com.sid.aop.MyAnnotation)") public void useMyAnnotation(){}
@Before("webLog()||useMyAnnotation()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
三、代码演示
引包
<!-- spring-boot的web启动的jar包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
切面代码
package com.sid.util.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @program: springboot
* @description:
* @author: Sid
* @date: 2018-11-19 14:38
* @since: 1.0
**/
@Order(1)
@Aspect
@Component
public class LogAspect {
/**
* execution()切点函数
* 除了execution匹配切点外还支持其他方式如下 args() @args() this() target() @target() within() @within() @annotation
*/
// @Pointcut("execution(public * com.sid.controller.TestAopController.aop(..))||@annotation(com.sid.util.aop.MyAnnotation)")
// public void webLog(){}
@Pointcut("execution(public * com.sid.controller.TestAopController.aop(..))")
public void webLog(){}
@Pointcut("@annotation(com.sid.util.aop.MyAnnotation)")
public void useMyAnnotation(){}
@Before("webLog()||useMyAnnotation()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
System.out.println("@Before start");
System.out.println("URL : " + request.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + request.getMethod());
System.out.println("IP : " + request.getRemoteAddr());
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
System.out.println("@Before end");
}
//环绕通知,环绕增强,相当于MethodInterceptor
//除了@Around外,其他方法加不加JoinPoint参数都行
@Around("webLog()||useMyAnnotation()")
public Object around(ProceedingJoinPoint pjp) {
System.out.println("@Around start.....");
try {
//pjp.proceed()执行被切面的方法 即是到@Before中
System.out.println("@Around proceed do");
Object o = pjp.proceed();
System.out.println("@Around proceed done,real method return is :" + o);
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
//后置异常通知
@AfterThrowing(throwing = "ex", pointcut = "webLog()")
public void doThrows(JoinPoint jp,Exception ex){
System.out.println("@AfterThrowing do: "+ex.getMessage());
}
//后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
@After("webLog()||useMyAnnotation()")
public void after(JoinPoint jp){
System.out.println("@After do");
}
//returning = “XXX”,XXX即为在controller里方法的返回值
@AfterReturning(returning = "ret", pointcut = "webLog()||useMyAnnotation()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
System.out.println("@AfterReturning do real method return is : " + ret);
}
}
自定义注解代码
package com.sid.util.aop;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE}) //注解的作用目标,即作用域
@Retention(RetentionPolicy.RUNTIME) //表示注解的声明周期,即可以是源码阶段、编译时、运行时
@Documented //标识在生成javadoc文档时包含该注解信息
public @interface MyAnnotation {
String desc() default "MyAnnotation text";
}
测试controller代码
package com.sid.controller;
import com.sid.util.aop.MyAnnotation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @program: springboot
* @description:
* @author: Sid
* @date: 2018-11-19 14:37
* @since: 1.0
**/
@Controller
public class TestAopController {
@RequestMapping("/aop")
@ResponseBody
public String aop(String test) {
System.out.println("real TestAopController aop method do");
String result = "TestAopController aop method params is: "+test;
return result;
}
@RequestMapping("/myAnnotation")
@ResponseBody
@MyAnnotation(desc = "user @MyAnnotation set desc")
public String myAnnotation(String test) {
System.out.println("real TestAopController myAnnotation method do");
String result = "TestAopController myAnnotation method params is: "+test;
return result;
}
@RequestMapping("/aopException")
@ResponseBody
public String aopException(String test) {
System.out.println("real TestAopController aopException method do");
//int i = 1 / 0;
String result = "TestAopController aopException method params is: "+test;
return result;
}
}
运行结果
1.测试AOP
@Around start.....
@Around proceed do
@Before start
URL : http://localhost:8088/sid/aop
HTTP_METHOD : POST
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.sid.controller.TestAopController.aop
ARGS : [hello word]
@Before end
real TestAopController aop method do
@Around proceed done,real method return is :TestAopController aop method params is: hello word
@After do
@AfterReturning do real method return is : TestAopController aop method params is: hello word
2.测试自定义注解
@Around start.....
@Around proceed do
@Before start
URL : http://localhost:8088/sid/myAnnotation
HTTP_METHOD : POST
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.sid.controller.TestAopController.myAnnotation
ARGS : [hello word]
@Before end
real TestAopController myAnnotation method do
@Around proceed done,real method return is :TestAopController myAnnotation method params is: hello word
@After do
@AfterReturning do real method return is : TestAopController myAnnotation method params is: hello word