AOP是一种与语言无关的程序思想、编程范式。项目业务逻辑中,将通用的模块以水平切割的方式进行分离统一处理,经常使用于日志、权限控制、异常处理等业务中。java
如下示例是基于Spring Boot实战系列(2)数据存储之Jpa操做MySQL chapter2-1可在Github获取源码git
项目根目录 pom.xml
添加依赖 spring-boot-starter-aop
github
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
复制代码
@Aspect
: 切面,由通知和切入点共同组成,这个注解标注在类上表示为一个切面。@Joinpoint
: 链接点,被AOP拦截的类或者方法,在前置通知中有介绍使用@Joinpoint
获取类名、方法、请求参数。Advice
: 通知的几种类型
@Before
: 前置通知,在某切入点@Pointcut
以前的通知@After
: 后置通知,在某切入点@Pointcut
以后的通知不管成功或者异常。@AfterReturning
: 返回后通知,方法执行return以后,能够对返回的数据作加工处理。@Around
: 环绕通知,在方法的调用前、后执行。@AfterThrowing
: 抛出异常通知,程序出错跑出异常会执行该通知方法。@Pointcut
: 切入点,从哪里开始。例如从某个包开始或者某个包下的某个类等。目录 aspect
下 新建 httpAspect.java
类,在收到请求以后先记录请求的相关参很多天志信息,请求成功完成以后打印响应信息,请求处理报错打印报错日志信息。web
httpAspect.java
spring
package com.angelo.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;
@Aspect
@Component
public class HttpAspect {
// 打印日志模块
private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
// 下面会一一介绍...
复制代码
定义切入的入口在哪里,封装一个公共的方法实现复用编程
httpAspect.java
json
/** * 定义一个公共的方法,实现服用 * 拦截UserController下面的全部方法 * 拦截UserController下面的userList方法里的任何参数(..表示拦截任何参数)写法:@Before("execution(public * com.angelo.controller.UserController.userList(..))") */
@Pointcut("execution(public * com.angelo.controller.UserController.*(..))")
public void log() {
}
复制代码
拦截方法以前的一段业务逻辑,获取请求的一些信息,其中用到了Gson
处理对象转json输出app
httpAspect.java
curl
@Before("log()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Map params = new HashMap();
params.put("url", request.getRequestURL()); // 获取请求的url
params.put("method", request.getMethod()); // 获取请求的方式
params.put("ip", request.getRemoteAddr()); // 获取请求的ip地址
params.put("className", joinPoint.getSignature().getDeclaringTypeName()); // 获取类名
params.put("classMethod", joinPoint.getSignature().getName()); // 获取类方法
params.put("args", joinPoint.getArgs()); // 请求参数
// 输出格式化后的json字符串
Gson gson = new GsonBuilder().setPrettyPrinting().create();
logger.info("REQUEST: {}", gson.toJson(params));
}
复制代码
拦截方法以后的一段业务逻辑函数
httpAspect.java
@After("log()")
public void doAfter() {
logger.info("doAfter");
}
复制代码
环绕通知是在方法的先后的一段逻辑操做,能够修改目标方法的返回值,第一个参数是org.aspectj.lang.ProceedingJoinPoint
类型,注意这里要调用执行目标方法proceed()
获取值返回,否则会形成空指针异常。在环绕通知里面也能够捕获错误返回。
httpAspect.java
@Around("log()")
public Object doAround(ProceedingJoinPoint point) {
try {
Object o = point.proceed();
System.out.println("方法环绕proceed,结果是 :" + o);
logger.info("doAround1");
return o;
} catch (Throwable e) {
// e.printStackTrace();
logger.info("doAround2");
return null;
}
}
复制代码
在切入点完成以后的返回通知,此时就不会抛出异常通知,除非返回后通知的业务逻辑报错。
httpAspect.java
/** * 获取响应返回值 * @param object */
@AfterReturning(returning = "object", pointcut = "log()")
public void doAfterReturning(Object object) {
// logger.info("RESPONSE: {}", object); 会打印出一个对象,想打印出具体内容须要在定义模型处加上toString()
logger.info("RESPONSE: {}", object.toString());
}
复制代码
抛出异常后的通知,此时返回后通知@AfterReturning
就不会执行。
httpAspect.java
@AfterThrowing(pointcut = "log()")
public void doAfterThrowing() {
logger.error("doAfterThrowing: {}", " 异常状况!");
}
复制代码
try {
// @Before 执行前通知
// 执行目标方法
// @Around 执行环绕通知 成功走finall,失败走catch
} finally {
// @After 执行后置通知
// @AfterReturning 执行返回后通知
} catch(e) {
// @AfterThrowing 抛出异常通知
}
复制代码
测试以前先对controller/UserController.java
文件的userList
方法增长了exception
参数
/** * 查询用户列表 * @return */
@RequestMapping(value = "/user/list/{exception}")
public List<User> userList(@PathVariable("exception") Boolean exception) {
if (exception) {
throw new Error("测试抛出异常!");
}
return userRepository.findAll();
}
复制代码
curl 127.0.0.1:8080/user/list/false
正常状况返回值以下所示:
curl 127.0.0.1:8080/user/list/true
异常状况返回值以下所示:
经过以上两种状况测试能够看到环绕通知在正常、异常两种状况均可以执行到。
做者:五月君
连接:www.imooc.com/article/259…
来源:慕课网
Github: Spring Boot实战系列