Spring Boot之AOP面向切面編程-實戰篇


前言

AOP是一種與語言無關的程序思想、編程范式。項目業務邏輯中,將通用的模塊以水平切割的方式進行分離統一處理,常用於日志、權限控制、異常處理等業務中。

編程范式主要有以下幾類

  • AOP(Aspect Oriented Programming)面向切面編程
  • OOP(Object Oriented Programming)面向對象編程
  • POP(procedure oriented programming)面向過程編程
  • FP(Functional Programming)面向函數編程

引入pom依賴

項目根目錄 pom.xml 添加依賴 spring-boot-starter-aop

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

aop注解

  • @Aspect: 切面,由通知和切入點共同組成,這個注解標注在類上表示為一個切面。
  • @Joinpoint: 連接點,被AOP攔截的類或者方法,在前置通知中有介紹使用@Joinpoint獲取類名、方法、請求參數。
  • Advice: 通知的幾種類型
  • @Before: 前置通知,在某切入點@Pointcut之前的通知
  • @After: 后置通知,在某切入點@Pointcut之后的通知無論成功或者異常。
  • @AfterReturning: 返回后通知,方法執行return之后,可以對返回的數據做加工處理。
  • @Around: 環繞通知,在方法的調用前、后執行。
  • @AfterThrowing: 拋出異常通知,程序出錯跑出異常會執行該通知方法。
  • @Pointcut: 切入點,從哪里開始。例如從某個包開始或者某個包下的某個類等。

實現日志分割功能

目錄 aspect下 新建 HttpAspect.java類,在收到請求之后先記錄請求的相關參數日志信息,請求成功完成之后打印響應信息,請求處理報錯打印報錯日志信息。

HttpAspect.java

package com.itaofly.aspect.aspect;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @desctiption:
 * @author: yinghuaYang
 * @date: 2018/12/22
 */

