AOP:面向切面编程
SpringAop底层是动态代理技术(Proxy)
下面讲解在Spring中如何使用AOP
1.添加AOP起步依赖
<!--AOP起步依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2.写AOP类(AOP类中用Around注释记录的方法一定要有返回值,因为方法本身就应该有返回值)
package com.itheima.aop; /* * 该类用于统计Service中个方法的运行时间 切面类 * */ import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Slf4j //日志 @Component // 将当前组件放入spring容器中 @Aspect // 当前类为切面类 public class TimeAspect { @Around("execution(* com.itheima.service.impl.*.*(..))") // execution()表示匹配方法 //Aroud()表示环绕通知 public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //1. 获取开始时间 long beginTime = System.currentTimeMillis(); //2.运行原方法 /* * ProceedingJoinPoint是Spring AOP提供的一个接口 用来获取原方法信息 * proceed()运行原方法 返回值为原方法的返回值 因此用Object来接并返回 * */ Object result = proceedingJoinPoint.proceed(); //3.获取结束时间 并计算运行时间 long end = System.currentTimeMillis(); log.info("\n"+proceedingJoinPoint.getSignature()+"运行时间:" + (end - beginTime)+"ms"); return result ; } }
AOP核心
1.通知类型:
- @Around:环绕通知 此类注解标注的通知方法在目标方法开始前结束后都被执行 报错最后不会执行 被此类注解标注的通知方法一定要有返回值 一定!
- @Before:前置通知 此类注解标注的通知方法在目标方法前执行
- @After:后置通知 此类注解标注的通知方法在目标方法后被执行 无论是否有异常都会执行
- @AfterReturning:返回后通知 此类注解标注的通知方法在目标方法后被执行,有异常不执行
- @AfterThrowing:异常后通知 此类注解标注的通知方法在目标方法发生异常后执行
2.@PointCut:该注解的作用是将公共的切点表达式抽取出来 需要用到时引用该切点即可
// 定义切点 切点为 com.itheima.service.impl包下所有类的所有方法 @Pointcut("execution(* com.itheima.service.impl.*.*(..))") public void pc(){};
3.其他通知类型使用该切点
@Around("pc()") // execution()表示匹配方法
4.通知执行顺序:当有多个切面的切入点都匹配到了目标方法,目标方法运行时 通知方法的执行顺序:默认按照类名的字符顺序执行
另一实现方法:使用@Order()注解
order中数字越小的在执行前越先执行 在执行后越后执行
5.切入点表达式
切入点表达式有两种形式,第一种是基于 execution()根据方法的签名来匹配,第二种是@annotation基于接口的形式进行匹配。
下面讲解第一种方式/* * execution()表示匹配方法 要在@pointcut中声明 * execution中表达式的基本形式是 * 访问修饰符 返回值类型 包名.类名.方法名(参数列表) throws 异常类型 * 其中访问修饰符可以省略,实际开发中也省略或者使用通配符来匹配 * 包名.类名可以省略,实际开发中不建议省略会导致匹配范围过大 * 参数列表中的值要填写清楚 比如Integer要写为java.lang.Integer * throws 异常类型可以省略,实际开发中并不会要求写throws * 例子 访问修饰符 返回值 包名. 类名 .方法名 (参数列表) 此方法没有抛出异常不需要声明 * execution(public void com.itheima.service.DeptService.deleteDept(java.lang.Integer)) * */ /* * execution中可以使用通配符来描述切入点 有两种通配符 * *可以统配单个独立的任意符号 * 例子 :execution(* com.*。service.*.update*(*)) * 第一个*通配任意返回值类型 第二个*通配任意一个包名 * 第三个*通配任意一个类名 第四个*通配任意一个以update开头的方法名称 * 第五个*通配任意一个参数且只能是一个 * ..可以通配多个连续的任意符号 可以匹配任意层级的包 或者任意类型 任意个数的参数 * 例子 :execution(* com.itheima..Deptservice.*(..)) * 第一个*通配任意返回值类型 第一个..通配任意层级的包 * 第二个*通配Deptservice下的任意一个方法 第二个..通配任意类型的任意个参数 * * * 为满足更繁琐的业务需求也可以使用&& || ! 等来描述比较复杂的切入点 * */ /* * 切入点表达式的书写规范 * 1.所有方法的命名尽可能规范 使可以使用*通配符来更好匹配 * 2.描述切入点尽量描述接口方法 方便后续修改而不是特定方法 * 3.在满足业务需求的情况下尽可能缩小切入点的范围 减少不必要的切入点而降低切入点带来的性能损失 * */
//切入点的返回类型为任意类型 包含com.itheima.service.impl包下所有类的所有方法 参数形式任意 @Pointcut("execution(* com.itheima.service.impl.*.*(..))") public void pc(){}; @Around("pc()") // execution()表示匹配方法 //Aroud()表示环绕通知 public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //1. 获取开始时间 long beginTime = System.currentTimeMillis(); //2.运行原方法 /* * ProceedingJoinPoint是Spring AOP提供的一个接口 用来获取原方法信息 * proceed()运行原方法 返回值为原方法的返回值 因此用Object来接并返回 * */ Object result = proceedingJoinPoint.proceed(); //3.获取结束时间 并计算运行时间 long end = System.currentTimeMillis(); log.info("\n"+proceedingJoinPoint.getSignature()+"运行时间:" + (end - beginTime)+"ms"); return result ; }
下面讲解第二种 基于接口的切入点表达式 使用方法
1.首先声明一个接口 (接口的名称任意)package com.itheima.aop; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /* * Target元注解描述的是该注解能注解的对象为 METHOD类型 * Retention元注解描述的是该注解的生命周期为RUNTIME也就是运行时 * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyLog { }
2.在目标方法上注明该注解 一点是方法上
@MyLog @Select("select * from emp where username = #{username} and password = #{password}") Emp selectByUsernameAndPassword(Emp emp);
3.在切入方法中的Pointcut中声明注解的全类名即可
@Pointcut("@annotation(com.itheima.aop.MyLog)") public void pc(){};
6.连接点:连接点就是能够被切面的方法
/*
* 在Spring中用JoinPoint抽象了连接点
* 用JoinPoint可以获取该方法执行的相信息 如目标类名 方法名 方法参数
*对于@Around环绕通知 获取连接点只能通过ProceedingJoinPoint类来获取
*对于其他四种通知方法 只可以通过JoinPoint来获取抽象连接点
* JoinPoint类的getSignature方法可以获取目标方法签名信息
* JoinPoint类的getArgs方法可以获取目标方法参数
* JoinPoint类的getTarget方法可以获取目标对象
* JoinPoint类的proceed方法可以令原方法执行
*JoinPoint是ProceedingJoinPoint的父类
* */
@Around("pc()")
public Object recordTime2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//1.获取目标类名
String className = proceedingJoinPoint.getTarget().getClass().getName();
//2.获取目标方法名
String MethodName = proceedingJoinPoint.getSignature().getName();
//3.获取目标方法参数 并封装在了Object[]中
Object[] args = proceedingJoinPoint.getArgs();
//4.运行原方法 并将该方法的返回值封装在Object中
Object result = proceedingJoinPoint.proceed();
return result ;
}
/*
* 对于其他四种通知方法 只可以通过JoinPoint来获取抽象连接点
* */
@Before("pc()")
public void AspectTest (JoinPoint joinPoint){
//1.获取目标类名
String className = JoinPoint.getTarget().getClass().getName();
//2.获取目标方法名
String MethodName = joinPoint.getSignature().getName();
//3.获取目标方法参数 并封装在了Object[]中
Object[] args = joinPoint.getArgs();
}