一、AOP
1、AOP定义:
AOP是一个面向切面的编程,基于动态代理,动态将代码功能切入到所需要的地方,对原代码无入侵
生活理解:(选择性阅读)
- 在健身房跑步机跑步,你戴着心率表,只管跑你的步;
- 跑前/跑时,我走过去,暂停跑步机,记录第一次生命特征信息,再打开跑步机,你继续接着跑;
- 红色部分:你一直在实现跑步的功能(相当于原代码功能一直在执行)
- 紫色部分:
- 我就是那个AOP,我的一系列动作,是我在做的;
- 我在你跑前/跑时的时候,我去切断你的动作,我先执行我的一系列动作,然后是我让你恢复继续跑;我的存在和动作,都不会改变你脑子在想跑的功能
- (所以我对你跑的代码,是没有入侵的,跟你没关系)
- note:那么就会有同学想到,同步调用方法不也可以实现吗?
- 我在执行:上述AOP方式
- 你在执行:调用方法方式
- 如果把上述中我的动作,以调用方法的方式去做。那么这个调用、执行这些动作,就不是我在执行,是你在调用,执行;
- 原本你的只需要管你跑步就行,但是你又要去管记录生命特征信息的动作(此时就是对你跑的代码,存在入侵)
- 结论:
- AOP方式:我想给你,给他,给谁,什么时间,去执行,都是我来管,跟你没关系(对原代码无入侵)
- 调用方法方式:你是否记录,什么时间记录,或者他要记录,那么你他都需要增加/减少代码(对原代码有入侵)
2、AOP常见作用:
- 日志记录
- 性能统计
- 安全控制
- 事务处理
- 异常处理
- 扩展
3、AOP相关概念
- Aspect(切面): Aspect 切面类,包含着一些 切入点(Pointcut) 以及通知( Advice)。
- Joint point(连接点):表示Target(目标对象)中明确定义的点。
- Pointcut(切点):通过通配、正则表达式等方式定义,它定义了相应的 Advice(通知) 将要发生的地方。
- Advice(通知):具体要操作的内容
- Target(目标对象):织入 Advice(通知) 的目标对象.。
- Weaving(织入):将 Aspect 切面类 和其他对象连接起来, 并创建 Adviced object 的过程
-
4、AOP重要的六个注解,两个对象
-
Pointcut(切点)中包含一个重要注解
- 切入点(Pointcut)
- 通过通配、正则表达式等方式定义
- 切入点(Pointcut)
-
Advice(通知)中包含五个重要注解
- 前置通知(@Before)
- 连接点(JoinPoint)之前执行的通知
- 后置通知(@After Advice)
- 连接点退出时,执行的通知(无视异常,异常时也会执行)
- 返回后通知(After Return Advice)
- 连接点正常完成后执行的通知(异常时不执行)
- 异常通知(After Throwing Advice)
- 在方法抛出异常退出时执行的通知
- 环绕通知(Around Advice)
- 可以代替前面四种
- 前置通知(@Before)
-
两个对象
- JoinPoint
- 封装了切面方法的信息
- ProceedingJoinPoint
- 继承JoinPoint的子类,有两个用于“执行连接点方法”的方法
- 两者关系:
-
- JoinPoint
5、 切入点(Pointcut)表达式
语法:execution(访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表))
note:访问修饰符可省略
-
标准表达式:
- public void com.aop.service.impl.xxxxImpl.run();
-
省略修饰符表达式:
- void com.aop.service.impl.xxxxImpl.run();
-
返回值“*”,表示任意返回值
- * com.aop.service.impl.xxxxImpl.run();
-
包名“*”,表示任意包
- * *.*.*.*.xxxxImpl.run();
- note:一个包对应一个*
-
包名“..”,表示当前包及其子包
- * *..xxxxImpl.run();
-
类名和方法名 “*”
- * *..*.*();
-
参数列表的写法
- 基本类型直接写 long
- 引用类型写全包名
- 用“*”:有参数时,任意类型
- 用“..”:表示有或无参数,有时,可表示任意类型
- 例:* *..*.*(..)
-
note:业务层语法需要在参数列表后面加异常
-
execution(访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表) 异常)
-
* void com.aop.service.impl.*.*(..)**;
-
6、代码实现
第一步:AOP拦截类
@Aspect
@Component
public class TestAspect {
private Logger log = LoggerFactory.getLogger(this.getClass());
public TestAspect() {
}
@Pointcut("execution(* com.xxx.study.controller.*.*(..))")
public void log() {
}
/**
* 目标方法执行前
*/
@Before("log()")
public void doBefore() {
System.out.println();
System.out.println("-------doAfter-------");
System.out.println("-------第一步-------");
}
/**
* 目标方法执行响应结果后
* 出现异常不出现
* @param result
*/
@AfterReturning(pointcut = "log()",returning = "result")
public void doAfterReturn(Object result) {
System.out.println("----AfterReturning----");
System.out.println("-------第二步-------");
}
/**
* 目标方法抛出异常时
* @param joinPoint
* @param e
*/
@AfterThrowing(pointcut = "log()",throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint,Exception e){
System.out.println("-------异常-------");
}
/**
* 目标方法执行之后
* note:无视异常
* @param joinPoint
*/
@After("log()")
public void doAfter(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
String ip = request.getRemoteAddr();
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
RequestMethod requestMethod = new RequestMethod(url, ip, classMethod, args);
System.out.println(requestMethod.toString());
System.out.println("----doAfter----");
System.out.println("-------第三步-------");
}
/**
* 可以代替前四种
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("log()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("----around:第一步----");
Object ret = proceedingJoinPoint.proceed();
System.out.println("-------around:第三步-------");
return ret;
}
private class RequestMethod {
private String url;
private String ip;
private String classMethod;
private Object[] args;
public RequestMethod(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}
@Override
public String toString() {
return "RequestMethod{url='" + this.url + '\'' + ", ip='" + this.ip + '\'' + ", classMethod='" + this.classMethod + '\'' + ", args=" + Arrays.toString(this.args) + '}';
}
}
}
第二步:请求接口方法
@RestController
@RequestMapping({"/entry"})
public class TestEntry {
@RequestMapping({"/hello"})
public String helloTest() {
System.out.println("----目标方法:进入----");
// 测试@AfterThrowing将下面代码注释去掉,执行测试一下
// int s = 1/0;
return "----目标方法:响应----";
}
}
第三步:请求接口测试
- @Around:在最开始和最后面,包裹所有
-
@Before:目标方法执行前,第一步
-
目标方法执行
-
@AfterReturning:在目标方法响应后执行;出现异常不出现
-
@After:在目标方法响应结束,表示目标方法整个都结束后执行;无视异常
-
@AfterThrowing:在Before和After之间,执行目标方法时,抛出异常会执行
note:
- 注意看此处我将目标方法中的int s = 1/0打开,让其报错
- 在如下图片中,目标方法进入后,抛出异常;
- 执行了AOP中的@AfterThrowing方法
- 最后还执行了@After方法,注意此方法是无视异常的