@Aspect
@Component
public class HttpAspect {
    // 打印日志模塊
    private final Logger logger = LoggerFactory.getLogger(HttpAspect.class);
    // 下面慢慢介紹... 

添加切入點

定義切入的入口在哪里,封裝一個公共的方法實現復用

HttpAspect.java

/**
* 定義一個公共的方法
* 攔截UserController下面的所有方法
* 攔截UserController下面的userList方法里的任何參數(..表示攔截任何參數)
* 寫法: @Pointcut("execution(public * com.itaofly.aspect.controller.UserController.*(..))")
*/
@Pointcut("execution(public * com.itaofly.aspect.controller.UserController.*(..))")
public void verify() {}

前置通知

攔截方法之前的一段業務邏輯,獲取請求的一些信息,其中用到了Gson處理對象轉json輸出

HttpAspect.java

@Before("verify()")
public void doBefore(JoinPoint joinPoint) {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();

    Map<String,Object> map = new HashMap<>();
    // 獲取請求的url
    map.put("url",request.getRequestURL());
    // 獲取請求的方式
    map.put("method",request.getMethod());
    // 獲取請求的ip地址
    map.put("ip",request.getRemoteAddr());
    // 獲取類名
    map.put("className",joinPoint.getSignature().getDeclaringTypeName());
    // 獲取類方法
    map.put("classMethod",joinPoint.getSignature().getName());
    // 請求參數
    map.put("args",joinPoint.getArgs());

    // 輸出格式化后的json字符串
    Gson gson = new GsonBuilder().setPrettyPrinting().create();

    logger.info("request: {}",gson.toJson(map));

}

后置通知

攔截方法之后的一段業務邏輯

HttpAspect.java

@After("verify()")
public void doAfter() {
	logger.info("doAfter!");
}

環繞通知

環繞通知是在方法的前后的一段邏輯操作,可以修改目標方法的返回值,第一個參數是org.aspectj.lang.ProceedingJoinPoint類型,注意這里要調用執行目標方法proceed()獲取值返回,不然會造成空指針異常。在環繞通知里面也可以捕獲錯誤返回。

HttpAspect.java

@Around("verify()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) {
    try {
        Object obj = proceedingJoinPoint.proceed();
        System.out.println("方法環繞..., 結果是 : {}" + obj);
        logger.info("doAround...1");

        return obj;
    }catch (Throwable e) {
        logger.info("Throwable ...");

        return null;
    }
}

返回后通知

在切入點完成之后的返回通知,此時就不會拋出異常通知,除非返回后通知的業務邏輯報錯。

HttpAspect.java

/**
* 獲取響應返回值
* @param obj
*/
@AfterReturning(returning = "obj",pointcut = "verify()")
public void doAfterReturning(Object obj) {
    // 會打印出一個對象,想打印出具體內容需要在定義模型處加上toString()
    //logger.info("response: {}",obj);

    logger.info("response: {}",obj.toString());
}

異常通知

拋出異常后的通知,此時返回后通知@AfterReturning就不會執行。

HttpAspect.java

@AfterThrowing(pointcut = "verify()")
    public void doAfterThrowing() {
    logger.error("doAfterThrowing: {}"," 異常情況!!!!");
}

一段段偽代碼讀懂執行順序

try {
    // @Before 執行前通知

    // 執行目標方法

    // @Around 執行環繞通知 成功走finall,失敗走catch
} finally {
    // @After 執行后置通知

    // @AfterReturning 執行返回后通知
} catch(e) {
    // @AfterThrowing 拋出異常通知
}

測試正常異常兩種情況

測試之前先對controller/UserController.java文件的getUserList 方法增加了exception`參數

/**
* 根據所有用戶
* @param exception
* @return
*/
@RequestMapping(value = "/list/{exception}")
public List<User> getUserList(@PathVariable("exception") Boolean exception) {
    if (exception) {
    throw new Error("throw error!");
	}

	return repository.findAll();
}
  • 測試正常情況
curl 127.0.0.1:8080/user/list/false

正常情況返回值如下所示:

  • 測試異常情況
curl 127.0.0.1:8080/user/list/true

異常情況返回值如下所示:

通過以上兩種情況測試可以看到環繞通知在正常、異常兩種情況都可以執行到

完整示例代碼

-- HttpAspect.java --
package com.itaofly.aspect.aspect;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @desctiption:
 * @author: yinghuaYang
 * @date: 2018/12/22
 */

@Aspect
@Component
public class HttpAspect {
    // 打印日志模塊
    private final Logger logger = LoggerFactory.getLogger(HttpAspect.class);


    /**
     * 定義一個公共的方法
     * 攔截UserController下面的所有方法
     * 攔截UserController下面的userList方法里的任何參數(..表示攔截任何參數)
     * 寫法: @Pointcut("execution(public * com.itaofly.aspect.controller.UserController.*(..))")
     */
    @Pointcut("execution(public * com.itaofly.aspect.controller.UserController.*(..))")
    public void verify() {}


    @Before("verify()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        Map<String,Object> map = new HashMap<>();
        // 獲取請求的url
        map.put("url",request.getRequestURL());
        // 獲取請求的方式
        map.put("method",request.getMethod());
        // 獲取請求的ip地址
        map.put("ip",request.getRemoteAddr());
        // 獲取類名
        map.put("className",joinPoint.getSignature().getDeclaringTypeName());
        // 獲取類方法
        map.put("classMethod",joinPoint.getSignature().getName());
        // 請求參數
        map.put("args",joinPoint.getArgs());

        // 輸出格式化后的json字符串
        Gson gson = new GsonBuilder().setPrettyPrinting().create();

        logger.info("request: {}",gson.toJson(map));

    }


    @Around("verify()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) {
        try {
            Object obj = proceedingJoinPoint.proceed();
            System.out.println("方法環繞..., 結果是 : {}" + obj);
            logger.info("doAround...1");

            return obj;
        }catch (Throwable e) {
            logger.info("Throwable ...");

            return null;
        }
    }

    @After("verify()")
    public void doAfter() {
        logger.info("doAfter!");
    }

    /**
     * 獲取響應返回值
     * @param obj
     */
    @AfterReturning(returning = "obj",pointcut = "verify()")
    public void doAfterReturning(Object obj) {
        // 會打印出一個對象,想打印出具體內容需要在定義模型處加上toString()
        //logger.info("response: {}",obj);

        logger.info("response: {}",obj.toString());
    }

    @AfterThrowing(pointcut = "verify()")
    public void doAfterThrowing() {
        logger.error("doAfterThrowing: {}"," 異常情況!!!!");
    }

}
-- UserController.java --
package com.itaofly.aspect.controller;

import com.itaofly.aspect.model.User;
import com.itaofly.aspect.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

/**
 * @desctiption:
 * @author: yinghuaYang
 * @date: 2018/12/22
 */

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserRepository repository;

    /**
     * 新增用戶
     * @param params
     * @return
     */
    @PostMapping(value = "/add")
    public User addUesr(@RequestBody User params) {
        User user = new User();
        user.setUserName(params.getUserName());
        user.setUserAge(params.getUserAge());

        return repository.save(user);
    }

    /**
     * 根據所有用戶
     * @param exception
     * @return
     */
    @GetMapping(value = "/list/{exception}")
    public List<User> getUserList(@PathVariable("exception") Boolean exception) {
        if (exception) {
            throw new Error("throw error!");
        }

        return repository.findAll();
    }

    /**
     * 根據Id查詢用戶
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Optional<User> getUserById(@PathVariable("id") Integer id) {
        return repository.findById(id);
    }

    /**
     * 根據userName獲取用戶信息
     * @param userName
     * @return
     */
    @GetMapping("/userName")
    public List<User> getUserListByName(@RequestParam(name = "userName",defaultValue = "") String userName) {
        return repository.findByUserName(userName);
    }

    /**
     * 更新用戶信息
     * @param id
     * @param userName
     * @param userAge
     * @return
     */
    @PutMapping("/{id}")
    public User updateUser(@PathVariable("id") Integer id,
                           @PathVariable("userName") String userName,
                           @PathVariable("userAge") Integer userAge){
        User user = new User();
        user.setId(id);
        user.setUserAge(userAge);
        user.setUserName(userName);

        return repository.save(user);

    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable("id") Integer id) {
        repository.deleteById(id);
    }

}
-- User.java --
package com.itaofly.aspect.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 * @desctiption:
 * @author: yinghuaYang
 * @date: 2018/12/22
 */

@Entity
public class User {
    @Id
    @GeneratedValue
    private Integer id;

    private String userName;

    private Integer userAge;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getUserAge() {
        return userAge;
    }

    public void setUserAge(Integer userAge) {
        this.userAge = userAge;
    }


    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", userAge=" + userAge +
                '}';
    }
}
-- UserReponsitory.java --
package com.itaofly.aspect.repository;

import com.itaofly.aspect.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

/**
 * @desctiption:
 * @author: yinghuaYang
 * @date: 2018/12/22
 */

public interface UserRepository extends JpaRepository<User,Integer> {

    /**
     * 通過用戶名查詢
     * @param userName
     * @return
     */
    List<User> findByUserName(String userName);

}

我的新博客地址鏈接:https://www.itaofly.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM