Java的基础思想是OOP,面向对象编程。 但是有一些场景不是OOP能解决的, 并且这些场景又是十分重要的,比如转账,下单和库存减少
代码如下
public void test(){ try { StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student = new Student(); student.setName("yy"); student.setEmail("email@email.com"); student.setDob(new Date()); student.setPhone(new PhoneNumber("123-2568-8947")); studentMapper.insertStudent(student); sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); } finally { sqlSession.close(); } } 复制代码
可见存在一整套 try catch finally ,try 的滥用使得代码的可读性很差,尤其是在业务功能复杂的场景,try通常是祸不单行,甚至嵌套出现
这里可以使用事务注解代替
@Transactional public void test(){ StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student = new Student(); student.setName("yy"); student.setEmail("email@email.com"); student.setDob(new Date()); student.setPhone(new PhoneNumber("123-2568-8947")); studentMapper.insertStudent(student); } 复制代码
添加了@Transactional 注解
但是没有打开或者关闭数据库资源的代码,也没有提交或者回滚事务的代码,
但是功能还是没有变,这段代码显然更加简洁,且业务重心更加突出。 这里就蕴含了AOP的设计思想
AOP有很多优势,运用到实际开发中会使得代码更加简介健壮。
让你不必为try catch finally重复冗余代码而苦恼。
将功能性需求和非功能性需求分离,将业务重心集中到业务上 好处:
我这里用的比较多的是日志与异常
良好的代码风格,对bug的排查异常的处理都有很大好处。
debuger的速度是跟不上日志的速度的
引入spring-boot-starter-aop依赖
注意版本
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 复制代码
需要查看依赖版本,传递性依赖可能会导致依赖版本不对,需要详细查看
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.0.3.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.11</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.11</version> </dependency> 复制代码
<aop:aspectj-autoproxy /> 复制代码
切面可以定义各类的 "通知","切点"和"引入内容"。
切面开启后, 切面中的方法
大致分为
Before 在原有方法 前 执行
After 在原有方法 后 执行
afterReturning 在原有方法 正常返回后 执行
afterThrowing 在原有方法 产生异常后 执行
around 可以取代原有方法, 可以 回调 原有方法(手动控制原有方法执行) 所以可以在原有方法 执行前后 分别写自定义处理函数
引入允许我们在现有类理添加自定义的类和方法
就是切入点,"切入点" 会告诉Spring AOP 什么时候启动拦截 有以下方式
连接点对应的是具体需要拦截的东西 需要拦截的方法,
将一个生成代理对象并将切面内容放入都流程中的过程 与动态代理设计模式有关。
确定哪个方法需要使用 AOP 这里纯属自己定义, 选择一个测试接口也行
@GetMapping("/test") public String test() { return "hello"; } 复制代码
使用@Aspect表示一个类 里面用于写上"通知","切点"等
@Slf4j @Aspect @Component public class LogAspect { @Pointcut("@annotation(com.ybj.crawler.annotation.GetExecutionTime)") public void getExecutionTime() { } @Around(value = "getExecutionTime()") public Object test(ProceedingJoinPoint joinPoint) throws Throwable { LocalDateTime start = LocalDateTime.now(); log.info("开始时间 {}", start); Object proceed = joinPoint.proceed(); LocalDateTime end = LocalDateTime.now(); Duration duration = Duration.between(start, end); log.info("结束时间 {}, 执行时间为 {}S", end, duration.getSeconds()); return proceed; } @Pointcut("execution( * com.ybj.crawler.controller.BusController.*(..) )") public void getExecutionTimeByExecution() { } @Around(value = "getExecutionTimeByExecution()") public Object getExecutionTimeByExecution(ProceedingJoinPoint joinPoint) throws Throwable { LocalDateTime start = LocalDateTime.now(); log.info("开始时间 {}", start); Object proceed = joinPoint.proceed(); LocalDateTime end = LocalDateTime.now(); Duration duration = Duration.between(start, end); log.info("结束时间 {}, 执行时间为 {}S", end, duration.getSeconds()); return proceed; } } 复制代码
标识拦截什么方法 一共两种方式,任选其一
使用自定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface GetExecutionTime { } 复制代码
使用注解类 在要使用AOP的方法上添加此注解
@GetExecutionTime @GetMapping("/test") public String test() { return "hello"; } 复制代码
复制代码
使用execution正则表达式
execution( * com.ybj.crawler.controller.BusController.*(..) ) 复制代码
分析:
1. execution:代表执行方法的时候会触发 2. * 标识任意字符(第一个标识返回类型, 第二标识所有方法) 3. (..)标识方法的任意参数 复制代码
在Advice函数中, 有时候需要对参数进行调用, 输入到日志中或者参与计算, 所以需要调用参数
这里获得参数需要使用到数组,根据下标获得
@Around(value = "getExecutionTime()") public Object test(ProceedingJoinPoint joinPoint) throws Throwable { String logType = (String) joinPoint.getArgs()[4]; } 复制代码
@After("execution( * com.ybj.crawler.controller.BusController.getBusInfo(..))" + "&& args(param1)" ) public void getExecutionTimeAndParamsByExecution(String param1) throws Throwable { System.out.println("参数为 = " + param1); } 复制代码