springMVC使用HandlerMethodArgumentResolver 自定义解析器实现请求参数绑定方法参数

时间:2021-12-16 09:47:54

springMVC 数据绑定 多个对象 如何准确绑定?

遇到的问题:
我有Person和Cat两个类,他们都有name这个field,如果我有一个Controller的方法接收Cat和Person两个参数,我应该如何分别他们的name?
话说在页面写person.name和cat.name是没什么意义的,于是我看了一下*。
有人推荐我写一个类,并给这个类里增加Person和Cat类型的变量,这样就可以在页面里写person.name和cat.name以作区分了。
虽然解决问题,但并不高雅。
于是我找到了另一个方法——给method参数加annotation,使用HandlerMethodArgumentResolver。

SpringMVC3.1引入了HandlerMethodArgumentResolver接口,spring调用该接口实现Controller的参数装配。HandlerMethodArgumentResolver实现类中会调用DataBinder,Converter等。

常用的该接口实现类有:

ServletModelAttributeMethodProcessor:实体类的组装用它实现。
RequestParamMethodArgumentResolver:基本数据类型如String用它实现。

在我学习过程中,发现对List类型的参数SpringMVC没有提供默认实现。我参照Spring的示例 通过实现 HandlerMethodArgumentResolver接口,实现对List参数的组装

那么在说HandlerMethodArgumentResolver接口实现之前,要先说一个类:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

/**
* An {@link AbstractHandlerMethodAdapter} that supports {@link HandlerMethod}s
* with the signature -- method argument and return types, defined in
* {@code @RequestMapping}.
*
* <p>Support for custom argument and return value types can be added via
* {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
* Or alternatively to re-configure all argument and return value types use
* {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
*
* @author Rossen Stoyanchev
* @since 3.1
* @see HandlerMethodArgumentResolver
* @see HandlerMethodReturnValueHandler
*/

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware,InitializingBean

看了说明大致明白,他可以用来定制method参数和返回值。
我这里需要修改的是参数,也就是需要用他的setCustomArgumentResolvers方法。

/**
* Provide resolvers for custom argument types. Custom resolvers are ordered
* after built-in ones. To override the built-in support for argument
* resolution use {@link #setArgumentResolvers} instead.
*/

public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.customArgumentResolvers = argumentResolvers;
}

参数List类型的泛型是HandlerMethodArgumentResolver,我需要有一个类去实现他。
实现类:

package ;

import java.lang.annotation.Annotation;
import java.util.Map;

import javax.servlet.ServletRequest;

import org.apache.commons.lang.StringUtils;
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.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
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 com.maigangle.erp.common.annotation.FormModel;

/**
* 解析obj.xx
*
*
*
* @version 1.0
*
*/

public class FormObjectMethodArgumentsResolver implements HandlerMethodArgumentResolver {

public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(FormModel.class);
}

public Object resolveArgument( MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
FormModel multi = parameter.getParameterAnnotation(FormModel.class);
String name = StringUtils.isEmpty(multi.value()) ? parameter.getParameterName() : multi.value();// 参数名:默认去@Multi的value值
// 如果是""则去获取参数值的命名变量
// String name = ModelFactory.getNameForParameter(parameter);
Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name)
: createAttribute(name, parameter, binderFactory, webRequest));

if (!mavContainer.isBindingDisabled(name)) {
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null && !ann.binding()) {
mavContainer.setBindingDisabled(name);
}
}

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest, name);
}
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);

}

protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request, String parameterName) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
// servletBinder.setFieldDefaultPrefix(servletBinder.getObjectName()+".");
servletBinder.setFieldDefaultPrefix(parameterName + ".");
servletBinder.bind(servletRequest);
}

protected Object createAttribute(String attributeName, MethodParameter methodParam, WebDataBinderFactory binderFactory, NativeWebRequest request)
throws Exception {

return BeanUtils.instantiateClass(methodParam.getParameterType());
}

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;
}
}
}

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;
}
}

supportsParameter里判断参数是否满足我的Resolver。
而在resolveArgument里我们可以处理这些参数。
另外,FormModel就是我创建的annotation。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
*
*
*
* @version 1.0
**/

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FormModel {
String value();
}

另外,我需要将他配置到spring context中。

    <!--自定义controller接受参数进行解析 -->
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean
class="com.common.util.FormObjectMethodArgumentsResolver" />

