为了账号安全,请及时绑定邮箱和手机立即绑定

Spring Boot之AOP面向切面编程-实战篇

标签:
SpringBoot

前言

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

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

5c1e578000012ecb15280928.jpg

5c1e578100017d4824280794.jpg

  • 测试异常情况

curl 127.0.0.1:8080/user/list/true

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

5c1e578200014dea23640806.jpg

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

完整示例代码

-- 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@Componentpublic 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
 */@Entitypublic 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);

}


作者:杨英华

出处:http://www.cnblogs.com/yangyinghua

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消