转载

Spring AOP初体验

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的设计思想

Why

AOP有很多优势,运用到实际开发中会使得代码更加简介健壮。

让你不必为try catch finally重复冗余代码而苦恼。

1. 切面分离

将功能性需求和非功能性需求分离,将业务重心集中到业务上 好处:

  1. 集中处理业务
  2. 增强代码维护性,减少非业务代码的侵入性

2. 应用场景

  1. 权限控制
  2. 缓存控制
  3. 审核日志
  4. 性能监控
  5. 异常处理
  6. 分布式追踪

我这里用的比较多的是日志与异常

良好的代码风格,对bug的排查异常的处理都有很大好处。

debuger的速度是跟不上日志的速度的

How

1. SpringBoot配置

引入spring-boot-starter-aop依赖

注意版本

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
复制代码

2. SSM配置

  1. 引入以下依赖

需要查看依赖版本,传递性依赖可能会导致依赖版本不对,需要详细查看

<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>

复制代码
  1. XML配置 添加以下XML
<aop:aspectj-autoproxy />
复制代码

3. AOP术语

1. 切面Aspect

切面可以定义各类的 "通知","切点"和"引入内容"。

2. 通知(Advice)

切面开启后, 切面中的方法

大致分为

  1. Before 在原有方法 执行

  2. After 在原有方法 执行

  3. afterReturning 在原有方法 正常返回后 执行

  4. afterThrowing 在原有方法 产生异常后 执行

  5. around 可以取代原有方法, 可以 回调 原有方法(手动控制原有方法执行) 所以可以在原有方法 执行前后 分别写自定义处理函数

3. 引入(Introduction)

引入允许我们在现有类理添加自定义的类和方法

4. 切点(PointCut)

就是切入点,"切入点" 会告诉Spring AOP 什么时候启动拦截 有以下方式

  1. 自定义注解 自己
  2. 正则表达式

5. 连接点(Join Point)

连接点对应的是具体需要拦截的东西 需要拦截的方法,

6. 织入

将一个生成代理对象并将切面内容放入都流程中的过程 与动态代理设计模式有关。

使用@AspectJ注解开发AOP

1. 选择连接点(Join Point)

确定哪个方法需要使用 AOP 这里纯属自己定义, 选择一个测试接口也行

@GetMapping("/test")
    public String test()  {
        return "hello";
    }
复制代码

2. 创建切面(Aspect)

使用@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;
    }

}
复制代码

3. 定义切点(Point Cut)

标识拦截什么方法 一共两种方式,任选其一

  1. 使用自定义注解

    1. 注解类
    @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface GetExecutionTime {
        }
    复制代码
    1. 使用注解类 在要使用AOP的方法上添加此注解

      @GetExecutionTime
      @GetMapping("/test")
      public String test()  {
      return "hello";
      }
      复制代码
    复制代码
  2. 使用execution正则表达式

    execution( * com.ybj.crawler.controller.BusController.*(..) )
    复制代码

分析:

1. execution:代表执行方法的时候会触发
2. * 标识任意字符(第一个标识返回类型, 第二标识所有方法)
3. (..)标识方法的任意参数
复制代码

3. 通知传入参数

在Advice函数中, 有时候需要对参数进行调用, 输入到日志中或者参与计算, 所以需要调用参数

  1. 自定义注解

这里获得参数需要使用到数组,根据下标获得

@Around(value = "getExecutionTime()")
    public Object test(ProceedingJoinPoint joinPoint) throws Throwable {
        String logType = (String) joinPoint.getArgs()[4];
        }
复制代码
  1. 正则表达式

不适用于环绕通知

@After("execution( * com.ybj.crawler.controller.BusController.getBusInfo(..))" + "&& args(param1)" )
    public void getExecutionTimeAndParamsByExecution(String param1) throws Throwable {
        System.out.println("参数为 = " + param1);
    }
复制代码
原文  https://juejin.im/post/5ef31ae5e51d453490663605
正文到此结束
Loading...