这次的主题

最近一直在学习SpringMVC..(这句话我已经至少写了3,4遍了....).这次的研究主要是RequestMappingHandlerAdapter中的各种ArgumentsResolver....

前面写了太多理论的东西...这次我想来点实践的....

SpringMVC自定义的@Controller的方法参数如果有多个,并且有重复的属性名称的话是默认不支持的..这点和Struts2很不一样..可能很多用过Struts2的朋友都不习惯SpringMVC的这种用法..

确实,我也是觉得Struts2那样方便很多.所以我想介绍下如何增强SpringMVC的功能,达到我们的目的.

注:具体步骤涉及了太多的类...在这篇文章中我不想弄的太复杂...不会介绍很多原理....

方法:改源码

具体方案

这方面网上也有不少例子....但是貌似都不简单..我先来介绍一种最简单粗暴的方法...直接改源码.

真真真最简单...源码基础上加一行即可!

 package org.springframework.web.method.annotation;

 /*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ import java.lang.annotation.Annotation;
import java.util.Map; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.bind.support.WebRequestDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer; /**
* Resolves method arguments annotated with {@code @ModelAttribute} and handles
* return values from methods annotated with {@code @ModelAttribute}.
*
* <p>Model attributes are obtained from the model or if not found possibly
* created with a default constructor if it is available. Once created, the
* attributed is populated with request data via data binding and also
* validation may be applied if the argument is annotated with
* {@code @javax.validation.Valid}.
*
* <p>When this handler is created with {@code annotationNotRequired=true},
* any non-simple type argument and return value is regarded as a model
* attribute with or without the presence of an {@code @ModelAttribute}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler { protected final Log logger = LogFactory.getLog(getClass()); private final boolean annotationNotRequired; /**
* @param annotationNotRequired if "true", non-simple method arguments and
* return values are considered model attributes with or without a
* {@code @ModelAttribute} annotation.
*/
public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
this.annotationNotRequired = annotationNotRequired;
} /**
* Returns {@code true} if the parameter is annotated with {@link ModelAttribute}
* or in default resolution mode, and also if it is not a simple type.
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
return true;
}
else if (this.annotationNotRequired) {
return !BeanUtils.isSimpleProperty(parameter.getParameterType());
}
else {
return false;
}
} /**
* Resolve the argument from the model or if not found instantiate it with
* its default if it is available. The model attribute is then populated
* with request values via data binding and optionally validated
* if {@code @java.validation.Valid} is present on the argument.
* @throws BindException if data binding and validation result in an error
* and the next method parameter is not of type {@link Errors}.
* @throws Exception if WebDataBinder initialization fails.
*/
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String name = ModelFactory.getNameForParameter(parameter);
Object attribute = (mavContainer.containsAttribute(name) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest)); WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
binder.setFieldDefaultPrefix(parameter.getParameterName() + "!");
bindRequestParameters(binder, webRequest);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
} // Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel); return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
} /**
* Extension point to create the model attribute if not found in the model.
* The default implementation uses the default constructor.
* @param attributeName the name of the attribute (never {@code null})
* @param methodParam the method parameter
* @param binderFactory for creating WebDataBinder instance
* @param request the current request
* @return the created model attribute (never {@code null})
*/
protected Object createAttribute(String attributeName, MethodParameter methodParam,
WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { return BeanUtils.instantiateClass(methodParam.getParameterType());
} /**
* Extension point to bind the request to the target object.
* @param binder the data binder instance to use for the binding
* @param request the current request
*/
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
((WebRequestDataBinder) binder).bind(request);
} /**
* Validate the model attribute if applicable.
* <p>The default implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param binder the DataBinder to be used
* @param methodParam the method parameter
*/
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
Annotation[] annotations = methodParam.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
break;
}
}
} /**
* Whether to raise a fatal bind exception on validation errors.
* @param binder the data binder used to perform data binding
* @param methodParam the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}
*/
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
int i = methodParam.getParameterIndex();
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
} /**
* Return {@code true} if there is a method-level {@code @ModelAttribute}
* or if it is a non-simple type when {@code annotationNotRequired=true}.
*/
@Override
public boolean supportsReturnType(MethodParameter returnType) {
if (returnType.getMethodAnnotation(ModelAttribute.class) != null) {
return true;
}
else if (this.annotationNotRequired) {
return !BeanUtils.isSimpleProperty(returnType.getParameterType());
}
else {
return false;
}
} /**
* Add non-null return values to the {@link ModelAndViewContainer}.
*/
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue != null) {
String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
mavContainer.addAttribute(name, returnValue);
}
} }

增加的就是第110行

1 binder.setFieldDefaultPrefix(parameter.getParameterName() + "!");

parameter.getParameterName()返回的是你@Controller里2RequestMapping方法参数的名字

