aop对请求后端的参数修改_Spring Boot AOP之对请求的参数入参与返回结果进行拦截处理...

对于spring框架来说,最重要的两大特性就是AOP 和IOC。

以前一直都知道有这两个东西,在平时做的项目中也常常会涉及到这两块,像spring的事务管理什么的,在看了些源码后,才知道原来事务管理也是用的AOP来实现的。对于IOC的话,平时接触的就更多了,什么autowired,resource各种注解,就是IOC的各种应用。

一直我也想着能有机会自己动手写个aop的小DEMO,不过一直没机会,想到了许多,在网上一搜,基本上都已经有了。今天想到一个用于对service方法进行拦截的功能点,今天决定用springBoot的工程来实现一下。

功能点描述:对某个service的方法执行前,获取出入参,对入参的参数进行修改,将参数进行替换。然后在这个方法执行完毕后,再对其返回结果进行修改。主要就是对一个方法装饰一下。说到装饰,第一想到的是采用装饰器模式来实现,但装饰器模式需要对整个代码的结构进行一些修改,为了达到对以前的代码不进行任何接触,且装饰器模式的局限性较小,所以最好还是用spring的AOP来实现这种对代码无任何侵入的功能。

service的代码如下:

@Servicepublic classTestServiceImpl implements TestService {private Logger logger = LoggerFactory.getLogger(this.getClass());

@OverridepublicResultVO getResultData(ParamVO paramVO) {returnprocess(paramVO);

}privateResultVO process(ParamVO paramVO) {

logger.info("----->input INFO:{}", paramVO);

ResultVO resultVO= newResultVO();

resultVO.setCode(200);

resultVO.setData(Arrays.asList("123", "456", "789"));

resultVO.setMessage("OK!!!!!!!! and your inputParam is" +paramVO.toString());

logger.info("---->return INFO:{}", resultVO.toString());returnresultVO;

}

其中入参为paramVO,代码如下:

public classParamVO {privateString inputParam;privateString inputParam2;//getter and setter

}

返回的参数ResutVO,代码如下:

public classResultVO {privateInteger code;privateString message;privateObject data;//getter and setter

}

其调用的入口为一个controller,代码如下:

@RequestMapping(value = "test")

@RestControllerpublic classTestController {

@ResourceprivateTestService testService;

@GetMapping(value= "getResult")publicResultVO getResult(ParamVO paramVO) {

ResultVO resultData=testService.getResultData(paramVO);returnresultData;

}

在正常情况下,按照如上的代码进行调用将返回如下的信息:

通过返回的信息可以看到,入参是我们在请求参数传入的inputParam=111和inputParam2=2220

现在要做的就是把入参的参数通过AOP来拦截,并进行修改。对于返回值,也进行一下修改。

首先让工程引入AOP的包:

org.springframework.boot

spring-boot-starter-aop

然后定义一个Aspect,并指定一个切入点,配置要进行哪些方法的拦截

这里只针对TestSevice这个接口下的getResultData进行拦截

private final String ExpGetResultDataPonit = "execution(* com.haiyang.onlinejava.complier.service.TestService.getResultData(..))";//定义切入点,拦截servie包其子包下的所有类的所有方法//@Pointcut("execution(* com.haiyang.onlinejava.complier.service..*.*(..))")//拦截指定的方法,这里指只拦截TestService.getResultData这个方法

@Pointcut(ExpGetResultDataPonit)public voidexcuteService() {

}

对于切入点的配置表达式,可以在网上自行搜索,网上也有许多

在指定了切入点后,就可以对这个切入点excuteService()这个点进行相应的操作了。

可以配置@Before  @After 等来进行相应的处理,其代表的意思分别是前置与后置,就是下面代码这个意思

@OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {

Object result;try{//@Before

result =method.invoke(target, args);//@After

returnresult;

}catch(InvocationTargetException e) {

Throwable targetException=e.getTargetException();//@AfterThrowing

throwtargetException;

}finally{//@AfterReturning

}

}

由于要对入参和最终返回结果进行处理,所以选择Before和AfterReturning,原来以为after也可以,但看了下,它好像并不能拿到这个方法的返回值,而AfterReturning是一定可以的

拦截后,对应的处理代码如下:

//执行方法前的拦截方法

@Before("excuteService()")public voiddoBeforeMethod(JoinPoint joinPoint) {

System.out.println("我是前置通知,我将要执行一个方法了");//获取目标方法的参数信息

Object[] obj =joinPoint.getArgs();for(Object argItem : obj) {

System.out.println("---->now-->argItem:" +argItem);if(argItem instanceof ParamVO) {

ParamVO paramVO=(ParamVO) argItem;

paramVO.setInputParam("666666");

}

System.out.println("---->after-->argItem:" +argItem);

}

}/**

* 后置返回通知

* 这里需要注意的是:

* 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息

* 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数

* returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值*/@AfterReturning(value= ExpGetResultDataPonit, returning = "keys")public voiddoAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {

System.out.println("第一个后置返回通知的返回值:" +keys);if(keys instanceof ResultVO) {

ResultVO resultVO=(ResultVO) keys;

String message=resultVO.getMessage();

resultVO.setMessage("通过AOP把值修改了" +message);

}

System.out.println("修改完毕-->返回方法为:" +keys);

}

然后再请求一下之前的请求

从这里可以看出,通过AOP的拦截,已经把对应的值修改了,入参inputParam由111改成了666666,返回结果message也加上了几个字

除了用Before和AfterReturning外,还可以用环绕来实现同样的功能,如:

/**

* 环绕通知:

* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。

* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型*/@Around(ExpGetResultDataPonit)publicObject doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {

System.out.println("环绕通知的目标方法名:" +proceedingJoinPoint.getSignature().getName());

processInputArg(proceedingJoinPoint.getArgs());try {//obj之前可以写目标方法执行前的逻辑

Object obj = proceedingJoinPoint.proceed();//调用执行目标方法

processOutPutObj(obj);returnobj;

}catch(Throwable throwable) {

throwable.printStackTrace();

}return null;

}/**

* 处理返回对象*/

private voidprocessOutPutObj(Object obj) {

System.out.println("OBJ 原本为:" +obj.toString());if(obj instanceof ResultVO) {

ResultVO resultVO=(ResultVO) obj;

resultVO.setMessage("哈哈,我把值修改了" +resultVO.getMessage());

System.out.println(resultVO);

}

}/**

* 处理输入参数

*

* @param args 入参列表*/

private voidprocessInputArg(Object[] args) {for(Object arg : args) {

System.out.println("ARG原来为:" +arg);if(arg instanceof ParamVO) {

ParamVO paramVO=(ParamVO) arg;

paramVO.setInputParam("654321");

}

}

}

这样写,也可以达到相同的目的

切面代码完整如下:

package com.haiyang.onlinejava.complier.aspect;

import com.haiyang.onlinejava.complier.vo.ParamVO;

import com.haiyang.onlinejava.complier.vo.ResultVO;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.*;

import org.springframework.context.annotation.Configuration;

@Configuration

@Aspectpublic classServiceAspect {private final String ExpGetResultDataPonit = "execution(* com.haiyang.onlinejava.complier.service.TestService.getResultData(..))";//定义切入点,拦截servie包其子包下的所有类的所有方法//@Pointcut("execution(* com.haiyang.onlinejava.complier.service..*.*(..))")//拦截指定的方法,这里指只拦截TestService.getResultData这个方法

@Pointcut(ExpGetResultDataPonit)public voidexcuteService() {

}//执行方法前的拦截方法//@Before("excuteService()")

public voiddoBeforeMethod(JoinPoint joinPoint) {

System.out.println("我是前置通知,我将要执行一个方法了");//获取目标方法的参数信息

Object[] obj =joinPoint.getArgs();for(Object argItem : obj) {

System.out.println("---->now-->argItem:" +argItem);if(argItem instanceof ParamVO) {

ParamVO paramVO=(ParamVO) argItem;

paramVO.setInputParam("666666");

}

System.out.println("---->after-->argItem:" +argItem);

}

}/**

* 后置返回通知

* 这里需要注意的是:

* 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息

* 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数

* returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值*/

//@AfterReturning(value = ExpGetResultDataPonit, returning = "keys")

public voiddoAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {

System.out.println("第一个后置返回通知的返回值:" +keys);if(keys instanceof ResultVO) {

ResultVO resultVO=(ResultVO) keys;

String message=resultVO.getMessage();

resultVO.setMessage("通过AOP把值修改了" +message);

}

System.out.println("修改完毕-->返回方法为:" +keys);

}/**

* 后置最终通知(目标方法只要执行完了就会执行后置通知方法)*/

//@After("excuteService()")

public voiddoAfterAdvice(JoinPoint joinPoint) {

System.out.println("后置通知执行了!!!!");

}/**

* 环绕通知:

* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。

* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型*/@Around(ExpGetResultDataPonit)publicObject doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {

System.out.println("环绕通知的目标方法名:" +proceedingJoinPoint.getSignature().getName());

processInputArg(proceedingJoinPoint.getArgs());try {//obj之前可以写目标方法执行前的逻辑

Object obj = proceedingJoinPoint.proceed();//调用执行目标方法

processOutPutObj(obj);returnobj;

}catch(Throwable throwable) {

throwable.printStackTrace();

}return null;

}/**

* 处理返回对象*/

private voidprocessOutPutObj(Object obj) {

System.out.println("OBJ 原本为:" +obj.toString());if(obj instanceof ResultVO) {

ResultVO resultVO=(ResultVO) obj;

resultVO.setMessage("哈哈,我把值修改了" +resultVO.getMessage());

System.out.println(resultVO);

}

}/**

* 处理输入参数

*

* @param args 入参列表*/

private voidprocessInputArg(Object[] args) {for(Object arg : args) {

System.out.println("ARG原来为:" +arg);if(arg instanceof ParamVO) {

ParamVO paramVO=(ParamVO) arg;

paramVO.setInputParam("654321");

}

}

}

}

如不进行@Before和@AfterReturing的注释,最终的结果如下:

控制台打印的日志为:

通过查看打印的结果,我们可以知道@Around @Before  @After  @AfterReturning这几个注解的执行顺序为:

Around

AroundBefore

before

method.invoke()

AroundAfter

After

AfterReturning

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,Spring Boot可以基于AOP实现指定切面的拦截处理AOP(面向切面编程)是一种编程范式,可以将与业务逻辑无关的横切关注点(如日志记录、安全控制、性能统计等)通过切面的方式进行集中管理。 在Spring Boot中,可以通过在切面类上使用`@Aspect`注解来标识一个类为切面类,通过在切面类的方法上使用不同的注解(如`@Before`、`@After`、`@Around`等)来指定不同类型的切点拦截处理。 例如,我们可以定义一个切面类`LogAspect`,用于记录所有控制层方法的执行时间和返回结果: ```java @Aspect @Component public class LogAspect { private static final Logger logger = LoggerFactory.getLogger(LogAspect.class); @Around("execution(* com.example.myapp.controller..*.*(..))") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = joinPoint.proceed(); long endTime = System.currentTimeMillis(); long executionTime = endTime - startTime; logger.info("{} executed in {} ms, returned: {}", joinPoint.getSignature(), executionTime, result); return result; } } ``` 在上述代码中,`@Aspect`注解标识了`LogAspect`类为切面类,`@Around`注解指定了要拦截的切点为`com.example.myapp.controller`包下的所有方法,`logExecutionTime()`方法用于记录方法的执行时间和返回结果,并将日志输出到控制台中。 当控制层的方法被调用时,如果方法的签名匹配到了切点表达式,那么`LogAspect`类中对应的切面方法(如`logExecutionTime()`)就会被执行,从而实现了对方法的拦截处理

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值