</mvc:argument-resolvers>
</mvc:annotation-driven>

或者中配置的方式:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="synchronizeOnSession" value="true" />
<property name="customArgumentResolvers">
<list>
<bean class="com.common.util.FormObjectMethodArgumentsResolver" />
</list>
</property>
</bean>

剩下的就是去使用他,controller中:

@RequestMapping(value="/index")
public ModelAndView index(@FormModel("p")Person p){
ModelAndView tmpMAV = new ModelAndView("index");
System.out.println(p);
tmpMAV.addObject("p.name",p.getName());
return tmpMAV;
}

页面中:

<form action="index" method="post">
<input type="text" name="p.name" />
<input type="text" name="name" />
<input type="text" name="p.age" />
<input type="text" name="weight" />
<input type="text" name="p.height" />
<input type="submit" value="提交"/>
</form>

参考:http://blog.csdn.net/truong/article/details/30971317

问题点:

1.为什么开始要讲RequestMappingHandlerAdapter

DefaultAnnotationHandlerMapping和 AnnotationMethodHandlerAdapter是spring mvc 支持类注解和方法级别注解的两个处理类
DefaultAnnotationHandlerMapping用来解析Spring MVC里面的annotation对应的Controller,也就是通过这个类,给annotation设置映射关系,如@RequestMapping等
AnnotationMethodHandlerAdapter
对Annotation设置的方法进行处理的类,通过此类,解析annotation设置的类的处理,也就是有请求时,通过此类,可以调用annotation设置controller的方法,主要处理方法
一般配置为:

<!-- 处理在类级别上的@RequestMapping注解 -->
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">

<property name="interceptors">
<list>
<!-- 多个拦截器,顺序执行 -->
<ref bean="SpringMVCInterceptor" />
<ref bean="OpenSessionInViewInterceptor"/>
</list>
</property>
</bean>
<!-- 处理方法级别上的@RequestMapping注解 -->
<bean id="annotationMethodHandlerAdapter"
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">

<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">

<property name="supportedMediaTypes">
<list>
<value>text/html;charset=utf-8</value>
<value>text/plain;charset=utf-8</value>
</list>
</property>
</bean>
<bean
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverterv2">

<property name="objectMapper">
<bean class="net.pm.misc.Hibernate4AwareObjectMapper" />
</property>
</bean>
</list>
</property>
</bean>

<!-- 表示使用cglib,而非JDK的动态代理,因为Controller没有实现接口,所以要配置这里 -->
<aop:aspectj-autoproxy proxy-target-class="true" />

xml配置中annotation-driven和DefaultAnnotationHandlerMapping/AnnotationMethodHandlerAdapter的关系

Spring 3.0.x中使用了annotation-driven后,缺省使用DefaultAnnotationHandlerMapping 来注册handler method和request的mapping关系。 AnnotationMethodHandlerAdapter来在实际调用handlermethod前对其参数进行处理。

在spring mvc 3.1中,对应变更为
DefaultAnnotationHandlerMapping -> RequestMappingHandlerMapping
AnnotationMethodHandlerAdapter -> RequestMappingHandlerAdapter
AnnotationMethodHandlerExceptionResolver -> ExceptionHandlerExceptionResolver

以上都在使用了annotation-driven后自动注册。
而且对应分别提供了AbstractHandlerMethodMapping , AbstractHandlerMethodAdapter和 AbstractHandlerMethodExceptionResolver以便于让用户更方便的实现自定义的实现类。

相当于注册了DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter两个bean,配置一些messageconverter。即解决了@Controller注解的使用前提配置。
这两个功能相似,不能一起使用
参考:http://starscream.iteye.com/blog/1098880

2.FormModel类上的注解都是什么意思
@Target,@Retention,@Documented详解
要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法。

1、元注解(meta-annotation):

  元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
    1.@Target,
    2.@Retention,
    3.@Documented,
    4.@Inherited
  这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。
 @Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

  作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

  取值(ElementType)有:

    1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention:

  @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

  作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

  取值(RetentionPoicy)有:

    1.SOURCE:在源文件中有效(即源文件保留)
    2.CLASS:在class文件中有效(即class保留)
    3.RUNTIME:在运行时有效(即运行时保留)
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

详情参考:http://www.cnblogs.com/gmq-sh/p/4798194.html