AOP学习
- AOP介绍
- 使用
- 对业务方法添加计算时间的增强
- @EnableAspectJAutoProxy
- AOP的术语
- 通知
- 前置通知@Before
- 后置通知@After
- 返回通知@AfterReturning
- 异常通知@AfterThrowing
- 总结-通知执行顺序
- 切点表达式的提取-使用@Pointcut进行抽取
- 切点表达式的详细用法
- execution和@annotation组合
- SpringAOP和注解的使用-配置全局日志
AOP介绍
- 如何在Spring中创建一个所谓切面?
@Aspect+@Component+通知+切点 - 切面里面的代码怎么运行在业务方法(之前、之后)?
通知+切点
使用
实现一个对service方法的增强,添加日志
- 添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
对业务方法添加计算时间的增强
- 业务类
@Service
public class UserService {public void add(){System.out.println("增加");}public void delete(){System.out.println("删除");}public void update(){System.out.println("修改");}public void query(){System.out.println("查询");}
}
- 切面类
啥叫面向切面编程:面向切面编程就是面向切面类进行编程
@Aspect //标记为切面类
@Component
public class LogAspect {//实现方法用时 切点表达式@Around("execution(* com.sping.service.UserService.*(..))")public Object log(ProceedingJoinPoint joinPoint) throws Throwable {//记录方法用时long startTime = System.currentTimeMillis();//执行具体的方法try {joinPoint.proceed(); //连接点就是具体的方法}catch (Exception e){System.out.println("方法执行异常");throw new Exception();}long endTime = System.currentTimeMillis();System.out.println("方法用时:" + (endTime - startTime) + "ms");return null;}}
- 测试
@Autowiredprivate UserService userService;@Testpublic void test() throws InterruptedException {userService.add();}
@EnableAspectJAutoProxy
AOP的术语
切面、切点、通知、连接点;顾问(通知+切点)
通知
- 前置通知
- 后置通知
- 返回通知
- 异常通知
在定义切点的时候,可以把切点定义到另一个方法中,然后在通知中引入即可
@Aspect
@Component
public class LogAspect{@Pointcut("execution( * com.sping.service.UserService.*(..))")public void PointCut(){}@AfterReturning("PointCut()")public void log(){System.out.println("后置通知执行");}
}
环绕通知和其他通知的区别:环绕通知需要手动去拿到连接点执行目标方法,环绕方法更加的灵活
前置通知@Before
在目标方法执行之前执行
@Aspect
@Component
public class LogAspect{@Before("execution(* com.sping.service.*.*(..))")public void log(){System.out.println("前置通知执行");}
}
可以看到标注了前置通知后,前置通知的增强方法先执行,然后再执行目标方法
后置通知@After
与前置通知相反,后置通知是目标方法执行之后才会执行,通知里的增强方法
@Aspect
@Component
public class LogAspect{@After("execution(* com.sping.service.*.*(..))")public void log(){System.out.println("后置通知执行");}
}
返回通知@AfterReturning
执行位置:在方法执行完,返回值返回之前执行
@Pointcut("execution( * com.sping.service.UserService.*(..))")public void PointCut(){}/*** 返回通知* 1、可以获取返回值* 2、在后置通知之前执行* 3、方法执行返回值需要手动声明*/@AfterReturning(value = "PointCut()",returning = "returnValue")public void log(){System.out.println("返回通知执行,返回值:" + returnValue);}
异常通知@AfterThrowing
/*** 发生了异常后会执行*/@AfterThrowing(value = "PointCut()",throwing = "exception")public void log(Exception exception){System.out.println("异常通知执行" + exception.getMessage());}
总结-通知执行顺序
正常:前置通知---->目标方法---->返回通知---->后置通知(finallly)
异常:前置通知---->目标方法---->异常通知---->后置通知(finally)
切点表达式的提取-使用@Pointcut进行抽取
方便切点进行统一管理
@Pointcut("execution( * com.sping.service.UserService.*(..))")public void PointCut(){}
切点表达式的详细用法
- 切点标识符
- execution:用于匹配方法执行连接点。这是使用SpringAOP时,使用的主要切点标识符,可以匹配到方法级别,细粒度
- @annotation:限制匹配连接点,在Spring AOP中执行的具有给定的注解(只有含有某些注解,才会生效)
比如下面这段代码,只有我方法上加了@Log注解的才会被切到,进行增强
@Pointcut("@annotation(com.sping.annotation.Log)")public void PointCut(){}
execution和@annotation组合
格式:@Pointcut(“execution()&&”+“@annotation()”)
- 第一种方式:
@Pointcut("execution(* com.sping.service.*.*(..))&&"+"@annotation(com.sping.annotation.Log)")public void PointCut() {}@Before(value = "PointCut()")public void log() {System.out.println("开始记录日志");}
- 第二种方式:
设置两个切点方法,再引入的时候通过&&符号进行引入
@Pointcut("execution(* com.sping.service.*.*(..))")public void pointCutClass() {}@Pointcut("@annotation(com.sping.annotation.Log)")public void pointCutAnnotation(){}@Before(value = "pointCutClass()&&pointCutAnnotation()")public void log() {System.out.println("开始记录日志");}
SpringAOP和注解的使用-配置全局日志
- 自定义注解
package com.test.aspect;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {/*模块*/String title() default "";/*功能*/String action() default "";
}
- 切面类
/*** @ClassName LogAspect* @Description* @Author 周志强* @Date 2021/5/12 12:27* @Version 1.0*/
@Aspect
@Component("logAspect")
public class LogAspect {private static Logger log= LoggerFactory.getLogger(LogAspect.class);//配置切入点@Pointcut("@annotation(com.test.aspect.Log)")public void logPointCut(){}//后置通知 用于拦截操作,在方法返回后执行@AfterReturning(pointcut = "logPointCut()")public void doBefore(JoinPoint joinPoint){handelLog(joinPoint,null);}//拦截异常操作,有异常时执行@AfterThrowing(value = "logPointCut()",throwing = "e")public void doAfter(JoinPoint joinPoint,Exception e){handelLog(joinPoint,e);}public void handelLog(JoinPoint joinpoint,Exception e){try {Log annotationLog = getAnnotationLog(joinPoint);if (annotationLog == null) {return;}String className = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();String action = annotationLog.action();String title = annotationLog.title();//打印日志,如有需要还可以存入数据库logger.info(">>>>>>>>>>>>>模块名称:{}",title);logger.info(">>>>>>>>>>>>>操作名称:{}",action);logger.info(">>>>>>>>>>>>>类名:{}",className);logger.info(">>>>>>>>>>>>>方法名:{}",methodName);}catch (Exception exception){logger.error("=前置通知异常=");logger.error("异常信息:{}",e.getMessage());e.printStackTrace();}}private static Log getAnnotationLog(JoinPoint joinPoint) throws Exception {Signature signature = joinPoint.getSignature();MethodSignature methodSignature= (MethodSignature) signature;Method method = methodSignature.getMethod();if (method != null) {return method.getAnnotation(Log.class);}return null;}
}