我们常知spring一共有两大核心技术:Aop, IOC。
Aop面向切面,IOC控制反转,在平常开发项目我们也经常用的到
最近楼主在开发一个日志模块的时候需要拿到每个方法的请求返回结果。
使用spring boot项目对请求方法的结果进行拦截。在每个方法执行完之后使用Aop的面向切面进行拦截,获取返回结果。
功能描述:
@Before在每个方法执行之前可以对入参的参数进行修改或者将参数进行替换。
@AfterReturning在方法执行完毕后也可以对参数进行拦截并进行修改。
@Around环绕通知,环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,
我写了一个例子使用postman亲测有效:
先准备一个实体类,也是需要返回的实体内容,代码如下:
@Data
public class User {
private String name;
private int age;
public User(){}
public User(String name, int age){
this.name = name;
this.age = age;
}
}
在写一个入口controller,给个提示表示进入了该方法,代码如下:
@Slf4j
@RestController
public class HelloController implements HandlerInterceptor {
@RequestMapping("/hello")
public User Helloshow(){
User user = new User("小龙",23);
log.info("先进方法");
return user;
}
}
注入Aop的依赖,代码如下:
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
接着定义一个Aspect,并指定一个切入点,配置需要拦截的方法
我这里对整个controller进行拦截
@Slf4j
@Aspect
@Configuration
public class ServiceAspect {
private final String ExpGetResultDataPonit = "execution(* com.haylion.demo.controller..*.*(..))";
/**
* 后置返回通知
* 这里需要注意的是:
* 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
*/
@AfterReturning(value = ExpGetResultDataPonit, returning = "keys")
public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
log.info("进入方法获取返回结果为:" + keys);
if (keys instanceof User) {
User resultVO = (User) keys;
resultVO.setName("小龙人");
}
log.info("可在方法内修改返回结果:"+ keys);
}
}
然后执行方法就可以看到下面的结果了,在请求完方法后可以获取到返回的值也可以对返回值进行修改,修改之后在进行返回:
如果想在方法执行之前拦截方法可以加入如下代码:
//执行方法前的拦截方法
@Before(ExpGetResultDataPonit)
public void doBeforeMethod(JoinPoint joinPoint) {
log.info("我是前置通知,我将要执行一个方法了");
//获取目标方法的参数信息
Object[] obj = joinPoint.getArgs();
for (Object argItem : obj) {
log.info("参数修改前:" + argItem);
if (argItem instanceof User) {
User user = (User) argItem;
user.setName("xiaolong英文版");
}
log.info("参数修改后:" + argItem);
}
}
在修改一下controller入口进行参数传入,代码如下:
@PostMapping("/hello")
public User Helloshow(@RequestBody User user){
// User user = new User("小龙",23);
log.info("先进方法");
return user;
}
我这里是使用postman进行请求的,编写简单的参数进行传值:
还有一个环绕方法(推荐使用),是可以结合前置和后置在一起的方法,代码如下:
/**
* 环绕通知:
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,
* 执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(ExpGetResultDataPonit)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
processInputArg(proceedingJoinPoint.getArgs());
try {//obj之前可以写目标方法执行前的逻辑
Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
processOutPutObj(obj);
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
/**
* 处理返回对象
*/
private void processOutPutObj(Object obj) {
log.info("原OBJ为:" + obj.toString());
if (obj instanceof User) {
User resultVO = (User) obj;
resultVO.setName("小龙人");
log.info("修改后:"+ resultVO);
}
}
/**
* 处理输入参数
*
* @param args 入参列表
*/
private void processInputArg(Object[] args) {
for (Object arg : args) {
System.out.println("ARG原来为:" + arg);
if (arg instanceof User) {
User paramVO = (User) arg;
paramVO.setName("xiaolong英文版");
}
}
}
切面完整代码:
@Slf4j
@Aspect
@Configuration
public class ServiceAspect {
private final String ExpGetResultDataPonit = "execution(* com.haylion.demo.controller..*.*(..))";
//定义切入点,拦截servie包其子包下的所有类的所有方法
//@Pointcut("execution(* execution(* com.haylion.demo.controller..*.*(..))")
//拦截指定的方法,这里指只拦截 Helloshow() 方法
//@Pointcut("execution(* execution(* com.haylion.demo.controller.Helloshow..*.*(..))")
//执行方法前的拦截方法
// @Before(ExpGetResultDataPonit)
// public void doBeforeMethod(JoinPoint joinPoint) {
// log.info("我是前置通知,我将要执行一个方法了");
// //获取目标方法的参数信息
// Object[] obj = joinPoint.getArgs();
// for (Object argItem : obj) {
// log.info("参数修改前:" + argItem);
// if (argItem instanceof User) {
// User user = (User) argItem;
// user.setName("xiaolong英文版");
// }
// log.info("参数修改后:" + argItem);
// }
// }
/**
* 后置返回通知
* 这里需要注意的是:
* 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
*/
// @AfterReturning(value = ExpGetResultDataPonit, returning = "keys")
// public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
// log.info("进入方法获取返回结果为:" + keys);
// if (keys instanceof User) {
// User resultVO = (User) keys;
// resultVO.setName("小龙人");
// }
// log.info("可在方法内修改返回结果:"+ keys);
// }
/**
* 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
*/
// @After("excuteService()")
// public void doAfterAdvice(JoinPoint joinPoint) {
// System.out.println("后置通知执行了!!!!");
// }
/**
* 环绕通知:
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,
* 执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(ExpGetResultDataPonit)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
log.info("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
processInputArg(proceedingJoinPoint.getArgs());
try {//obj之前可以写目标方法执行前的逻辑
Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
processOutPutObj(obj);
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
/**
* 处理返回对象
*/
private void processOutPutObj(Object obj) {
log.info("原OBJ为:" + obj.toString());
if (obj instanceof User) {
User resultVO = (User) obj;
resultVO.setName("小龙人");
log.info("修改后:"+ resultVO);
}
}
/**
* 处理输入参数
*
* @param args 入参列表
*/
private void processInputArg(Object[] args) {
for (Object arg : args) {
log.info("ARG原来为:" + arg);
if (arg instanceof User) {
User paramVO = (User) arg;
paramVO.setName("xiaolong英文版");
}
}
}
}
主要参考:https://blog.csdn.net/puhaiyang/article/details/78146620