AOP概念:AOP是一种设计思想,的目的就是在不修改源代码的基础上,对原有功能进行增强
AOP的实现涉及到spring的动态代理,spring有两种代理方式:
- jdk动态代理:jdk动态代理只提供接口代理,通过实现代理接口创建代理类对象
- CGLIB动态代理:CGLIB动态代理是通过继承被代理类创建代理类对象的
spring2.0之前默认是使用JDK动态代理创建代理类对象的,因为JDK动态代理效率较高
spring2.0之改为了CGILB动态代理,因为CGILB动态代理不容易出现转换错误
# true: 使用cglib动态代理(默认)
# false: 使用jdk动态代理
spring.aop.proxy-target-class=false
Spring会根据被代理的类是否有接口去自动选择代理方式
AOP中相关概念
- Target(目标对象):就是需要增强功能的方法对应的类产生的对象
- Joinpoint(连接点):目标对象中的所有方法
- Pointcut (切入点):切入点是连接点的过滤条件,确定要对哪些方法增强。
- Advice(通知):就是共性功能,一个具体的增强功能(日志、事务...)
- Aspect (切面):通知 + 切点的配置,切面是一种描述,描述通知和切点之间的关系,一个什么样的增强功能加入到了哪些的切点的什么位置上
- Proxy(代理):通过代理(JDK/CGLib)创建处理具有增强功能的对象
如果需要使用AOP切面增强方法
第一步:导入jar
<!--AOP相关注解和配置解析实现-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
第二步:准备目标类+增强类
-
目标类:AccountServiceImpl
@Slf4j //使用Slf4j记录日志 @Service public class AccountServiceImpl implements AccountService { @Autowired private TimeLog timeLog; @Override public void updateBalance(Account account) { timeLog.start(); //调用mapper完成更新 System.out.println("updateBalance:"+account); //记录结束时间并计算耗时 timeLog.end(); } @Override public Account getById(Integer id) { //记录开始时间 timeLog.start(); //模拟从mapper查询的结果 Account account = new Account(1,"tom",100D); //记录结束时间并计算耗时 timeLog.end(); return account; } @Override public List<Account> list() { //记录开始时间 timeLog.start(); //模拟假数据 ArrayList<Account> accounts = new ArrayList<>(); accounts.add(new Account(1,"tom",100D)); accounts.add(new Account(2,"jack",200D)); accounts.add(new Account(3,"lily",300D)); //记录结束时间并计算耗时 timeLog.end(); return accounts; } }
-
增强类:TimeLog
@Slf4j @Component public class TimeLog { private Long start = 0L; /*记录开始时间*/ public void start(){ //记录开始时间 start = System.nanoTime(); } /*计算耗时*/ public void end(){ Long end = System.nanoTime(); log.info("用时:"+(end-start)+" 纳秒"); } }
第三步:在增强类中进行切面配置
- 加@Aspect和@Component注解:标识这是个增强/增强类并交给容器管理
- 定义一个空方法加@Pointcut注解并指定增强位置:标识这个方法是个切点方法
- 再在增强类中的方法加环绕注解,指定切点方法:告诉spring在执行切点
@Component
@Aspect
public class LogAspect extends ConsoleHandler {
//对加了LogAnnotation中所有方法进行增强
@Pointcut("@annotation(cn.ffcs.uam.usercenter.annotation.LogAnnotation)")
public void pt() {
}
//环绕通知
@Around("pt()")
public Object addLog(ProceedingJoinPoint proceedingJoinPoint) {
Object result = null;
OperLog operLog = new OperLog();
try {
//得到当前方法入参列表
System.out.println("在被增强方法之前执行");
//执行目标方法,得到结果
result = proceedingJoinPoint.proceed()
//被增强方法之后执行
System.out.println("实际传入参数:"+ Arrays.toString(proceedingJoinPoint.getArgs()));
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
System.out.println("方法所属类:"+signature.getDeclaringTypeName());
System.out.println("方法名:"+signature.getName());
System.out.println("参数类型:"+ Arrays.toString(signature.getParameterTypes()));
System.out.println("返回值类型:"+signature.getReturnType());
} catch (Throwable throwable) {
System.out.println("【环绕通知】在目标方法出现异常之后执行...异常信息:" + throwable.getMessage());
//一定要把异常抛出去,否则事务失效
throw new RuntimeException(throwable);
} finally {
System.out.println("【环绕通知】不管目标方法是否出现异常都会执行...");
}
return result;
}
}
ProceedingJoinPoint类常用API
//---------------------------
System.out.println("实际传入参数:"+Arrays.toString(pjp.getArgs()));
MethodSignature signature = (MethodSignature) pjp.getSignature();
System.out.println("方法所属类:"+signature.getDeclaringTypeName());
System.out.println("方法名:"+signature.getName());
System.out.println("参数类型:"+ Arrays.toString(signature.getParameterTypes()));
System.out.println("返回值类型:"+signature.getReturnType());
//---------------------------
result = pjp.proceed();//执行目标方法
当一个方法有多个切面增强类匹配时
切面类按照bean名称默认规则或者order排序起来,先执行所有的前置通知---->执行被增强方法(被增强方法只执行一次)----->后置通知
前置通知先执行排序最小的,后置通知先执行排序最大的
通知起作用的前提:执行该方法的对象是被代理对象
被增强类方法中调用本类其他方法时会导致通知失效,因为本类方法中调用本类其他方法本质是本类对象调用,而非代理对象,故会出现通知失效的情况,解决方法:
1.将本类注入进本类局部变量中,并打开循环依赖
/*
通知失效
*/
@Service
@Slf4j
public class AdviceInvalid {
//注入代理对象,但是此时出现自己依赖自己,循环依赖
@Autowired
AdviceInvalid adviceInvalid;
public void fun1(){
log.info("fun1()执行了...");
//方法内部调用当前对象的方法
//this.fun2();
//System.out.println(this); //打印调用方法的对象
adviceInvalid.fun2(); //使用代理对象调用方法
}
public void fun2(){
log.info("fun2()执行了...");
System.out.println(this);
}
}
spring.main.allow-circular-references=true