场景:在重构公司的项目当中(将scalay语言转为java语言),由于历史遗留问题和语言,前端多个页面,对于同一接口有时使用json格式提交,有时使用表单形式提交,项目使用的是前后端分离模式,需要自定义参数解析器。
分析:SpringMVC中对json形式和表单形式的参数解析器都有实现,因而不需要我们自己去实现具体参数解析,而是在原有的功能方面进行增强,故此很容易想到可利用java中的装饰器模式去实现,具体实现如下。
首先:自定义注解,
import java.lang.annotation.*;
/**
*
* 标志者,我们将使用{@link com.lin.CustomMethodArgumentResolver}类去绑定参数
* @author jianglinzou
* @date 2019/4/13 下午3:20
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomParamBinding {
}
自定义的参数解析器如下:
import com.lin.CustomMethodArgumentResolverException;
import com.lin.annos.CustomParamBinding;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 一个HandlerMethodArgumentResolver的装饰器模式,能够支持json格式和表单形式的数据解析
*
* @author jianglinzou
* @date 2019/4/13 上午1:17
*/
/** * 一个HandlerMethodArgumentResolver的装饰器模式,能够支持json格式和表单形式的数据解析 * * @author jianglinzou * @date 2019/4/13 上午1:17 */ @Component public class CustomMethodArgumentResolver implements HandlerMethodArgumentResolver, Ordered, ApplicationListener { private RequestMappingHandlerAdapter requestMappingHandlerAdapter; private ListlocalResolver = new ArrayList<>(); private boolean seal; private static Logger logger = LoggerFactory.getLogger(CustomMethodArgumentResolver.class); // @Autowired // RequestMappingHandlerAdapter requestMappingHandlerAdapter; //对应json格式的数据解析,SpringMVC中已实现 private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor; //对应表单格式的数据解析,SpringMVC中已实现 private ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor; @Override public boolean supportsParameter(MethodParameter parameter) { // !parameter.hasParameterAnnotation(PathVariable.class) if (parameter.hasParameterAnnotation(CustomParamBinding.class)) { logger.info("will user CustomMethodArgumentResolver to resolve parameter for:{}", parameter.getExecutable()); return true; } return false; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Class> paramType = parameter.getParameterType(); HttpServletRequest httpServletRequest = (HttpServletRequest) webRequest.getNativeRequest(); //根据请求头Content-Type,判断是什么形式提交 String contentType = httpServletRequest.getHeader("Content-Type"); if (Strings.isBlank(contentType)) { //没有,默认用表单 return servletModelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } //表单形式 if (contentType.startsWith("application/x-www-form-urlencoded")) { logger.info("the contentType is application/x-www-form-urlencoded "); return servletModelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } //json形式 if (contentType.startsWith("application/json")) { logger.info("the contentType is application/json "); return requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } return servletModelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory); // // return doContinueResolver(localResolver, parameter, mavContainer, webRequest, binderFactory); } private Object doContinueResolver(List localResolver, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //移除,自定义解析器,继续解析 if (CollectionUtils.isEmpty(localResolver)) { logger.warn("localResolver is empty"); return null; } localResolver.remove(this); for (HandlerMethodArgumentResolver resolver : localResolver) { if (resolver.supportsParameter(parameter)) { return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } } throw new CustomMethodArgumentResolverException("自定义解析参数出现异常,无法解析该参数:" + parameter.getParameter()); } @Override public int getOrder() { return HIGHEST_PRECEDENCE + 1; } @Override public void onApplicationEvent(ApplicationEvent event) { if (!seal) { //响应ApplicationReadyEvent事件,表明tomcat(jetty)容器已将上下文填充完毕,从而从容器中获取json和表单的参数解析器 if (event instanceof ApplicationReadyEvent) { System.out.println("--------"); RequestMappingHandlerAdapter requestMappingHandlerAdapter = (RequestMappingHandlerAdapter) ((ApplicationReadyEvent) event).getApplicationContext().getBean("requestMappingHandlerAdapter"); this.requestMappingHandlerAdapter = requestMappingHandlerAdapter; if (Objects.isNull(requestMappingHandlerAdapter)) { throw new RuntimeException("自定义参数解析器加载失败"); } for (HandlerMethodArgumentResolver resolver : requestMappingHandlerAdapter.getArgumentResolvers()) { if (resolver instanceof RequestResponseBodyMethodProcessor) { //获取json形式的解析器 this.requestResponseBodyMethodProcessor = (RequestResponseBodyMethodProcessor) resolver; continue; } if (resolver instanceof ServletModelAttributeMethodProcessor) { //获取表单形式的解析器 this.servletModelAttributeMethodProcessor = (ServletModelAttributeMethodProcessor) resolver; } } localResolver.addAll(requestMappingHandlerAdapter.getArgumentResolvers()); this.seal = true; } } } }
其中CustomMethodArgumentResolverException如下:
/**
* @author jianglinzou
* @date 2019/4/13 下午10:49
*/
public class CustomMethodArgumentResolverException extends Exception {
public CustomMethodArgumentResolverException(String msg) {
super(msg);
}
}
在最后在配置文件中添加我们的自定义解析器:
import com.lin.CustomMethodArgumentResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.OrderComparator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import javax.annotation.Resource;
import java.util.List;
/**
* @author jianglinzou
* @date 2019/4/13 上午2:41
*/
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Resource
private CustomMethodArgumentResolver customMethodArgumentResolver;
@Override
protected void addArgumentResolvers(List argumentResolvers) {
argumentResolvers.add(customMethodArgumentResolver);
OrderComparator.sort(argumentResolvers);
// Collections.sort(argumentResolvers,OrderComparator);
// 注册Spring data jpa pageable的参数分解器
// argumentResolvers.add(new PageableHandlerMethodArgumentResolver());
}
}
配置完成后,写一个简单的controller如下
@Controller
@RequestMapping("/")
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
// @Autowired
// AmazonSummaryMapper amazonSummaryMapper;
@PostMapping("/test/helloWorld")
@ResponseBody
public String returnHelloWorld(@CustomParamBinding Account account) {
List sellerId = new ArrayList();
sellerId.add("A7DCHBVITGESC");
// sellerId.add("A16W335XFOTZRV");
return account.getAccountType();
}
}
验证过后,我们发现,该接口即支持表单形式提交,又支持json格式提交