"!"是我区分成员域与对象名的分解符...这个可以自己设置.你想设置成.也可以!也可以#也OK

只要自己能区分就行了.

测试

URL:

 http://localhost:8080/quick-start/test18?context!stateCode=91&a!name=hehe&context!exception.message=error&a!stateCode=84

后台打印参数:

 com.labofjet.web.ContextDTO@9344568[stateCode=91,data=<null>,exception=com.labofjet.exception.BaseException: error]
com.labofjet.dto.ADTO@814d736[id=<null>,name=hehe,age=<null>,value=<null>,b=0,stateCode=84]

Controller的方法签名:

     @RequestMapping("/test18")
public Object index18(ContextDTO context, ADTO a) throws IOException {
System.out.println(context);
System.out.println(a);
return null;
}

原理

先简明说下原理..具体的理论我想后面等我整理下思路,介绍RequestMappingHandlerAdapter的时候再讲(反正没人看...)

SpringMVC里@Controller里的各种参数由各种argumentsResolver来解析.

解析自定义的这种DTO的argumentsResolver是ServletModelAttributeMethodProcessor这个类.

收到参数以后他怎么解析呢?

很简单呀.在我测试例子中,比如我要初始化一个context对象,SpringMVC就先把context包装成BeanWrapperImpl对象..然后把Request里的各种key-value包装成MutablePropertyValues..然后set进去就可以了.利用的是反射的知识,你有一个key,那我就看BeanWrapperImpl warp的那个对象有没有对应的属性.有就设置进去.

既然原理是这样,那怎么达到我们的目的呢?

这里又有至少2种方法:

第一种就是像我那样: binder.setFieldDefaultPrefix(parameter.getParameterName() + "!"); 这样在解析MutablePropertyValues的时候会去掉你set进去的Feild的Prefix.

第二种方法: ServletRequestDataBinder在绑定的参数的时候需要先把request转化成MutablePropertyValues,做法是:

     public void bind(ServletRequest request) {
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
addBindValues(mpvs, request);
doBind(mpvs);
}

也就是说:

 MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);

而MutablePropertyValues 其实还有一种构造方法:

     public ServletRequestParameterPropertyValues(ServletRequest request, String prefix) {
this(request, prefix, DEFAULT_PREFIX_SEPARATOR);
}

这种方法允许你填入一个prefix...那原理和前面是一样的..

但是这种方法需要改很多类...所以没有方法一简单....

为什么不使用继承

可能会有朋友想问.改进的ArgumentResolver和原本Spring自带的基本一样,只是多了一步,为什么不继承自原本的ServletModelAttributeMethodProcessor? 毕竟修改源码方案不太好.

原因有很多,主要有2个原因:

原因1:

ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor

resolveArgument在ModelAttributeMethodProcessor里的定义是:

public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
...........
}

是final啊!final!!!!!!!!!!!

所以我修改不了.

原因2:

一般父类定义了一个处理流程的话不能修改的话,会在子类给我们留一个扩展接口...

没错,那就是:

     @Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
servletBinder.bind(servletRequest);
}

这个是在ServletModelAttributeMethodProcessor里覆盖了父类的方法,我们可以继承ServletModelAttributeMethodProcessor再覆盖这个bindRequestParameters方法..

但是......

这里只有2个参数啊!!!!我们需要的@Controller的参数名称的信息不在这里....我们需要这个变量:

 public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
.............
}

参数的信息都在这里里面..可是bindRequestParameters方法里没有传入这个参数...所以坑爹的是我们在bindRequestParameters里并不能知道@Controller里参数的名字到底是什么...

所以我们没有办法设置一个通用的绑定方法...

方法:利用其它的HandlerMethodArgumentResolver

具体方法

不改源码最简单的方法可能是不自己写ArgumentResolver,而是利用SpringMVC原有的Resolver了吧..

我们可以利用RequestResponseBodyMethodProcessor这个ArgumentResolver..

具体请参考我的另外一篇文章:

http://www.cnblogs.com/abcwt112/p/5169250.html

原理就是利用HttpMessageConverter和其它的json转化工具将request里的参数转化成java bean.这也是很简单的.

基本只要在参数前加一个@RequestBody...

方法:利用@InitBinder注解

具体:

请大家看这篇文章:

http://jinnianshilongnian.iteye.com/blog/1888474

缺点:

1.原理和方法:改源码是差不多的....都是通过修改binder设置额外属性来达到目的的,但是没传入MethodParameter parameter,所以还是不知道你的@Controller里的参数名字..只能手动指定前缀

2.貌似要绑定的时候每个Controller里都要写@InitBinder,稍微有点麻烦..当然好处是更灵活...

方法N:自己实现HandlerMethodArgumentResolver

这个方法就太多了......

请参考:

