AOP面向切面编程

    • AOP(Aspect Oriented Programming)面向切面编程
    • OOP(Object Oriented Programming)面向对象编程
    • POP(procedure oriented programming)面向过程编程
    • FP(Functional Programming)面向函数编程

    以下示例是基于Spring Boot实战系列(2)数据存储之Jpa操作MySQL /chapter2/chapter2-1可在Github获取源码

    项目根目录 pom.xml 添加依赖 spring-boot-starter-aop

    • @Aspect: 切面,由通知和切入点共同组成,这个注解标注在类上表示为一个切面。
    • @Joinpoint: 连接点,被AOP拦截的类或者方法,在前置通知中有介绍使用@Joinpoint获取类名、方法、请求参数。
    • Advice: 通知的几种类型
      • @Before: 前置通知,在某切入点@Pointcut之前的通知
      • @After: 后置通知,在某切入点@Pointcut之后的通知无论成功或者异常。
      • @AfterReturning: 返回后通知,方法执行return之后,可以对返回的数据做加工处理。
      • @Around: 环绕通知,在方法的调用前、后执行。
      • @AfterThrowing: 抛出异常通知,程序出错跑出异常会执行该通知方法。

    目录 aspect下 新建 httpAspect.java类,在收到请求之后先记录请求的相关参数日志信息,请求成功完成之后打印响应信息,请求处理报错打印报错日志信息。

    1. ```java
    2. package com.angelo.aspect;
    3. import com.google.gson.Gson;
    4. import com.google.gson.GsonBuilder;
    5. import org.aspectj.lang.JoinPoint;
    6. import org.aspectj.lang.ProceedingJoinPoint;
    7. import org.aspectj.lang.annotation.*;
    8. import org.slf4j.Logger;
    9. import org.slf4j.LoggerFactory;
    10. import org.springframework.stereotype.Component;
    11. import org.springframework.web.context.request.RequestContextHolder;
    12. import javax.servlet.http.HttpServletRequest;
    13. import java.util.HashMap;
    14. import java.util.Map;
    15. @Aspect
    16. @Component
    17. public class HttpAspect {
    18. // 打印日志模块
    19. private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
    20. // 下面会一一介绍...

    添加切入点

    定义切入的入口在哪里,封装一个公共的方法实现复用

    1. ```java
    2. /**
    3. * 定义一个公共的方法,实现服用
    4. * 拦截UserController下面的所有方法
    5. * 拦截UserController下面的userList方法里的任何参数(..表示拦截任何参数)写法:@Before("execution(public * com.angelo.controller.UserController.userList(..))")
    6. */
    7. @Pointcut("execution(public * com.angelo.controller.UserController.*(..))")
    8. public void log() {
    9. }

    前置通知

    拦截方法之前的一段业务逻辑,获取请求的一些信息,其中用到了Gson处理对象转json输出

    后置通知

    1. ```java
    2. @After("log()")
    3. logger.info("doAfter");
    4. }

    环绕通知

    环绕通知是在方法的前后的一段逻辑操作,可以修改目标方法的返回值,第一个参数是org.aspectj.lang.ProceedingJoinPoint类型,注意这里要调用执行目标方法proceed()获取值返回,不然会造成空指针异常。在环绕通知里面也可以捕获错误返回。

    1. ```java
    2. @Around("log()")
    3. public Object doAround(ProceedingJoinPoint point) {
    4. try {
    5. System.out.println("方法环绕proceed,结果是 :" + o);
    6. logger.info("doAround1");
    7. return o;
    8. } catch (Throwable e) {
    9. // e.printStackTrace();
    10. logger.info("doAround2");
    11. return null;
    12. }
    13. }

    返回后通知

    在切入点完成之后的返回通知,此时就不会抛出异常通知,除非返回后通知的业务逻辑报错。

    异常通知

    抛出异常后的通知,此时返回后通知@AfterReturning就不会执行。

    1. ```java
    2. @AfterThrowing(pointcut = "log()")
    3. public void doAfterThrowing() {
    4. logger.error("doAfterThrowing: {}", " 异常情况!");
    5. }

    一段段伪代码读懂执行顺序

    1. try {
    2. // @Before 执行前通知
    3. // 执行目标方法
    4. // @Around 执行环绕通知 成功走finall,失败走catch
    5. } finally {
    6. // @After 执行后置通知
    7. // @AfterReturning 执行返回后通知
    8. } catch(e) {
    9. // @AfterThrowing 抛出异常通知
    10. }

    测试正常异常两种情况

    测试之前先对controller/UserController.java文件的userList方法增加了exception参数

    • 测试异常情况

    curl 127.0.0.1:8080/user/list/true

    异常情况返回值如下所示:

    AOP面向切面编程 - 图1

    通过以上两种情况测试可以看到环绕通知在正常、异常两种情况都可以执行到。