在Spring基礎(chǔ)上使用自定義AOP氢橙,主要有以下兩種
-
1.通過(guò)spring提供的注解來(lái)實(shí)現(xiàn)AOP。這種方式有很大靈活性匈仗,因?yàn)锧Pointcut可以搭配自定義注解識(shí)別出pointcut瓢剿,也可以通過(guò)切點(diǎn)表達(dá)式來(lái)識(shí)別pointcut逢慌。
注解實(shí)現(xiàn)AOP.png - 2.通過(guò)xml文件配置實(shí)現(xiàn)AOP悠轩。
以上兩種實(shí)現(xiàn)都可以參考這篇文章。
Spring還有幾種可以讓開(kāi)發(fā)者自己擴(kuò)展的AOP攻泼,相比上面介紹的AOP火架,主要體現(xiàn)在切入點(diǎn)是固定的,開(kāi)發(fā)者需要自己實(shí)現(xiàn)相應(yīng)的Advice忙菠。
- 1.ControllerAdvice或者RestControllerAdvice何鸡。主要用在Controller層,用于捕獲全局異常牛欢、自定義異常并處理異常骡男。
- 2.RequestBodyAdvice和ResponseBodyAdvice。主要用在Controller層的方法參數(shù)解析階段傍睹,也就是我們將入?yún)⑥D(zhuǎn)化為對(duì)象的階段隔盛。更加直白點(diǎn),就是使用@RequestBody和@ResponseBody注解前后拾稳。
現(xiàn)在我們的業(yè)務(wù)需求是這樣的:
APP使用對(duì)稱加秘鑰將參數(shù)json串加密吮炕,加密報(bào)文通過(guò)BasePayParam對(duì)象傳輸給后臺(tái),后臺(tái)將BasePayParam對(duì)象的密文解密為json,再轉(zhuǎn)化為對(duì)象處理業(yè)務(wù)访得。之前的處理邏輯是這樣的:
- BasePayParam
public class BasePayParam {
/**傳輸?shù)拿芪?/
private String param;
}
- controller層方法接收BasePayParam 對(duì)象
@PostMapping(value = "/pay")
public BasePayVo bindPhone(@RequestBody BasePayParam basePayParam ) {
//具體的解密邏輯就在一下的serVice完成
return bindPhoneService.bindPhone(basePayParam);
}
-Service層解密參數(shù)并轉(zhuǎn)化為相應(yīng)的對(duì)象
public BasePayVo bindPhone( BasePayParam basePayParam) {
// 解密參數(shù)
String dgutPayAesKey = "AB222222222";
String param = basePayParam.getParam();
//把加密報(bào)文轉(zhuǎn)化為對(duì)象
BindPhoneReq bindPhoneDto= checkParamsService.deAESParam(param, dgutPayAesKey, BindPhoneReq.class);
......
//把參數(shù)加密返回APP
String enAESRsp = checkParamsService.enAESParam(JsonUtil.Object2Json(result).trim(), dgutPayAesKey);
basePayVo .setData(enAESRsp);
return basePayVo ;
}
加解密的處理代碼在service層的每個(gè)方法都使用一遍龙亲。這種做法是非常浪費(fèi)效率的,顯得啰里啰嗦,沒(méi)有方法的復(fù)用確實(shí)讓人受不了鳄炉。作為一個(gè)很懶的程序員杜耙,我要用AOP優(yōu)化一下。因?yàn)槲覀兊臉I(yè)務(wù)處理邏輯是高度一致的:解密報(bào)文->處理業(yè)務(wù)->加密返回?cái)?shù)據(jù)迎膜。自然而然應(yīng)該有這個(gè)思路:使用AOP簡(jiǎn)化處理泥技。
優(yōu)化后處理流程
1.自定義注解
/**
* 解密注解
* @Author zhouyu
* @date 2020/9/8 9:54
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decrypt {
String value() default "";
}
/**
* 加密注解
* @Author zhouyu
* @date 2020/9/8 9:55
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt {
String value() default "";
}
2.入?yún)⒔饷芴幚?/p>
@RestControllerAdvice(basePackageClasses = {com.dgut.web.pay.icbc.IcbcPayQrController.class,
com.dgut.web.pay.unipay.UnipayQrController.class})
public class DecryRequestBodyAdvice implements RequestBodyAdvice {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SystemConfigRepository systemConfigRepository;
public String getPayAesKey() {
return "112121212121212";
}
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
//只解析帶有@RequestBody注解
return methodParameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
if (methodParameter.getMethod().isAnnotationPresent(Decrypt.class)) {
return new HttpInputMessage() {
@Override
public InputStream getBody() {
try {
String bodyStr = IOUtils.toString(httpInputMessage.getBody(), "utf-8");
JsonObject jsonObject = new JsonParser().parse(bodyStr).getAsJsonObject();
JsonElement jsonElement = jsonObject.get("param");
if (jsonElement != null) {
//解密
String encrtStr = jsonElement.getAsString();
bodyStr = AesAlgorithmUtil.DecryptAES(encrtStr,getPayAesKey());
}
return IOUtils.toInputStream(bodyStr, "utf-8");
} catch (Exception e) {
logger.error("解密參數(shù)異常", e);
throw new RuntimeException("參數(shù)解密異常");
}
}
@Override
public HttpHeaders getHeaders() {
return httpInputMessage.getHeaders();
}
};
} else {
return httpInputMessage;
}
}
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
}
controller直接使用@RequestBody 接收解密后的參數(shù),并加上剛才自定義的注解磕仅。
@Encrypt
@Decrypt
@PostMapping(value = "/pay")
public BasePayVo bindPhone(@RequestBody BindPhoneReq bindPhoneDto) {
//下面的service就不需要解密珊豹、加密啦
return bindPhoneService.bindPhone(bindPhoneDto);
}
3.響應(yīng)加密處理
@RestControllerAdvice(basePackageClasses = {com.dgut.web.pay.icbc.IcbcPayQrController.class,
com.dgut.web.pay.unipay.UnipayQrController.class})
public class EncryResponseBodyAdvice implements ResponseBodyAdvice {
private Logger logger = LoggerFactory.getLogger(EncryResponseBodyAdvice.class);
@Autowired
private SystemConfigRepository systemConfigRepository;
public String getPayAesKey() {
return "112121212121212";
}
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object bodyObj, MethodParameter methodParameter, MediaType mediaType, Class aClass,
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (methodParameter.getMethod().isAnnotationPresent(Encrypt.class)) {
if (bodyObj instanceof BasePayVo) {
BasePayVo resBody = (BasePayVo) bodyObj;
Object encryData = resBody.getInfo();
if (encryData != null) {
try {
//將body加密后返回
String jsonData = JsonUtil.Object2Json(encryData);
String aesEncryptData = AesAlgorithmUtil.EncryptAES(jsonData, getPayAesKey());
resBody.setInfo(aesEncryptData);
} catch (Exception e) {
logger.error("加密參數(shù)異常", e);
}
} else {
return bodyObj;
}
}
}
return bodyObj;
}
}
到此為止,所有的service層的重復(fù)使用的加密解密代碼就可以逐步去掉了榕订。