對於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