对于spring框架来讲,最重要的两大特性就是AOP 和IOC。java
之前一直都知道有这两个东西,在平时作的项目中也经常会涉及到这两块,像spring的事务管理什么的,在看了些源码后,才知道原来事务管理也是用的AOP来实现的。对于IOC的话,平时接触的就更多了,什么autowired,resource各类注解,就是IOC的各类应用。spring
一直我也想着能有机会本身动手写个aop的小DEMO,不过一直没机会,想到了许多,在网上一搜,基本上都已经有了。今天想到一个用于对service方法进行拦截的功能点,今天决定用springBoot的工程来实现一下。app
功能点描述:对某个service的方法执行前,获取出入参,对入参的参数进行修改,将参数进行替换。而后在这个方法执行完毕后,再对其返回结果进行修改。主要就是对一个方法装饰一下。说到装饰,第一想到的是采用装饰器模式来实现,但装饰器模式须要对整个代码的结构进行一些修改,为了达到对之前的代码不进行任何接触,且装饰器模式的局限性较小,因此最好仍是用spring的AOP来实现这种对代码无任何侵入的功能。框架
service的代码以下:ide
@Service
public class TestServiceImpl implements TestService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@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" + paramVO.toString());
logger.info("---->return INFO:{}", resultVO.toString());
return resultVO;
}spring-boot
其中入参为paramVO,代码以下:this
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
}.net
其调用的入口为一个controller,代码以下:日志
@RequestMapping(value = "test")
@RestController
public class TestController {
@Resource
private TestService testService;
@GetMapping(value = "getResult")
public ResultVO getResult(ParamVO paramVO) {
ResultVO resultData = testService.getResultData(paramVO);
return resultData;
}code
在正常状况下,按照如上的代码进行调用将返回以下的信息:
经过返回的信息能够看到,入参是咱们在请求参数传入的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(* com.haiyang.onlinejava.complier.service.TestService.getResultData(..))";
//定义切入点,拦截servie包其子包下的全部类的全部方法
// @Pointcut("execution(* com.haiyang.onlinejava.complier.service..*.*(..))")
//拦截指定的方法,这里指只拦截TestService.getResultData这个方法
@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 = 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 void doAfterReturningAdvice1(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)
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) {
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 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 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
@Aspect
public class ServiceAspect {
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 void excuteService() {
}
//执行方法前的拦截方法
// @Before("excuteService()")
public void doBeforeMethod(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 void doAfterReturningAdvice1(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 void doAfterAdvice(JoinPoint joinPoint) {
System.out.println("后置通知执行了!!!!");
}
/**
* 环绕通知:
* 环绕通知很是强大,能够决定目标方法是否执行,何时执行,执行时是否须要替换方法参数,执行完毕是否须要替换返回值。
* 环绕通知第一个参数必须是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) {
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 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
监控某一方法的书写规则为:
ExpGetResultDataPoint = "execution(* com.dahuatech.thenext.beans.JerseyClient.call(..))"
其中call是JerseyClient类中的一个方法。
@After(ExpGetResultDataPoint)
public void deAfter(JoinPoint joinPoint){
do business
}
https://blog.csdn.net/puhaiyang/article/details/78146620