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@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
正常情况返回值如下所示:
测试异常情况
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@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
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
共同学习,写下你的评论
评论加载中...
作者其他优质文章