SpringMVC的Controller接口方法参数解析
2017-09-22 16:59
323 查看
一、举例说明
(1)示例:方法参数没有任何注解 public Object query(List<Long> idList),传递参数为 .param("idList", "1").param("idList", "2")
结果:失败。org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [java.util.List]: Specified class is an interface
原因:没有任何注解并且不属于原生类型以及在业务系统不常见的类型,常用的List,Set就不满足前面的条件,所以参数的解析就交给了保底的ServletModelAttributeMethodProcessor以及它的父类ModelAttributeMethodProcessor,进过一系列的处理到达BeanUtils.instantiateClass(parameter.getParameterType())函数进行参数类型初始化。
(2)示例:方法参数没有任何注解 public Object query(ArrayList<Long> idList) ,传递参数为 .param("idList", "1").param("idList", "2")
结果:失败。内部报错throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,matches.buildErrorMessage(), matches.getPossibleMatches()),SpringMVC隐藏了错误,结果虽然没有报错,但是参数也没有映射成功。
原因:同(1),区别是ArrayList可以初始化,但是参数具体映射需要根据set/get属性方法进行反射赋值,ArrayList并没有set属性方法,所以失败。
(3)示例:方法参数有注解 public Object query(@RequestParam("idList") List<Long> idList) ,传递参数为 .param("idList", "1").param("idList", "2")
结果:成功
原因:因为我们有@RequestParam注解,所以参数解析给了优先级最高的RequestParamMethodArgumentResolver,它不需要对参数类型进行初始化,
而是采用另一套解析逻辑(它的父类AbstractNamedValueMethodArgumentResolver)来处理,在这里先针对注解进行解析,获取参数名value,通过String[] paramValues = webRequest.getParameterValues(name)获取参数值,然后选择SpringMVC容器预先加载的100多个转换器中的StringToCollectionConverter进行参数转化,不经过set/get属性方法,根据需要的参数类型List直接创建ArrayList,然后赋值成功。
(4)示例:方法参数有注解 public Object query(@RequestParam("idList") List<Long> notIdList) ,传递参数为 .param("idList", "1").param("idList", "2")
结果:成功
原因:当有注解时不会关注接口的参数名,只会以注解里面的value为准
(5)示例:方法参数没有任何注解 public Object query(User user) ,传递参数为 .param("name", "1").param("age", "2").param("time", "2017-09-22 00:00:00"),User对象里面含有这三个属性。
结果:成功
原因:同(1),区别是该类的两个属性具有set/get属性方法,writeMethod.invoke(this.object, value)可以赋值成功,在反射之前,需要对参数值进行类型转换Converter,因为字符串转日期的转换器容器自身并没有,所以我们需要自定义,通过@InitBinder注解定义一个转换器,容器启动就会加载到转换器列表里,用的时候会取出来使用。
(6)示例:方法参数没有任何注解 public Object query(User user) ,传递参数为.param("idList", "1").param("idList", "2"),User对象里面含有这个List集合属性。
结果:成功
原因:同(5),获取传递来的参数Enumeration<String> paramNames = request.getParameterNames(),同过set方法反射赋值
二、总结
1、SpringMVC其实是鼓励我们对参数使用注解的,容器会预先加载几十个Resolver到list集合里面,在进行参数解析的时候侯从中按顺序选择,RequestParamMethodArgumentResolver是排在第一个的,而不用注解的话会使用ServletModelAttributeMethodProcessor进行解析,它在集合的位置是最后,所以每个参数需要遍历一遍,但是容器会对每次参数的解析Resolver关系进行缓存,效率也没有多少降低。
<
4000
p style="margin-top:0px;margin-bottom:0px;padding-top:0px;padding-bottom:0px;color:rgb(85,85,85);font-family:'microsoft yahei';font-size:15px;">
2、集合参数传递,容器内部支持以逗号的集合参数param("idList", "1,2,3,4")
3、ServletModelAttributeMethodProcessor会先给变量类进行实例化,再根据set/get反射赋值
4、自定义对象User最好使用@ModelAttribute 注解,其他的基本类型包装对象以及集合使用@RequestParam注解,虽然@RequestParam的required方法经常会打印出很多错误日志,RESTful API接口可以使用@PathVariable注解。
三、补充
根据前面的例子显示,集合类参数不使用注解的话就不能成功被解析,这怎么能忍?下面是我参考源码改写的一个支持集合的CollectionArgumentResolver
public class CollectionArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
public CollectionArgumentResolver() {
super();
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
return new RequestParamNamedValueInfo();
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotations()) {
return false;
}
Class<?> paramType = parameter.getParameterType();
return Collection.class.isAssignableFrom(paramType);
}
@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
Object arg;
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
MultipartHttpServletRequest multipartRequest =
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
if (MultipartFile.class.equals(parameter.getParameterType())) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFile(name);
}
else if (isMultipartFileCollection(parameter)) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFiles(name);
}
else if(isMultipartFileArray(parameter)) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFiles(name).toArray(new MultipartFile[0]);
}
else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
assertIsMultipartRequest(servletRequest);
arg = servletRequest.getPart(name);
}
else if (isPartCollection(parameter)) {
assertIsMultipartRequest(servletRequest);
arg = new ArrayList<Object>(servletRequest.getParts());
}
else if (isPartArray(parameter)) {
assertIsMultipartRequest(servletRequest);
arg = RequestPartResolver.resolvePart(servletRequest);
}
else {
arg = null;
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
if (arg == null) {
String[] paramValues = webRequest.getParameterValues(name);
if (paramValues != null) {
arg = paramValues.length == 1 ? paramValues[0] : paramValues;
}
}
}
return arg;
}
private void assertIsMultipartRequest(HttpServletRequest request) {
String contentType = request.getContentType();
if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) {
throw new MultipartException("The current request is not a multipart request");
}
}
private boolean isMultipartFileCollection(MethodParameter parameter) {
Class<?> collectionType = getCollectionParameterType(parameter);
return ((collectionType != null) && collectionType.equals(MultipartFile.class));
}
private boolean isPartCollection(MethodParameter parameter) {
Class<?> collectionType = getCollectionParameterType(parameter);
return ((collectionType != null) && "javax.servlet.http.Part".equals(collectionType.getName()));
}
private boolean isPartArray(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType().getComponentType();
return ((paramType != null) && "javax.servlet.http.Part".equals(paramType.getName()));
}
private boolean isMultipartFileArray(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType().getComponentType();
return ((paramType != null) && MultipartFile.class.equals(paramType));
}
private Class<?> getCollectionParameterType(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
if (Collection.class.equals(paramType) || List.class.isAssignableFrom(paramType)){
Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(parameter);
if (valueType != null) {
return valueType;
}
}
return null;
}
@Override
protected void handleMissingValue(String paramName, MethodParameter parameter) throws ServletException {
throw new MissingServletRequestParameterException(paramName, parameter.getParameterType().getSimpleName());
}
@Override
public void contributeMethodArgument(MethodParameter parameter, Object value,
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
}
private static class RequestParamNamedValueInfo extends NamedValueInfo {
public RequestParamNamedValueInfo() {
super("", false, ValueConstants.DEFAULT_NONE);
}
}
private static class RequestPartResolver {
public static Object resolvePart(HttpServletRequest servletRequest) throws Exception {
return servletRequest.getParts().toArray(new Part[servletRequest.getParts().size()]);
}
}
}
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.ph3636.CollectionArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
(1)示例:方法参数没有任何注解 public Object query(List<Long> idList),传递参数为 .param("idList", "1").param("idList", "2")
结果:失败。org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [java.util.List]: Specified class is an interface
原因:没有任何注解并且不属于原生类型以及在业务系统不常见的类型,常用的List,Set就不满足前面的条件,所以参数的解析就交给了保底的ServletModelAttributeMethodProcessor以及它的父类ModelAttributeMethodProcessor,进过一系列的处理到达BeanUtils.instantiateClass(parameter.getParameterType())函数进行参数类型初始化。
(2)示例:方法参数没有任何注解 public Object query(ArrayList<Long> idList) ,传递参数为 .param("idList", "1").param("idList", "2")
结果:失败。内部报错throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,matches.buildErrorMessage(), matches.getPossibleMatches()),SpringMVC隐藏了错误,结果虽然没有报错,但是参数也没有映射成功。
原因:同(1),区别是ArrayList可以初始化,但是参数具体映射需要根据set/get属性方法进行反射赋值,ArrayList并没有set属性方法,所以失败。
(3)示例:方法参数有注解 public Object query(@RequestParam("idList") List<Long> idList) ,传递参数为 .param("idList", "1").param("idList", "2")
结果:成功
原因:因为我们有@RequestParam注解,所以参数解析给了优先级最高的RequestParamMethodArgumentResolver,它不需要对参数类型进行初始化,
而是采用另一套解析逻辑(它的父类AbstractNamedValueMethodArgumentResolver)来处理,在这里先针对注解进行解析,获取参数名value,通过String[] paramValues = webRequest.getParameterValues(name)获取参数值,然后选择SpringMVC容器预先加载的100多个转换器中的StringToCollectionConverter进行参数转化,不经过set/get属性方法,根据需要的参数类型List直接创建ArrayList,然后赋值成功。
(4)示例:方法参数有注解 public Object query(@RequestParam("idList") List<Long> notIdList) ,传递参数为 .param("idList", "1").param("idList", "2")
结果:成功
原因:当有注解时不会关注接口的参数名,只会以注解里面的value为准
(5)示例:方法参数没有任何注解 public Object query(User user) ,传递参数为 .param("name", "1").param("age", "2").param("time", "2017-09-22 00:00:00"),User对象里面含有这三个属性。
结果:成功
原因:同(1),区别是该类的两个属性具有set/get属性方法,writeMethod.invoke(this.object, value)可以赋值成功,在反射之前,需要对参数值进行类型转换Converter,因为字符串转日期的转换器容器自身并没有,所以我们需要自定义,通过@InitBinder注解定义一个转换器,容器启动就会加载到转换器列表里,用的时候会取出来使用。
(6)示例:方法参数没有任何注解 public Object query(User user) ,传递参数为.param("idList", "1").param("idList", "2"),User对象里面含有这个List集合属性。
结果:成功
原因:同(5),获取传递来的参数Enumeration<String> paramNames = request.getParameterNames(),同过set方法反射赋值
二、总结
1、SpringMVC其实是鼓励我们对参数使用注解的,容器会预先加载几十个Resolver到list集合里面,在进行参数解析的时候侯从中按顺序选择,RequestParamMethodArgumentResolver是排在第一个的,而不用注解的话会使用ServletModelAttributeMethodProcessor进行解析,它在集合的位置是最后,所以每个参数需要遍历一遍,但是容器会对每次参数的解析Resolver关系进行缓存,效率也没有多少降低。
<
4000
p style="margin-top:0px;margin-bottom:0px;padding-top:0px;padding-bottom:0px;color:rgb(85,85,85);font-family:'microsoft yahei';font-size:15px;">
2、集合参数传递,容器内部支持以逗号的集合参数param("idList", "1,2,3,4")
3、ServletModelAttributeMethodProcessor会先给变量类进行实例化,再根据set/get反射赋值
4、自定义对象User最好使用@ModelAttribute 注解,其他的基本类型包装对象以及集合使用@RequestParam注解,虽然@RequestParam的required方法经常会打印出很多错误日志,RESTful API接口可以使用@PathVariable注解。
三、补充
根据前面的例子显示,集合类参数不使用注解的话就不能成功被解析,这怎么能忍?下面是我参考源码改写的一个支持集合的CollectionArgumentResolver
public class CollectionArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
public CollectionArgumentResolver() {
super();
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
return new RequestParamNamedValueInfo();
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotations()) {
return false;
}
Class<?> paramType = parameter.getParameterType();
return Collection.class.isAssignableFrom(paramType);
}
@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
Object arg;
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
MultipartHttpServletRequest multipartRequest =
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
if (MultipartFile.class.equals(parameter.getParameterType())) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFile(name);
}
else if (isMultipartFileCollection(parameter)) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFiles(name);
}
else if(isMultipartFileArray(parameter)) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFiles(name).toArray(new MultipartFile[0]);
}
else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
assertIsMultipartRequest(servletRequest);
arg = servletRequest.getPart(name);
}
else if (isPartCollection(parameter)) {
assertIsMultipartRequest(servletRequest);
arg = new ArrayList<Object>(servletRequest.getParts());
}
else if (isPartArray(parameter)) {
assertIsMultipartRequest(servletRequest);
arg = RequestPartResolver.resolvePart(servletRequest);
}
else {
arg = null;
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
if (arg == null) {
String[] paramValues = webRequest.getParameterValues(name);
if (paramValues != null) {
arg = paramValues.length == 1 ? paramValues[0] : paramValues;
}
}
}
return arg;
}
private void assertIsMultipartRequest(HttpServletRequest request) {
String contentType = request.getContentType();
if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) {
throw new MultipartException("The current request is not a multipart request");
}
}
private boolean isMultipartFileCollection(MethodParameter parameter) {
Class<?> collectionType = getCollectionParameterType(parameter);
return ((collectionType != null) && collectionType.equals(MultipartFile.class));
}
private boolean isPartCollection(MethodParameter parameter) {
Class<?> collectionType = getCollectionParameterType(parameter);
return ((collectionType != null) && "javax.servlet.http.Part".equals(collectionType.getName()));
}
private boolean isPartArray(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType().getComponentType();
return ((paramType != null) && "javax.servlet.http.Part".equals(paramType.getName()));
}
private boolean isMultipartFileArray(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType().getComponentType();
return ((paramType != null) && MultipartFile.class.equals(paramType));
}
private Class<?> getCollectionParameterType(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
if (Collection.class.equals(paramType) || List.class.isAssignableFrom(paramType)){
Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(parameter);
if (valueType != null) {
return valueType;
}
}
return null;
}
@Override
protected void handleMissingValue(String paramName, MethodParameter parameter) throws ServletException {
throw new MissingServletRequestParameterException(paramName, parameter.getParameterType().getSimpleName());
}
@Override
public void contributeMethodArgument(MethodParameter parameter, Object value,
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
}
private static class RequestParamNamedValueInfo extends NamedValueInfo {
public RequestParamNamedValueInfo() {
super("", false, ValueConstants.DEFAULT_NONE);
}
}
private static class RequestPartResolver {
public static Object resolvePart(HttpServletRequest servletRequest) throws Exception {
return servletRequest.getParts().toArray(new Part[servletRequest.getParts().size()]);
}
}
}
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.ph3636.CollectionArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
相关文章推荐
- 通过实现HandlerMethodArgumentResolver接口,给springMvc的Controller的方法注入自定义参数
- (不推荐使用)springMVC基本配置+继承MultiActionController来实现根据参数名指定要请求的方法
- 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
- 详解SpringMVC中Controller的方法中参数的工作原理——基于maven
- 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
- SpringMVC的Controller方法的参数不能直接绑定List、Set、Map
- 详解SpringMVC中Controller的方法中参数的工作原理
- 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
- springmvc中参数绑定pojo绑定,同个controller的不同方法调用,后一个方法的形参取到request域中的值为null
- SpringMVC 拦截 controller并解析接口平台
- springMVC controller间跳转 重定向 传递参数的方法
- springmvc接口参数如何解析
- 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
- springMVC controller间跳转 重定向 传递参数的方法
- SpringMVC学习(二)高级参数绑定,拦截器,controller方法返回值
- Hibernate的Session接口中delete/update方法2个参数(entityName)的深入解析
- 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析] – format丶
- Spring-boot application controller 方法自动解析Locale参数原理
- springMVC controller间跳转 重定向 传递参数的方法
- 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]