http://jinnianshilongnian.iteye.com/blog/1717180

简单总结

方法有太多太多了..不同方法可能适合不同场景,但是我觉得最简单的还是@InitBinder和@RequestBody这2种方案.

SpringMVC学习记录3的更多相关文章

  1. springMVC学习记录1-使用XML进行配置

    SpringMVC是整个spring中的一个很小的组成,准确的说他是spring WEB这个模块的下一个子模块,Spring WEB中除了有springMVC还有struts2,webWork等MVC ...

  2. SpringMVC学习记录5

    Springmvc流程中的扩展点有很多,可以在很多地方插入自己的代码逻辑达到控制流程的目的. 如果要对Controller的handler方法做统一的处理.我想应该会有很多选择,比如:@ModelAt ...

  3. SpringMVC学习记录4

    主题 SpringMVC有很多很多的注解.其中有2个注解@SessionAttributes @ModelAttribute我平时一般不用,因为实在是太灵活了.但是又有一定限制,用不好容易错.. 最近 ...

  4. SpringMVC学习记录2

    废话 最近在看SpringMVC...里面东西好多...反正东看一点西看一点吧... 分享一下最近的一些心得..是关于DispatcherServlet的 DispatcherServlet与Cont ...

  5. SpringMVC学习记录

    1E)Spring MVC框架 ①Jar包结构: docs+libs+schema. 版本区别:核心包,源码包. SpringMVC文档学习: 学习三步骤: 1)是什么? 开源框架 2)做什么? IO ...

  6. springMVC学习记录2-使用注解配置

    前面说了一下使用xml配置springmvc,下面再说说注解配置.项目如下: 业务很简单,主页和输入用户名和密码进行登陆的页面. 看一下springmvc的配置文件: <?xml version ...

  7. SpringMVC学习记录1

    起因 以前大三暑假实习的时候看到公司用SpringMVC而不是Struts2,老司机告诉我SpringMVC各种方便,各种解耦. 然后我自己试了试..好像是蛮方便的.... 基本上在Spring的基础 ...

  8. springMVC学习记录3-拦截器和文件上传

    拦截器和文件上传算是springmvc中比较高级一点的内容了吧,让我们一起看一下. 下面先说说拦截器.拦截器和过滤器有点像,都可以在请求被处理之前和请求被处理之到做一些额外的操作. 1. 实现Hand ...

  9. SpringMVC学习记录七——sjon数据交互和拦截器

    21       json数据交互 21.1      为什么要进行json数据交互 json数据格式在接口调用中.html页面中较常用,json格式比较简单,解析还比较方便. 比如:webservi ...

随机推荐

  1. 网页日历显示控件calendar3.1

    关于日历控件,我做了好多次尝试,一直致力于开发一款简单易用的日历控件.我的想法是争取在引用这个控件后,用一行js代码就能做出一个日历,若在加点参数,就能自定义外观和功能丰富多彩的日历.Calendar ...

  2. LDR、STR指令

    LDR(load register)指令将内存内容加载入通用寄存器 STR(store register)指令将寄存器内容存入内存空间中 #define GPJ0CON 0xE0200240 _sta ...

  3. BZOJ 1901: Zju2112 Dynamic Rankings[带修改的主席树]【学习笔记】

    1901: Zju2112 Dynamic Rankings Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 7143  Solved: 2968[Su ...

  4. 基于pcDuino-V2的无线视频智能小车 - UBUNTU系统上的gtk编程

    详细的代码已经上传到git网站:https://github.com/qq2216691777/pcduino_smartcar

  5. python3条件控制if

    Python条件语句是通过一条或多条语句的执行结果(为真或假)来决定执行哪部分代码. if语句 if语句的一般形式如下: if 条件1: 语句1 elif 条件2: 语句2 else: 语句3 其意思 ...

  6. jmeter(十)参数化

    jmeter可以用来做接口.性能测试,原理是模拟客户端向服务器发送请求,请求里面包含两种不同情况的参数,一种是包含在URL中,一种是请求中需要发送的参数. 包含在URL中的参数,例如:http://b ...

  7. 011商城项目:图片服务器的安装---nginx

    这个是电商的项目,不是传统项目,所以给图片单独架一台服务器. 我们看上图: 用户上传图片时上传到Tomcat1或者Tomcat2.然后Tomcat1和Tomcat2通过FTP服务把图片上传到图片服务器 ...

  8. Combinations

    Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. For exampl ...

  9. [LeetCode] House Robber III 打家劫舍之三

    The thief has found himself a new place for his thievery again. There is only one entrance to this a ...

  10. redis学习笔记

    Redis 命令 Redis 命令用于在 redis 服务上执行操作. 要在 redis 服务上执行命令需要一个 redis 客户端.Redis 客户端在我们之前下载的的 redis 的安装包中. 语 ...