AOP
-
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
- 作用:在不惊动原始设计的基础上为其进行功能增强。
-
spring理念:无入侵式编程(没有改变代码,但是功能发生了变化)
-
连接点(JoinPoint):原始方法
- 在SpringAOP中,理解为方法的执行。
-
切入点(Pointcut):匹配追加功能的方法
- 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法。
-
通知(Advice):提取共性功能,在切入点处执行的操作
- 在SpringAOP中,功能最终以方法的形式呈现
-
通知类:定义通知的类
-
切面(Aspect):描述通知和切入点的对应关系
- 通知和切入点一一对应
AOP快速实现
-
导入坐标
-
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
-
-
定义通知类
-
@Component//配置bean @Aspect//告知spring该类为通知类 public class MyAdvice { //表示执行该连接点方法时,触发AOP通知 @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pointCut(){//切入点方法为私有方法 } @Before("pointCut()")//切面:绑定通知方法与before的关系:在pointCut()方法前面执行。 public void method(){//通知方法:增强原始方法的功能 System.out.println(System.currentTimeMillis()); } }
-
-
在spring配置类上添加@EnableAspectJAutoProxy
-
@Configuration @ComponentScan("com.itheima") @EnableAspectJAutoProxy//开启注解开发AOP功能 public class SpringConfig { }
-
AOP工作流程
- Spring容器启动
- 读取切面配置中所有的切入点
- 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 获取bean,调用方法并执行,完成操作。
- 匹配成功,创建目标对象的代理对象
- 根据代理对象的运行模式,运行原始方法与增强的内容,完成操作。
- 匹配失败,创建对象
AOP切入点表达式
- 切入点:要进行增强的方法
- 切入点表达式:要进行增强的方法的描述方式
- 描述实现类中的方法
- 描述接口中的方法
- 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
- 访问修饰符:public,private等,可以省略
- 异常名:方法定义中抛出指定异常,可以省略
- 使用通配符描述切入点
-
*:表示单个任意符号
-
… :表示多个连续任意符号
-
+:专用于匹配子类类型
-
AOP通知类型
-
AOP通知分为5种类型:在通知方法上加入不同注解
-
前置通知:@Before
-
@Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt(){} //@Before:前置通知,在原始方法运行之前执行 @Before("pt()") public void before() { System.out.println("before advice ..."); }
-
-
后置通知:@After
-
@Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt(){} //@After:后置通知,在原始方法运行之后执行 @After("pt()") public void after() { System.out.println("after advice ..."); }
-
-
环绕通知(重点):@Around
-
@Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt(){} /** * 环绕通知,在原始方法运行的前后执行 * @param pjp 必须使用形参ProceedingJoinPoint才能实现对原始方法的调用 * @return Object 原始方法有返回值需要将返回值类型定义为Object * @throws Throwable 无法判断原始方法是否有异常需要强制抛出 */ @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before advice ...");//原始操作前 Object ret = pjp.proceed();//表示对原始操作的调用 System.out.println("around after advice ...");//原始操作后 return ret;//如果原始方法有返回值需要return }
-
-
返回后通知:@AfterReturning
-
/** * 返回后通知,原始方法抛异常,该方法不执行。 */ @AfterReturning("pt2()") public void afterReturning() { System.out.println("afterReturning advice ..."); }
-
-
抛出异常后通知:@AfterThrowing
-
/** * 抛出异常后通知,在原始方法执行过程中出现异常后运行 */ @AfterThrowing("pt2()") public void afterThrowing() { System.out.println("afterThrowing advice ..."); }
-
-
-
万次执行时间案例练习:
-
@Component @Aspect public class ProjectAdvice { @Pointcut("execution(* com.itheima.service.*Service.*(..))") private void servicePt(){ } @Around("servicePt()") public void runSpeed(ProceedingJoinPoint pjp) throws Throwable { Signature signature = pjp.getSignature();//通过pjp对象获得签名 String className = signature.getDeclaringTypeName();//通过签名获取原始方法所在路径名 String methodName = signature.getName();//通过签名获取原始方法名 long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { Object proceed = pjp.proceed(); } long end = System.currentTimeMillis(); long time = end - start; System.out.println("执行万次" + className + "." + methodName + "方法,耗费时间:" + time + "ms"); } }
-
AOP通知获取数据
-
获取切入点方法的参数
-
JoinPoint:适用于前置、后置、返回后、抛出异常后通知
-
/** * * @param jp 用于描述切入点的对象,必须配置成通知方法中的第一个参数,可用于获取原始方法调用的参数 */ @Before("pt()") public void before(JoinPoint jp) { Object[] args = jp.getArgs();//获取切入点方法传入的参数 System.out.println(Arrays.toString(args)); System.out.println("before advice ..." ); }
-
-
ProceedJointPoint:适用于环绕通知
-
/** * * @param pjp 专用于环绕通知,是JoinPoint子类,可以实现对原始方法的调用 * @return */ //@Around("pt()") public Object around(ProceedingJoinPoint pjp) { Object[] args = pjp.getArgs();//获取原始方法的参数 System.out.println(Arrays.toString(args)); args[0] = 666;//将原始方法的参数进行处理 Object ret = null; try { ret = pjp.proceed(args);//将修改的参数传入原始方法 } catch (Throwable t) { t.printStackTrace(); } return ret; }
-
-
-
获取切入点方法返回值
-
返回后通知
-
//设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同,JoinPoint参数位置必须是第一位 @AfterReturning(value = "pt()",returning = "ret")//returning接收切入点方法的返回值,并将返回值作为形参传入 public void afterReturning(JoinPoint jp,String ret) { System.out.println("afterReturning advice ..."+ret); }
-
-
环绕通知:Object ret = pjp.proceed();
-
-
获取切入点方法运行异常信息
-
抛出异常后通知
-
//设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同 @AfterThrowing(value = "pt()",throwing = "t")//throwing接收切入点方法的异常对象,并将异常对象作为形参传入 public void afterThrowing(Throwable t) { System.out.println("afterThrowing advice ..."+t); }
-
-
环绕通知
-
-
案例:百度网盘密码数据兼容处理
-
@Component @Aspect public class DataAdvice { @Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))") private void servicePt(){ } @Around("servicePt()") public Object trimStr(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs();//获取传入参数 for (int i = 0; i < args.length; i++) { if (args[i].getClass().equals(String.class)){ //如果参数类型是字符串 args[i] = args[i].toString().trim();//去除空格 } } Object ret = pjp.proceed(args);//获取返回值 return ret; } }
-