SpringBootAOP之对请求的参数⼊参与返回结果进⾏拦截处理
对于spring框架来说,最重要的两⼤特性就是AOP 和IOC。
以前⼀直都知道有这两个东西,在平时做的项⽬中也常常会涉及到这两块,像spring的事务管理什么的,在看了些源码后,才知道原来事务管理也是⽤的AOP来实现的。对于IOC的话,平时接触的就更多了,什么autowired,resource各种注解,就是IOC的各种应⽤。
⼀直我也想着能有机会⾃⼰动⼿写个aop的⼩DEMO,不过⼀直没机会,想到了许多,在⽹上⼀搜,基本上都已经有了。今天想到⼀个⽤于对service⽅法进⾏拦截的功能点,今天决定⽤springBoot的⼯程来实现⼀下。
功能点描述:对某个service的⽅法执⾏前,获取出⼊参,对⼊参的参数进⾏修改,将参数进⾏替换。然后在这个⽅法执⾏完毕后,再对其返回结果进⾏修改。主要就是对⼀个⽅法装饰⼀下。说到装饰,第⼀想到的是采⽤装饰器模式来实现,但装饰器模式需要对整个代码的结构进⾏⼀些修改,为了达到对以前的代码不进⾏任何接触,且装饰器模式的局限性较⼩,所以最好还是⽤spring的AOP来实现这种对代码⽆任何侵⼊的功能。
service的代码如下:
spring out 是什么意思
@Service
public class TestServiceImpl implements TestService {
private Logger logger = Class());
@Override
public ResultVO getResultData(ParamVO paramVO) {
return process(paramVO);
}
private ResultVO process(ParamVO paramVO) {
logger.info("----->input INFO:{}", paramVO);
ResultVO resultVO = new ResultVO();
resultVO.setCode(200);
resultVO.setData(Arrays.asList("123", "456", "789"));
resultVO.setMessage("OK!!!!!!!! and your inputParam is" + String());
logger.info("---->return INFO:{}", String());
return resultVO;
}
其中⼊参为paramVO,代码如下:
public class ParamVO {
private String inputParam;
private String inputParam2;
//getter and setter
}
返回的参数ResutVO,代码如下:
public class ResultVO {
private Integer code;
private String message;
private Object data;
//getter and setter
}
其调⽤的⼊⼝为⼀个controller,代码如下:
@RequestMapping(value = "test")
@RestController
public class TestController {
@Resource
private TestService testService;
@GetMapping(value = "getResult")
public ResultVO getResult(ParamVO paramVO) {
ResultVO resultData = ResultData(paramVO);
return resultData;
}
在正常情况下,按照如上的代码进⾏调⽤将返回如下的信息:
通过返回的信息可以看到,⼊参是我们在请求参数传⼊的inputParam=111和inputParam2=2220
现在要做的就是把⼊参的参数通过AOP来拦截,并进⾏修改。对于返回值,也进⾏⼀下修改。
⾸先让⼯程引⼊AOP的包:
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后定义⼀个Aspect,并指定⼀个切⼊点,配置要进⾏哪些⽅法的拦截
这⾥只针对TestSevice这个接⼝下的getResultData进⾏拦截
private final String ExpGetResultDataPonit = "execution(* linejavaplier.ResultData(..))";
//定义切⼊点,拦截servie包其⼦包下的所有类的所有⽅法
//    @Pointcut("execution(* linejavaplier.service..*.*(..))")
//拦截指定的⽅法,这⾥指只拦截ResultData这个⽅法
@Pointcut(ExpGetResultDataPonit)
public void excuteService() {
}
对于切⼊点的配置表达式,可以在⽹上⾃⾏搜索,⽹上也有许多
在指定了切⼊点后,就可以对这个切⼊点excuteService()这个点进⾏相应的操作了。
可以配置@Before  @After 等来进⾏相应的处理,其代表的意思分别是前置与后置,就是下⾯代码这个意思
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
try {
//@Before
result = method.invoke(target, args);
//@After
return result;
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
//@AfterThrowing
throw targetException;
} finally {
//@AfterReturning
}
}
由于要对⼊参和最终返回结果进⾏处理,所以选择Before和AfterReturning,原来以为after也可以,但看了下,它好像并不能拿到这个⽅法的返回值,⽽AfterReturning是⼀定可以的
拦截后,对应的处理代码如下:
//执⾏⽅法前的拦截⽅法
@Before("excuteService()")
public void doBeforeMethod(JoinPoint joinPoint) {
System.out.println("我是前置通知,我将要执⾏⼀个⽅法了");
//获取⽬标⽅法的参数信息
Object[] obj = Args();
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 void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
System.out.println("第⼀个后置返回通知的返回值:" + keys);
if (keys instanceof ResultVO) {
ResultVO resultVO = (ResultVO) keys;
String message = Message();
resultVO.setMessage("通过AOP把值修改了 " + message);
}
System.out.println("修改完毕-->返回⽅法为:" + keys);
}
然后再请求⼀下之前的请求
从这⾥可以看出,通过AOP的拦截,已经把对应的值修改了,⼊参inputParam由111改成了666666,返回结果message也加上了⼏个字
除了⽤Before和AfterReturning外,还可以⽤环绕来实现同样的功能,如:
/**
* 环绕通知:
* 环绕通知⾮常强⼤,可以决定⽬标⽅法是否执⾏,什么时候执⾏,执⾏时是否需要替换⽅法参数,执⾏完毕是否需要替换返回值。
* 环绕通知第⼀个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(ExpGetResultDataPonit)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("环绕通知的⽬标⽅法名:" + Signature().getName());
Args());
try {//obj之前可以写⽬标⽅法执⾏前的逻辑
Object obj = proceedingJoinPoint.proceed();//调⽤执⾏⽬标⽅法
processOutPutObj(obj);
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
/**
* 处理返回对象
*/
private void processOutPutObj(Object obj) {
System.out.println("OBJ 原本为:" + String());
if (obj instanceof ResultVO) {
ResultVO resultVO = (ResultVO) obj;
resultVO.setMessage("哈哈,我把值修改了" + Message());
System.out.println(resultVO);
}
}
/**
* 处理输⼊参数
*
* @param args ⼊参列表
*/
private void processInputArg(Object[] args) {
for (Object arg : args) {
System.out.println("ARG原来为:" + arg);
if (arg instanceof ParamVO) {
ParamVO paramVO = (ParamVO) arg;
paramVO.setInputParam("654321");
}
}
}
这样写,也可以达到相同的⽬的
切⾯代码完整如下:
package linejavaplier.aspect;
import linejavaplier.vo.ParamVO;
import linejavaplier.vo.ResultVO;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import t.annotation.Configuration;
@Configuration
@Aspect
public class ServiceAspect {
private final String ExpGetResultDataPonit = "execution(* linejavaplier.ResultData(..))";
//定义切⼊点,拦截servie包其⼦包下的所有类的所有⽅法
//    @Pointcut("execution(* linejavaplier.service..*.*(..))")
//拦截指定的⽅法,这⾥指只拦截ResultData这个⽅法
@Pointcut(ExpGetResultDataPonit)
public void excuteService() {
}
//执⾏⽅法前的拦截⽅法
//    @Before("excuteService()")
public void doBeforeMethod(JoinPoint joinPoint) {
System.out.println("我是前置通知,我将要执⾏⼀个⽅法了");
//获取⽬标⽅法的参数信息
Object[] obj = Args();
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 void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
System.out.println("第⼀个后置返回通知的返回值:" + keys);
if (keys instanceof ResultVO) {
ResultVO resultVO = (ResultVO) keys;
String message = Message();
resultVO.setMessage("通过AOP把值修改了 " + message);
}
System.out.println("修改完毕-->返回⽅法为:" + keys);
}
/**
* 后置最终通知(⽬标⽅法只要执⾏完了就会执⾏后置通知⽅法)
*/
//    @After("excuteService()")
public void doAfterAdvice(JoinPoint joinPoint) {
System.out.println("后置通知执⾏了!!!!");
}
/**
* 环绕通知:
* 环绕通知⾮常强⼤,可以决定⽬标⽅法是否执⾏,什么时候执⾏,执⾏时是否需要替换⽅法参数,执⾏完毕是否需要替换返回值。
* 环绕通知第⼀个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(ExpGetResultDataPonit)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("环绕通知的⽬标⽅法名:" + Signature().getName());
Args());
try {//obj之前可以写⽬标⽅法执⾏前的逻辑
Object obj = proceedingJoinPoint.proceed();//调⽤执⾏⽬标⽅法
processOutPutObj(obj);
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
/**
* 处理返回对象
*/
private void processOutPutObj(Object obj) {
System.out.println("OBJ 原本为:" + String());
if (obj instanceof ResultVO) {
ResultVO resultVO = (ResultVO) obj;
resultVO.setMessage("哈哈,我把值修改了" + Message());
System.out.println(resultVO);
}
}
/**
* 处理输⼊参数
*
* @param args ⼊参列表
*/
private void processInputArg(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

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。