在基於SpringMVC的WEB服務開發過程中,經常會遇到如入參解密、返參加密這樣的過程,這個過程可能需要很多代碼可能只需要一行調用,但是每個Controller都需要調用這樣一個方法就可能會感覺繁瑣,而且不利於主要業務代碼閱讀,有時會影響項目架構。本文通過一個小案例,基於Spring AOP思想來解決類似這樣的問題,希望能起到舉一反三的作用,在開發過程中能夠解決一些問題、減小代碼的重複,用最少的代碼完成功能。
SpringMVC的基本過程
使用SpringMVC時,所有的請求都是最先經過DispatcherServlet的,然後由DispatcherServlet選擇合適的HandlerMapping和HandlerAdapter來處理請求,HandlerMapping的作用就是找到請求所對應的方法,而HandlerAdapter則來處理和請求相關的的各種事情,比如參數如何綁定到@RequestMapping的方法中,參數如何返回到Model中等。
SpringMVC Controller參數解析是由RequestMappingHandlerAdapter
類進行處理的,這個類通過一個List維護不僅維護了SpringMVC自帶的參數解析方法,同時也支持用記自定義的參數解析器。SpringMVC通過查詢List中的解析器,如果找到一個能支持解析的對象,就會讓它去解析而不會再去查找其它的解析器。所以一般來講Controller方法的入參模型實體類只有一個對應的解析器去處理。
參數綁定接口是HandlerMethodArgumentResolver
,接口定義了兩個方法: boolean supportsParameter(MethodParameter parameter)
需要返回該解析器是否支持參數 parameter的封裝,支持就返回true;然後具體的解析業務由接口的另一個方法 Object resolveArgument(MethodParameter parameter,
處理,並返回處理後的對象。
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory)
以下代碼支持有@MobileRequestParam註解的Controller方法參數進行參數綁定。
public class EhrMobileRequestParamResolver implements HandlerMethodArgumentResolver {
private static final Logger logger = LoggerFactory.getLogger(EhrMobileRequestParamResolver.class);
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(MobileRequestParam.class);
}
/**
* 解析json串,並綁定傳參
*/
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Object resBean = null;
try {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Object data = request.getAttribute("data");
Object version = request.getAttribute("version");
if (StringUtils.isNotBlank(data.toString())) {
JSONObject jsonObject = JSONObject.fromObject(data);
jsonObject.put("version", version);
resBean = JSONObject.toBean(jsonObject, parameter.getParameterType());
}
} catch (Exception e) {
logger.error(">>>> 參數解析出錯 {} <<<<", e);
}
return resBean;
}
}
使用上述自定義綁定參數,需要application-servlet.xml配置如下
```xml
<mvc:annotation-driven >
<mvc:argument-resolvers>
<bean class="com.project.EhrMobileRequestParamResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
Controller方法返回參數增強處理
@ControllerAdvice是SpringMVC的Controller增加處理類,其原理是使用AOP對Controller控制器進行增強,這樣就可以對控制器的方法調用前後進行處理。使用了這個註解,表示Controller會在方法調用前後根據註解類定義的方法進行相應的處理。開啓註解需要在application-servlet.xml配置中添加掃描註解。
ResponseBodyAdvice接口對調用帶有@ResponseBody、@ResponseEntity註解的Controller方法執行後,而在返回到View層前進行自定義的處理。與HandlerMethodArgumentResolver 接口一樣有兩個方法:supports方法的作用是判斷是否需要對此Controller方法進行處理,Controller方法及返參的信息由對象MethodParameter returnType
維護,如果支持處理則返回ture;然後再由beforeBodyWrite方法進行自定義處理,顧名思義,這個方法就是在使用相應的HttpMessageConvert 進行write之前會被調用,就是一個切面方法。
如下的代碼是在Model writer之前對其中一個參數進行加密。
@ControllerAdvice
public class EhrMobileResponseAdvice implements ResponseBodyAdvice<Object>{
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return DtoPublicResponse.class.isAssignableFrom(returnType.getParameterType());
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
HttpServletRequest req = ((ServletServerHttpRequest) request).getServletRequest();
System.err.println(returnType.getParameterType());
if (body instanceof DtoPublicMobileResponseObj) {
DtoPublicMobileResponseObj obj = (DtoPublicMobileResponseObj) body;
Object reStr = obj.getResultMessage();
if (null != reStr && StringUtils.isNotBlank(reStr.toString()) && !"1".equals(req.getAttribute("token"))) {
obj.setResultMessage(SecurityUtil.getEncryptDataForParam(reStr, req.getAttribute("version").toString()));
return obj;
}
}
// TODO Auto-generated method stub
return body;
}
}