1.DispatcherServlet源码分析

1.@InitBinder(续)

1.DataBinder概述

package org.springframework.validation;

此类所在的包是org.springframework.validation,所以可想而知,它不仅仅完成数据的绑定,还会和数据校验有关~

  1. DataBinder使用Demo

    public static void main(String[] args) throws BindException {// 创建对象User user = new User();// 对象绑定到一起DataBinder binder = new DataBinder(user, "user");//添加数据MutablePropertyValues pvs = new MutablePropertyValues();pvs.add("id", 9);pvs.add("username", "小明");pvs.add("password", "123456");// 进行自动装配binder.bind(pvs);//这个时候就已经进行了注入System.out.println(user);
    }
    
  2. 源码分析

    属性字段:

    /** 用于绑定的默认对象名称: "target" */
    public static final String DEFAULT_OBJECT_NAME = "target";/** 数组和集合增长的默认限制: 256 */
    public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;/*** 我们将创建许多DataBinder实例:让我们使用一个静态日志程序。*/
    protected static final Log logger = LogFactory.getLog(DataBinder.class);
    // 目标对象
    @Nullable
    private final Object target;
    // 对象名称
    private final String objectName;        //默认值是target// BindingResult:绑定错误、失败的时候会放进这里来~
    @Nullable
    private AbstractPropertyBindingResult bindingResult;// 类型转换器,会注册最为常用的那么多类型转换Map<Class<?>, PropertyEditor> defaultEditors
    @Nullable
    private SimpleTypeConverter typeConverter;// 默认忽略不能识别的字段~
    private boolean ignoreUnknownFields = true;// 不能忽略非法的字段(比如我要Integer,你给传aaa,那肯定就不让绑定了,抛错)
    private boolean ignoreInvalidFields = false;// 默认是支持级联的~~~
    private boolean autoGrowNestedPaths = true;private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;// 这三个参数  都可以自己指定~~ 允许的字段、不允许的、必须的
    @Nullable
    private String[] allowedFields;
    @Nullable
    private String[] disallowedFields;
    @Nullable
    private String[] requiredFields;// 转换器ConversionService
    @Nullable
    private ConversionService conversionService;
    // 状态码处理器~
    @Nullable
    private MessageCodesResolver messageCodesResolver;
    // 绑定出现错误的处理器~
    private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
    // 校验器(这个非常重要)
    private final List<Validator> validators = new ArrayList<>();
    //  objectName没有指定,就用默认的
    public DataBinder(@Nullable Object target) {this(target, DEFAULT_OBJECT_NAME);
    }
    // 这个没什么可说的就是创建对象
    public DataBinder(@Nullable Object target, String objectName) {this.target = ObjectUtils.unwrapOptional(target);this.objectName = objectName;
    }
    
  3. 重要的方法

    //提供一些列的初始化方法,供给子类使用 或者外部使用  下面两个初始化方法是互斥的
    public void initBeanPropertyAccess() {this.bindingResult = createBeanPropertyBindingResult();
    }
    //使用标准JavaBean属性访问创建实例。
    protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(),getObjectName(),isAutoGrowNestedPaths(),getAutoGrowCollectionLimit());if (this.conversionService != null) {result.initConversion(this.conversionService);}if (this.messageCodesResolver != null) {result.setMessageCodesResolver(this.messageCodesResolver);}return result;
    }// 你会发现,初始化DirectFieldAccess的时候,校验的也是bindingResult ~~~~
    public void initDirectFieldAccess() {...this.bindingResult = createDirectFieldBindingResult();
    }protected AbstractPropertyBindingResult createDirectFieldBindingResult() {DirectFieldBindingResult result = new DirectFieldBindingResult(getTarget(),getObjectName(),                                                             isAutoGrowNestedPaths());if (this.conversionService != null) {result.initConversion(this.conversionService);}if (this.messageCodesResolver != null) {result.setMessageCodesResolver(this.messageCodesResolver);}return result;
    }// 把属性访问器返回,PropertyAccessor(默认直接从结果里拿),子类MapDataBinder有复写
    protected ConfigurablePropertyAccessor getPropertyAccessor() {return getInternalBindingResult().getPropertyAccessor();
    }// 可以看到简单的转换器也是使用到了conversionService的,可见conversionService它的效用
    protected SimpleTypeConverter getSimpleTypeConverter() {if (this.typeConverter == null) {this.typeConverter = new SimpleTypeConverter();if (this.conversionService != null) {this.typeConverter.setConversionService(this.conversionService);}}return this.typeConverter;
    }
    // 设置指定的可以绑定的字段,默认是所有字段~~~
    // 例如,在绑定HTTP请求参数时,限制这一点以避免恶意用户进行不必要的修改。
    // 简单的说:我可以控制只有指定的一些属性才允许你修改~~~~
    // 注意:它支持xxx*,*xxx,*xxx*这样的通配符  支持[]这样子来写~
    public void setAllowedFields(@Nullable String... allowedFields) {this.allowedFields = PropertyAccessorUtils.canonicalPropertyNames(allowedFields);
    }
    public void setDisallowedFields(@Nullable String... disallowedFields) {this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
    }// 注册每个绑定进程所必须的字段。
    public void setRequiredFields(@Nullable String... requiredFields) {this.requiredFields=PropertyAccessorUtils.canonicalPropertyNames(requiredFields);...
    }
    ...
    // 注意:这个是set方法,后面是有add方法的~
    // 注意:虽然是set,但是引用是木有变的~~~~
    public void setValidator(@Nullable Validator validator) {// 判断逻辑在下面:你的validator至少得支持这种类型呀  哈哈assertValidators(validator);// 因为自己手动设置了,所以先清空  再加进来~~~// 这步你会发现,即使validator是null,也是会clear的哦~  符合语意this.validators.clear();if (validator != null) {this.validators.add(validator);}
    }
    private void assertValidators(Validator... validators) {Object target = getTarget();for (Validator validator : validators) {if (validator != null && (target != null && !validator.supports(target.getClass()))) {...抛出异常}}
    }
    public void addValidators(Validator... validators) {assertValidators(validators);this.validators.addAll(Arrays.asList(validators));
    }
    // 效果同set
    public void replaceValidators(Validator... validators) {assertValidators(validators);this.validators.clear();this.validators.addAll(Arrays.asList(validators));
    }// 返回一个,也就是primary默认的校验器
    @Nullable
    public Validator getValidator() {return (!this.validators.isEmpty() ? this.validators.get(0) : null);
    }
    // 只读视图
    public List<Validator> getValidators() {return Collections.unmodifiableList(this.validators);
    }// since Spring 3.0
    public void setConversionService(@Nullable ConversionService conversionService) {this.conversionService = conversionService;if (this.bindingResult != null && conversionService != null) {this.bindingResult.initConversion(conversionService);}
    }// 下面它提供了非常多的addCustomFormatter()方法  注册进PropertyEditorRegistry里
    public void addCustomFormatter(Formatter<?> formatter);
    public void addCustomFormatter(Formatter<?> formatter, String... fields);
    public void addCustomFormatter(Formatter<?> formatter, Class<?>... fieldTypes);// 实现接口方法
    public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
    public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String field, PropertyEditor propertyEditor);
    ...
    // 实现接口方法
    // 统一委托给持有的TypeConverter~~或者是getInternalBindingResult().getPropertyAccessor();这里面的
    @Override
    @Nullable
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable MethodParameter methodParam) {return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
    }// ===========上面的方法都是开胃小菜,下面才是本类最重要的方法==============// 该方法就是把提供的属性值们,绑定到目标对象target里去~~~
    public void bind(PropertyValues pvs) {MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?(MutablePropertyValues) pvs : new MutablePropertyValues(pvs));doBind(mpvs);
    }
    // 此方法是protected的,子类WebDataBinder有复写~~~加强了一下
    protected void doBind(MutablePropertyValues mpvs) {// 前面两个check就不解释了,重点看看applyPropertyValues(mpvs)这个方法~checkAllowedFields(mpvs);checkRequiredFields(mpvs);applyPropertyValues(mpvs);
    }// allowe允许的 并且还是没有在disallowed里面的 这个字段就是被允许的
    protected boolean isAllowed(String field) {String[] allowed = getAllowedFields();String[] disallowed = getDisallowedFields();return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&(ObjectUtils.isEmpty(disallowed) ||!PatternMatchUtils.simpleMatch(disallowed, field)));
    }
    ...
    // protected 方法,给target赋值~~~~
    protected void applyPropertyValues(MutablePropertyValues mpvs) {try {// 可以看到最终赋值 是委托给PropertyAccessor去完成的getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(),isIgnoreInvalidFields());// 抛出异常,交给BindingErrorProcessor一个个处理~~~} catch (PropertyBatchUpdateException ex) {for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());}}
    }// 执行校验,此处就和BindingResult 关联上了,校验失败的消息都会放进去(不是直接抛出异常哦~ )
    public void validate() {Object target = getTarget();Assert.state(target != null, "No target to validate");BindingResult bindingResult = getBindingResult();// 每个Validator都会执行~~~~for (Validator validator : getValidators()) {validator.validate(target, bindingResult);}
    }// 带有校验提示的校验器。SmartValidator
    // @since 3.1
    public void validate(Object... validationHints) { ... }// 这一步也挺有意思:实际上就是若有错误,就抛出异常
    // 若没错误  就把绑定的Model返回~~~(可以看到BindingResult里也能拿到最终值哦~~~)
    // 此方法可以调用,但一般较少使用~
    public Map<?, ?> close() throws BindException {if (getBindingResult().hasErrors()) {throw new BindException(getBindingResult());}return getBindingResult().getModel();
    }
    

    从源源码的分析中,大概能总结到DataBinder它提供了如下能力:

    1. 把属性值PropertyValues绑定到target上(bind()方法,依赖于PropertyAccessor实现~)
    2. 提供校验的能力:提供了public方法validate()对各个属性使用Validator执行校验~
    3. 提供了注册属性编辑器(PropertyEditor)和对类型进行转换的能力(TypeConverter)

    还需要注意的是:

    initBeanPropertyAccess和initDirectFieldAccess两个初始化PropertyAccessor方法是互斥的

    1. initBeanPropertyAccess()创建的是BeanPropertyBindingResult,内部依赖BeanWrapper
    2. initDirectFieldAccess创建的是DirectFieldBindingResult,内部依赖DirectFieldAccessor
      这两个方法内部没有显示的调用,但是Spring内部默认使用的是initBeanPropertyAccess(),具体可以参照getInternalBindingResult()方法~

    它是Spring提供的能力而非web提供的~

2.WebDataBinder概述

它的作用就是从web request里(**注意:这里指的web请求,并不一定就是ServletRequest请求哟**)把web请求的`parameters`绑定到`JavaBean`上

Controller方法的参数类型可以是基本类型,也可以是封装后的普通Java类型。若这个普通Java类型没有声明任何注解,则意味着它的每一个属性都需要到Request中去查找对应的请求参数。

public class WebDataBinder extends DataBinder {// 此字段意思是:字段标记  比如name -> _name// 这对于HTML复选框和选择选项特别有用。public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";// !符号是处理默认值的,提供一个默认值代替空值~~~public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";@Nullableprivate String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;@Nullableprivate String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;// 默认也会绑定空的文件流~private boolean bindEmptyMultipartFiles = true;// 完全沿用父类的两个构造~~~public WebDataBinder(@Nullable Object target) {super(target);}public WebDataBinder(@Nullable Object target, String objectName) {super(target, objectName);}... //  省略get/set// 在父类的基础上,增加了对_和!的处理~~~@Overrideprotected void doBind(MutablePropertyValues mpvs) {checkFieldDefaults(mpvs);checkFieldMarkers(mpvs);super.doBind(mpvs);}protected void checkFieldDefaults(MutablePropertyValues mpvs) {String fieldDefaultPrefix = getFieldDefaultPrefix();if (fieldDefaultPrefix != null) {PropertyValue[] pvArray = mpvs.getPropertyValues();for (PropertyValue pv : pvArray) {// 若你给定的PropertyValue的属性名确实是以!打头的  那就做处理如下:// 如果JavaBean的该属性可写 && mpvs不存在去掉!后的同名属性,那就添加进来表示后续可以                // 使用了(毕竟是默认值,没有精确匹配的高的)// 然后把带!的给移除掉(因为默认值以已经转正了~~~)// 其实这里就是说你可以使用!来给个默认值。比如!name表示若找不到name这个属性的时,就取              // 它的值~~~// 也就是说你request里若有穿!name保底,也就不怕出现null值啦~if (pv.getName().startsWith(fieldDefaultPrefix)) {String field = pv.getName().substring(fieldDefaultPrefix.length());if (getPropertyAccessor().isWritableProperty(field) &&!mpvs.contains(field)) {mpvs.add(field, pv.getValue());}mpvs.removePropertyValue(pv);}}}}// 处理_的步骤// 若传入的字段以_打头// JavaBean的这个属性可写 && mpvs木有去掉_后的属性名字// getEmptyValue(field, fieldType)就是根据Type类型给定默认值。// 比如Boolean类型默认给false,数组给空数组[],集合给空集合,Map给空map  可以参考此类:         // CollectionFactory// 当然,这一切都是建立在你传的属性值是以_打头的基础上的,Spring才会默认帮你处理这些默认值protected void checkFieldMarkers(MutablePropertyValues mpvs) {String fieldMarkerPrefix = getFieldMarkerPrefix();if (fieldMarkerPrefix != null) {PropertyValue[] pvArray = mpvs.getPropertyValues();for (PropertyValue pv : pvArray) {if (pv.getName().startsWith(fieldMarkerPrefix)) {String field = pv.getName().substring(fieldMarkerPrefix.length());if (getPropertyAccessor().isWritableProperty(field) &&!mpvs.contains(field)) {Class<?> fieldType=getPropertyAccessor().getPropertyType(field);mpvs.add(field, getEmptyValue(field, fieldType));}mpvs.removePropertyValue(pv);}}}}// @since 5.0@Nullablepublic Object getEmptyValue(Class<?> fieldType) {try {if (boolean.class == fieldType || Boolean.class == fieldType) {// Special handling of boolean property.return Boolean.FALSE;} else if (fieldType.isArray()) {// Special handling of array property.return Array.newInstance(fieldType.getComponentType(), 0);} else if (Collection.class.isAssignableFrom(fieldType)) {return CollectionFactory.createCollection(fieldType, 0);} else if (Map.class.isAssignableFrom(fieldType)) {return CollectionFactory.createMap(fieldType, 0);}} catch (IllegalArgumentException ex) {...异常信息}}// 若不在这几大类型内,就返回默认值null呗~~~// 但需要说明的是,若你是简单类型比如int,// Default value: null. return null;}// 单独提供的方法,用于绑定org.springframework.web.multipart.MultipartFile类型的数据到        // JavaBean属性上~// 显然默认是允许MultipartFile作为Bean一个属性  参与绑定的// Map<String, List<MultipartFile>>它的key,一般来说就是文件们啦~protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles,MutablePropertyValues mpvs) {multipartFiles.forEach((key, values) -> {if (values.size() == 1) {MultipartFile value = values.get(0);if (isBindEmptyMultipartFiles() || !value.isEmpty()) {mpvs.add(key, value);}}else {mpvs.add(key, values);}});}
}

单从WebDataBinder来说,它对父类进行了增强,提供的增强能力如下:

  1. 支持对属性名以_打头的默认值处理(自动挡,能够自动处理所有的Bool、Collection、Map等)
  2. 支持对属性名以!打头的默认值处理(手动档,需要手动给某个属性赋默认值,自己控制的灵活性很高)
  3. 提供方法,支持把MultipartFile绑定到JavaBean的属性上~

3.ServletRequestDataBinder概述

前面说了这么多,亲有没有发现还木有聊到过我们最为常见的Web场景API:javax.servlet.ServletRequest。本类从命名上就知道,它就是为此而生。

它的目标就是:data binding from servlet request parameters to JavaBeans, including support for multipart files.从Servlet Request里把参数绑定到JavaBean里,支持multipart。

备注:到此类为止就已经把web请求限定为了Servlet Request,和Servlet规范强绑定了。

public class ServletRequestDataBinder extends WebDataBinder {... 沿用父类构造// 注意这个可不是父类的方法,是本类增强的~~~~意思就是kv都从request里来~~当然内部还是适配成了一       // 个MutablePropertyValuespublic void bind(ServletRequest request) {// 内部最核心方法是它:WebUtils.getParametersStartingWith()  把request参数转换成一个Map// request.getParameterNames()MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);MultipartRequest multipartRequest = WebUtils.getNativeRequest(request,MultipartRequest.class);// 调用父类的bindMultipart方法,把MultipartFile都放进MutablePropertyValues里去~~~if (multipartRequest != null) {bindMultipart(multipartRequest.getMultiFileMap(), mpvs);}// 这个方法是本类流出来的一个扩展点~~~子类可以复写此方法自己往里继续添加// 比如ExtendedServletRequestDataBinder它就复写了这个方法,进行了增强(下面会说)  支持到         // 了uriTemplateVariables的绑定addBindValues(mpvs, request);doBind(mpvs);}// 这个方法和父类的close方法类似,很少直接调用public void closeNoCatch() throws ServletRequestBindingException {if (getBindingResult().hasErrors()) {...(抛出异常信息)}}
}

下面就以MockHttpServletRequest为例作为Web 请求实体,演示一个使用的小Demo。说明:MockHttpServletRequest它是HttpServletRequest的实现类~

1.Demo示例

public static void main(String[] args) {Person person = new Person();ServletRequestDataBinder binder = new ServletRequestDataBinder(person, "person");// 构造参数,此处就不用MutablePropertyValues,以HttpServletRequest的实现类                   // MockHttpServletRequest为例吧MockHttpServletRequest request = new MockHttpServletRequest();// 模拟请求参数request.addParameter("name", "fsx");request.addParameter("age", "18");// flag不仅仅可以用true/false  用0和1也是可以的?request.addParameter("flag", "1");// 设置多值的request.addParameter("list", "4", "2", "3", "1");// 给map赋值(Json串)// request.addParameter("map", "{'key1':'value1','key2':'value2'}"); // 这样可不行request.addParameter("map['key1']", "value1");request.addParameter("map['key2']", "value2"); 一次性设置多个值(传入Map)//request.setParameters(new HashMap<String, Object>() {{//    put("name", "fsx");//    put("age", "18");//}});binder.bind(request);System.out.println(person);}

2.ExtendedServletRequestDataBinder

此类代码不多但也不容小觑,它是对ServletRequestDataBinder的一个增强,它用于把URI template variables参数添加进来用于绑定。它会去从request的HandlerMapping.class.getName() + ".uriTemplateVariables";这个属性里查找到值出来用于绑定~~~

比如我们熟悉的@PathVariable它就和这相关:它负责把参数从url模版中解析出来,然后放在attr上,最后交给ExtendedServletRequestDataBinder进行绑定~~~

介于此:我觉得它还有一个作用,就是定制我们全局属性变量用于绑定~

向此属性放置值的地方是:AbstractUrlHandlerMapping.lookupHandler() --> chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); --> preHandle()方法 -> exposeUriTemplateVariables(this.uriTemplateVariables, request); -> request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);

// @since 3.1
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {... // 沿用父类构造//本类的唯一方法@Override@SuppressWarnings("unchecked")protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {// 它的值是:HandlerMapping.class.getName() + ".uriTemplateVariables";String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;// 注意:此处是attr,而不是parameterMap<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);if (uriVars != null) {uriVars.forEach((name, value) -> {// 若已经存在确切的key了,不会覆盖~~~~if (mpvs.contains(name)) {if (logger.isWarnEnabled()) {logger.warn("Skipping URI variable '" + name + "' because request contains bind value with same name.");}} else {mpvs.addPropertyValue(name, value);}});}}
}

可见,通过它我们亦可以很方便的做到在每个ServletRequest提供一份共用的模版属性们,供以绑定~

此类基本都沿用父类的功能,比较简单,此处就不写Demo了(Demo请参照父类)~

说明:ServletRequestDataBinder一般不会直接使用,而是使用更强的子类ExtendedServletRequestDataBinder

3.WebExchangeDataBinder

它是Spring5.0后提供的,对Reactive编程的Mono数据绑定提供支持,因此暂略~

4.MapDataBinder

它位于org.springframework.data.web是和Spring-Data相关,专门用于处理targetMap<String, Object>类型的目标对象的绑定,它并非一个public类~

它用的属性访问器是MapPropertyAccessor:一个继承自AbstractPropertyAccessor的私有静态内部类~(也支持到了SpEL哦)

5.WebRequestDataBinder

它是用于处理Spring自己定义的org.springframework.web.context.request.WebRequest的,旨在处理和容器无关的web请求数据绑定,有机会详述到这块的时候,再详细说~


如何注册自己的PropertyEditor来实现自定义类型数据绑定?

通过前面的分析我们知道了,数据绑定这一块最终会依托于PropertyEditor来实现具体属性值的转换(毕竟request传进来的都是字符串嘛~)

一般来说,像String, int, long会自动绑定到参数都是能够自动完成绑定的,因为前面有说,默认情况下Spring是给我们注册了N多个解析器的:

public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {@Nullableprivate Map<Class<?>, PropertyEditor> defaultEditors;private void createDefaultEditors() {this.defaultEditors = new HashMap<>(64);// Simple editors, without parameterization capabilities.// The JDK does not contain a default editor for any of these target types.this.defaultEditors.put(Charset.class, new CharsetEditor());this.defaultEditors.put(Class.class, new ClassEditor());...// Default instances of collection editors.// Can be overridden by registering custom instances of those as custom editors.this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));...// 这里就部全部枚举出来了}
}

虽然默认注册支持的Editor众多,但是依旧发现它并没有对Date类型、以及Jsr310提供的各种事件、日期类型的转换(当然也包括我们的自定义类型)。
因此我相信小伙伴都遇到过这样的痛点:Date、LocalDate等类型使用自动绑定老不方便了,并且还经常傻傻搞不清楚。所以最终很多都无奈选择了语义不是非常清晰的时间戳来传递

演示Date类型的数据绑定Demo:

@Getter
@Setter
@ToString
public class Person {public String name;public Integer age;// 以Date类型为示例private Date start;private Date end;private Date endTest;}public static void main(String[] args) {Person person = new Person();DataBinder binder = new DataBinder(person, "person");// 设置属性MutablePropertyValues pvs = new MutablePropertyValues();pvs.add("name", "fsx");// 事件类型绑定pvs.add("start", new Date());pvs.add("end", "2019-07-20");// 试用试用标准的事件日期字符串形式~pvs.add("endTest", "Sat Jul 20 11:00:22 CST 2019");binder.bind(pvs);System.out.println(person);
}

结果是符合我预期的:start有值,end没有,endTest却有值。
可能小伙伴对start、end都可以理解,最诧异的是endTest为何会有值呢???
此处我简单解释一下处理步骤:

  1. BeanWrapper调用setPropertyValue()给属性赋值,传入的value值都会交给convertForProperty()方法根据get方法的返回值类型进行转换~(比如此处为Date类型)

  2. 委托给this.typeConverterDelegate.convertIfNecessary进行类型转换(比如此处为string->Date类型)

  3. this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);找到一个合适的PropertyEditor(显然此处我们没有自定义Custom处理Date的PropertyEditor,返回null)

  4. 回退到使用ConversionService,显然此处我们也没有设置,返回null

  5. 回退到使用默认的editor = findDefaultEditor(requiredType);(注意:此处只根据类型去找了,因为上面说了默认不处理了Date,所以也是返回null)

  6. 最终的最终,回退到Spring对Array、Collection、Map的默认值处理问题,最终若是String类型,都会调用BeanUtils.instantiateClass(strCtor, convertedValue)也就是有参构造进行初始化~~~(请注意这必须是String类型才有的权利)

    所以本例中,到最后一步就相当于new Date("Sat Jul 20 11:00:22 CST 2019")因为该字符串是标准的时间日期串,所以是阔仪的,也就是endTest是能被正常赋值的~

通过这个简单的步骤分析,解释了为何end没值,endTest有值了。
其实通过回退到的最后一步处理,我们还可以对此做巧妙的应用。比如我给出如下的一个巧用例子:

@Getter
@Setter
@ToString
public class Person {private String name;// 备注:child是有有一个入参的构造器的private Child child;
}@Getter
@Setter
@ToString
public class Child {private String name;private Integer age;public Child() {}public Child(String name) {this.name = name;}
}public static void main(String[] args) {Person person = new Person();DataBinder binder = new DataBinder(person, "person");// 设置属性MutablePropertyValues pvs = new MutablePropertyValues();pvs.add("name", "fsx");// 给child赋值,其实也可以传一个字符串就行了 非常的方便   Spring会自动给我们new对象pvs.add("child", "fsx-son");binder.bind(pvs);System.out.println(person);
}
Person(name=fsx, child=Child(name=fsx-son, age=null))

废话不多说,下面我通过自定义属性编辑器的手段,来让能够支持处理上面我们传入2019-07-20这种非标准的时间字符串

我们知道DataBinder本身就是个PropertyEditorRegistry,因此我只需要自己注册一个自定义的PropertyEditor即可:

1、通过继承PropertyEditorSupport实现一个自己的处理Date的编辑器:

public class MyDatePropertyEditor extends PropertyEditorSupport {private static final String PATTERN = "yyyy-MM-dd";@Overridepublic String getAsText() {Date date = (Date) super.getValue();return new SimpleDateFormat(PATTERN).format(date);}@Overridepublic void setAsText(String text) throws IllegalArgumentException {try {super.setValue(new SimpleDateFormat(PATTERN).parse(text));} catch (ParseException e) {System.out.println("ParseException....................");}}
}

2、注册进DataBinder并运行

public static void main(String[] args) {Person person = new Person();DataBinder binder = new DataBinder(person, "person");binder.registerCustomEditor(Date.class, new MyDatePropertyEditor());//binder.registerCustomEditor(Date.class, "end", new MyDatePropertyEditor());// 设置属性MutablePropertyValues pvs = new MutablePropertyValues();pvs.add("name", "fsx");// 事件类型绑定pvs.add("start", new Date());pvs.add("end", "2019-07-20");// 试用试用标准的事件日期字符串形式~pvs.add("endTest", "Sat Jul 20 11:00:22 CST 2019");binder.bind(pvs);System.out.println(person);
}
ParseException....................
Person(name=fsx, age=null, start=Sat Jul 20 11:41:49 CST 2019, end=Sat Jul 20 00:00:00 CST 2019, endTest=null)

结果符合预期。不过对此结果我仍旧抛出如下两个问题供小伙伴自行思考:
1、输出了ParseException…
2、start有值,endTest值却为null了

理解这块最后我想说:通过自定义编辑器,我们可以非常自由、高度定制化的完成自定义类型的封装,可以使得我们的Controller更加容错、更加智能、更加简洁。有兴趣的可以运用此块知识,自行实践~

6.WebBindingInitializer

WebBindingInitializer

WebBindingInitializer:实现此接口重写initBinder方法注册的属性编辑器是全局的属性编辑器,对所有的Controller都有效。

可以简单粗暴的理解为:WebBindingInitializer为编码方式,@InitBinder为注解方式(当然注解方式还能控制到只对当前Controller有效,实现更细粒度的控制)

观察发现,Spring对这个接口的命名很有意思:它用的Binding正在进行时态~

// @since 2.5   Spring在初始化WebDataBinder时候的回调接口,给调用者自定义~
public interface WebBindingInitializer {// @since 5.0void initBinder(WebDataBinder binder);// @deprecated as of 5.0 in favor of {@link #initBinder(WebDataBinder)}@Deprecateddefault void initBinder(WebDataBinder binder, WebRequest request) {initBinder(binder);}}

此接口它的内建唯一实现类为:ConfigurableWebBindingInitializer,若你自己想要扩展,建议继承它~

public class ConfigurableWebBindingInitializer implements WebBindingInitializer {private boolean autoGrowNestedPaths = true;private boolean directFieldAccess = false; // 显然这里是false// 下面这些参数,不就是WebDataBinder那些可以配置的属性们吗?@Nullableprivate MessageCodesResolver messageCodesResolver;@Nullableprivate BindingErrorProcessor bindingErrorProcessor;@Nullableprivate Validator validator;@Nullableprivate ConversionService conversionService;// 此处使用的PropertyEditorRegistrar来管理的,最终都会被注册进PropertyEditorRegistry嘛@Nullableprivate PropertyEditorRegistrar[] propertyEditorRegistrars;... //  省略所有get/set// 它做的事无非就是把配置的值都放进去而已~~@Overridepublic void initBinder(WebDataBinder binder) {binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);if (this.directFieldAccess) {binder.initDirectFieldAccess();}if (this.messageCodesResolver != null) {binder.setMessageCodesResolver(this.messageCodesResolver);}if (this.bindingErrorProcessor != null) {binder.setBindingErrorProcessor(this.bindingErrorProcessor);}// 可以看到对校验器这块  内部还是做了容错的if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) {binder.setValidator(this.validator);}if (this.conversionService != null) {binder.setConversionService(this.conversionService);}if (this.propertyEditorRegistrars != null) {for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {propertyEditorRegistrar.registerCustomEditors(binder);}}}
}

此实现类主要是提供了一些可配置项,方便使用。注意:此接口一般不直接使用,而是结合InitBinderDataBinderFactoryWebDataBinderFactory等一起使用~

7.WebDataBinderFactory

顾名思义它就是来创造一个WebDataBinder的工厂。

// @since 3.1   注意:WebDataBinder 可是1.2就有了~
public interface WebDataBinderFactory {// 此处使用的是Spring自己的NativeWebRequest   后面两个参数就不解释了WebDataBinder createBinder(NativeWebRequest webRequest,@Nullable Object target, String objectName) throws Exception;
}

它的继承树如下:

1.DefaultDataBinderFactory
public class DefaultDataBinderFactory implements WebDataBinderFactory {@Nullableprivate final WebBindingInitializer initializer;// 注意:这是唯一构造函数public DefaultDataBinderFactory(@Nullable WebBindingInitializer initializer) {this.initializer = initializer;}// 实现接口的方法@Override@SuppressWarnings("deprecation")public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);// 可见WebDataBinder 创建好后,此处就会回调(只有一个)if (this.initializer != null) {this.initializer.initBinder(dataBinder, webRequest);}// 空方法 子类去实现,比如InitBinderDataBinderFactory实现了词方法initBinder(dataBinder, webRequest);return dataBinder;}//  子类可以复写,默认实现是WebRequestDataBinder// 比如子类ServletRequestDataBinderFactory就复写了,使用的new                                 // ExtendedServletRequestDataBinder(target, objectName)protected WebDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest webRequest) throws Exception {return new WebRequestDataBinder(target, objectName);}
}

按照Spring一贯的设计,本方法实现了模板动作,子类只需要复写对应的动作即可达到效果。

2.InitBinderDataBinderFactory

它继承自DefaultDataBinderFactory,主要用于处理标注有@InitBinder的方法做初始绑定~

// @since 3.1
public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {// 需要注意的是:`@InitBinder`可以标注N多个方法~  所以此处是Listprivate final List<InvocableHandlerMethod> binderMethods;// 此子类的唯一构造函数public InitBinderDataBinderFactory(List<InvocableHandlerMethod> binderMethods,@Nullable WebBindingInitializer initializer) {super(initializer);this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList());}// 上面知道此方法的调用方法生initializer.initBinder之后// 所以使用注解它生效的时机是在直接实现接口的后面的~@Overridepublic void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {for (InvocableHandlerMethod binderMethod : this.binderMethods) {// 判断@InitBinder是否对dataBinder持有的target对象生效~~~(根据name来匹配的)if (isBinderMethodApplicable(binderMethod, dataBinder)) {// 关于目标方法执行这块,可以参考另外一篇@InitBinder的原理说明~Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);// 标注@InitBinder的方法不能有返回值if (returnValue != null) {。。。抛出异常}}}}// @InitBinder有个Value值,它是个数组。它是用来匹配dataBinder.getObjectName()是否匹配的   若     // 匹配上了,现在此注解方法就会生效// 若value为空,那就对所有生效~~~protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod,WebDataBinder dataBinder) {InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);Assert.state(ann != null, "No InitBinder annotation");String[] names = ann.value();return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));}
}
3.ServletRequestDataBinderFactory

它继承自InitBinderDataBinderFactory,作用就更明显了。既能够处理@InitBinder,而且它使用的是更为强大的数据绑定器:ExtendedServletRequestDataBinder

// @since 3.1
public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory {public ServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) {super(binderMethods, initializer);}@Overrideprotected ServletRequestDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest request) throws Exception  {return new ExtendedServletRequestDataBinder(target, objectName);}
}

此工厂是RequestMappingHandlerAdapter这个适配器默认使用的一个数据绑定器工厂,而RequestMappingHandlerAdapter却又是当下使用得最频繁、功能最强大的一个适配器

4.总结

WebDataBinderSpringMVC中使用,它不需要我们自己去创建,我们只需要向它注册参数类型对应的属性编辑器PropertyEditorPropertyEditor可以将字符串转换成其真正的数据类型,它的void setAsText(String text)方法实现数据转换的过程。

好好掌握这部分内容,这在Spring MVC中结合@InitBinder注解一起使用将有非常大的威力,能一定程度上简化你的开发,提高效率

作者:YourBatman链接:https://www.jianshu.com/p/b1cd2234e012来源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2.注册校验器

我们再来看一个注册校验器的例子, Spring MVC中参数校验非常简单,只需要在ModelAttribute类型的参数(这种参数不一定前面都有@ModelAttribute注释,后面有详细,介绍)前注释@Valid或者@Validated就可以了,不过具体校验是通过Binder中的校验器来做的,所以需要提前给Binder注册校验器,下面这个例子,首先新建一个User类和个相应的校验器UserValidator

3.@ModelAttribute

@ModelAttribute注释如果用在方法上,则用于设置参数,它会在执行处理前将参数设置到Model中。其规则是如果@ModelAttribute设置了value则将其作为参数名,返回值作为参数值设置到Model;如果方法含有Model, Map或者ModelMap类型的参数,则可以直接将需要设置的参数设置上去;如果既没有设置value也没有Model类型的参数则根据返回值类型解析出参数名(具体逻辑在ModelFactory中讲解),返回值作为参数值设置到Model。来看个例子就明白了。

4.RequestMappingHandlerAdapter

1.创建RequestMappingHandlerAdapter

看一下他的继承关系

可以看到这个类实现了InitializingBean接口,也就是说他在实例化的过程中会执行afterPropertiesSet方法

所以RequestMappingHandlerAdapter的初始化就在这里进行的。我们看一下这个方法

public void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beans// 初始化注释了@ControllerAdvice的类的相关属性// 这里面也初始化了modelAttributeAdviceCache属性和initBinderAdviceCache属性// 同时初始化了requestResponseBodyAdvice:用来保存前面介绍过的实现了ResponseBodyAdvice接口、// 可以修改返回的ResponseBody的类。/*** modelAttributeAdviceCache和initBinderAdviceCache :* 分别用于缓存@ControllerAdvice注释的类里面注释了@ModelAttribute和@InitBinder的方法,* 也就是全局的@ModelAttribute和@InitBinder方法。* 每个处理器自己的@ModelAttribute和@InitBinder方法是在第一次使用处理器处理请求时缓存起来的,* 这种做法既不需要启动时就花时间遍历每个Controller查找@ModelAttribute和@InitBinder方法,* 又能在调用过一次后再调用相同处理器处理请求时不需要再次查找而从缓存中获取。* 这两种缓存的思路类似于单例模式中的饿汉式和懒汉式。*/initControllerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}
}

非常清晰的4步,首先initControllerAdviceCache初始化注释了@ControllerAdvice的类的那三个属性,然后依次初始化argumentResolvers, initBinderArgumentResolvers和returnValueHandlers。后面三个属性初始化的方式都一样,都是先调用getDefaultxxx得到相应的值,然后设置给对应的属性,而且都是new出来的XXXComposite类型,这种类型在分析HandlerMapping中的RequestCondition时已经见到过了,使用的是责任链模式,它自己并不实际干活,而是封装了多个别的组件,干活时交给别的组件,主要作用是方便调用。getDefaultXXX方法稍后分析,下面先来看一下initControllerAdviceCache是怎么工作的:

private void initControllerAdviceCache() {if (getApplicationContext() == null) {return;}...日志// 其实这里面就是拿到所有的bean然后判断一下这些bean那些标注了@ControllerAdvice注解List<ControllerAdviceBean> adviceBeans =ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());//进行排序AnnotationAwareOrderComparator.sort(adviceBeans);List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {...抛出异常}// 查找注释了@ModelAttribute而且没注释@RequestMapping的方法Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType,MODEL_ATTRIBUTE_METHODS);if (!attrMethods.isEmpty()) {this.modelAttributeAdviceCache.put(adviceBean, attrMethods);...日志}// 查找注释了@InitBinder的方法Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType,INIT_BINDER_METHODS);if (!binderMethods.isEmpty()) {this.initBinderAdviceCache.put(adviceBean, binderMethods);...日志}// 查找实现了ResponseBodyAdvice接口的类boolean isRequestBodyAdvice = RequestBodyAdvice.class.isAssignableFrom(beanType);boolean isResponseBodyAdvice =ResponseBodyAdvice.class.isAssignableFrom(beanType);if (isRequestBodyAdvice || isResponseBodyAdvice) {requestResponseBodyAdviceBeans.add(adviceBean);...打印日志}}// 将查找到的实现了ResponseBodyAdvice接口的类从前面添加到responseBodyAdvice属性if (!requestResponseBodyAdviceBeans.isEmpty()) {this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);}
}

​ 这里首先通过ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()拿到容器中所有注释了@ControllerAdvice的bean,并根据Order排了序,然后使用for循环遍历,找到每个bean里相应的方法(或bean自身)设置到相应的属性。查找@ModelAttribute和@InitBinder注释方法使用的是HandlerMethodSelector.selectMethods,这种方法前面已经介绍过了,它是根据第二个参数Filter来选择的,只不过这里的Filter单独定义成了静态变量INITBINDER METHODS和MODEL ATTRIBUTE-METHODS,它们分别表示查找注释了@InitBinder的方法和注释了@ModelAttribute而且没注释@ RequestMapping的方法(同时注释了@ RequestMapping的方法只是将返回值设置到Model而不是作为View使用了,但不会提前执行),代码如下:

/*** MethodFilter that matches {@link InitBinder @InitBinder} methods.*/
public static final MethodFilter INIT_BINDER_METHODS = method ->(AnnotationUtils.findAnnotation(method, InitBinder.class) != null);/*** MethodFilter that matches {@link ModelAttribute @ModelAttribute} methods.*/
public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->(AnnotationUtils.findAnnotation(method, RequestMapping.class) == null &&AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null);

实现了ResponseBodyAdvice接口的类并没有在for循环里直接添加到responseBodyAdvice属性中,而是先将它们保存到responseBodyAdviceBeans临时变量里,最后再添加到responseBodyAdvice里的,添加的代码是this.responseBodyAdvice.addAll (0, responseBodyAdviceBeans),这么做的目的就是要把这里找到的ResponseBodyAdvice放在最前面。ResponseBodyAdvice的实现类有两种注册方法,一种是直接注册到RequestMappingHandlerAdapter,另外一种是通过@ControllerAdvice注释,让Spring MVC自己找到并注册,从这里可以看到通过@ControllerAdvice注释注册的优先级更高。说完initControllerAdviceCache,再返回去看一下那三个getDefaultXXX方法,这三个方法非常类似,下面以getDefaultArgumentResolvers为例来进行分析,这个方法用来设置argumentResolvers属性,这是一个非常核心的属性,后面要分析的很多组件都和这个属性有关系。代码如下:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();// Annotation-based argument resolution// 基于注解的参数解析resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));resolvers.add(new RequestParamMapMethodArgumentResolver());resolvers.add(new PathVariableMethodArgumentResolver());resolvers.add(new PathVariableMapMethodArgumentResolver());resolvers.add(new MatrixVariableMethodArgumentResolver());resolvers.add(new MatrixVariableMapMethodArgumentResolver());resolvers.add(new ServletModelAttributeMethodProcessor(false));resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.requestResponseBodyAdvice));resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(),this.requestResponseBodyAdvice));resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));resolvers.add(new RequestHeaderMapMethodArgumentResolver());resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new SessionAttributeMethodArgumentResolver());resolvers.add(new RequestAttributeMethodArgumentResolver());// 基于类型参数解析resolvers.add(new ServletRequestMethodArgumentResolver());resolvers.add(new ServletResponseMethodArgumentResolver());resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(),this.requestResponseBodyAdvice));resolvers.add(new RedirectAttributesMethodArgumentResolver());resolvers.add(new ModelMethodProcessor());resolvers.add(new MapMethodProcessor());resolvers.add(new ErrorsMethodArgumentResolver());resolvers.add(new SessionStatusMethodArgumentResolver());resolvers.add(new UriComponentsBuilderMethodArgumentResolver());// 自定义参数if (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}// 解析所有类型的参数resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));resolvers.add(new ServletModelAttributeMethodProcessor(true));return resolvers;
}

通过注释可以看到,这里的解析器可以分为四类:通过注释解析的解析器、通过类型解析的解析器、自定义的解析器和可以解析所有类型的解析器。第三类是可以自己定义的解析器,定义方法是自己按要求写个resolver然后通过customArgumentResolvers属性注册到RequestMappingHandlerAdapter,需要注意的是, 自定义的解析器是在前两种类型的解析器都无法解析的时候才会使用到,这个顺序无法改变!所以如果要想自己写一个解析器来解析@Path Variable注释的PathVariable类型的参数,是无法实现的,即使写出来并注册到RequestMappingHandlerAdapter上面也不会被调用。Spring MVC自己定义的解析器的顺序也是固定的,不可以改变。

上面的说了挺多,主要是涉及到了几个注解和接口

1.@ControllerAdvice

  1. 功能:

    • 全局异常处理
    • 全局数据绑定
    • 全局数据预处理
  2. 全局异常处理

    @ControllerAdvice
    public class MyGlobalExceptionHandler {@ExceptionHandler(Exception.class)public ModelAndView customException(Exception e) {ModelAndView mv = new ModelAndView();mv.addObject("message", e.getMessage());mv.setViewName("myerror");return mv;}
    }

    在该类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法…,也可以直接向上面代码一样,在一个方法中处理所有的异常信息。

    @ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。

  3. 全局数据绑定

    全局数据绑定功能可以用来做一些初始化的数据操作,我们可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问导致这些数据。

    使用步骤,首先定义全局数据,如下:

    @ControllerAdvice
    public class MyGlobalExceptionHandler {@ModelAttribute(name = "md")public Map<String,Object> mydata() {HashMap<String, Object> map = new HashMap<>();map.put("age", 99);map.put("gender", "男");return map;}
    }

    使用 @ModelAttribute 注解标记该方法的返回数据是一个全局数据,默认情况下,这个全局数据的 key 就是返回的变量名,value 就是方法返回值,当然开发者可以通过 @ModelAttribute 注解的 name 属性去重新指定 key。

    定义完成后,在任何一个Controller 的接口中,都可以获取到这里定义的数据:

    @RestController
    public class HelloController {@GetMapping("/hello")public String hello(Model model) {Map<String, Object> map = model.asMap();System.out.println(map);int i = 1 / 0;return "hello controller advice";}
    }
  4. 全局数据预处理

    考虑我有两个实体类,Book 和 Author,分别定义如下:

    public class Book {private String name;private Long price;//getter/setter
    }
    public class Author {private String name;private Integer age;//getter/setter
    }

    此时,如果我定义一个数据添加接口,如下:

    @PostMapping("/book")
    public void addBook(Book book, Author author) {System.out.println(book);System.out.println(author);
    }

    这个时候,添加操作就会有问题,因为两个实体类都有一个 name 属性,从前端传递时 ,无法区分。此时,通过 @ControllerAdvice 的全局数据预处理可以解决这个问题

    解决步骤如下:

    1. 给接口中的变量取别名:

      @PostMapping("/book")
      public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {System.out.println(book);System.out.println(author);
      }
    2. 进行请求数据预处理
      在 @ControllerAdvice 标记的类中添加如下代码:

      @InitBinder("b")
      public void b(WebDataBinder binder) {binder.setFieldDefaultPrefix("b.");
      }
      @InitBinder("a")
      public void a(WebDataBinder binder) {binder.setFieldDefaultPrefix("a.");
      }

      @InitBinder(“b”) 注解表示该方法用来处理和Book和相关的参数,在方法中,给参数添加一个 b 前缀,即请求参数要有b前缀.

    3. 发送请求

2.ResponseBodyAdvice接口

直接看一个案例就能明白了:

//方法的执行时机,其实就是返回标注了@ResponseBody的方法之后
@ControllerAdvice
public class MyResponseBodyAdvice<T> implements ResponseBodyAdvice<T> {@Overridepublic boolean supports(MethodParameter returnType,Class<? extends HttpMessageConverter<?>> converterType) {// 我们可以在这里进行一些类型的判断什么的System.out.println(returnType + "==" + converterType);// 直接返回true才会继续执行下面的方法return true;}@Overridepublic T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>>                                                 selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 这个就是返回的数据,我们可以对他进行一些处理System.out.println(body)// 这个时候才返回到前台return body;}
}

2.使用RequestMappingHandlerAdapter

RequestMappingHandlerAdapter处理请求的入口方法是handlelnternal,代码如下:

先说明一下当这个类初始化的时候,会调用父类的构造方法,所以说能会进行一次构造方法的执行

// 其实这里初始化的时候会设置为false的,因为不可能仅仅支持这几种请求
// 如果传递进来的事true表示仅仅支持下面的三种类型请求
// 但是说他到底支持几种类型呢:看看源码说明
public WebContentGenerator(boolean restrictDefaultSupportedMethods) {if (restrictDefaultSupportedMethods) {this.supportedMethods = new LinkedHashSet<>(4);this.supportedMethods.add(METHOD_GET);this.supportedMethods.add(METHOD_HEAD);this.supportedMethods.add(METHOD_POST);}// 其实这里就是设置支持所有的类型,如果说呢之前已经设置了,下面就是直接设置上面设置的类型// GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE(这个没有被添加);initAllowHeader();
}
protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;// 检查session是否存在// 如果不存在抛出异常信息checkRequest(request);// Execute invokeHandlerMethod in synchronized block if required.// 如果需要,在同步块中执行invokeHandlerMethod。// 判断Handler是否有@SessionAttributes注释的参数if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {mav = invokeHandlerMethod(request, response, handlerMethod);}} else {// No HttpSession available -> no mutex necessary// 没有可用的HttpSession ->没有必要的互斥mav = invokeHandlerMethod(request, response, handlerMethod);}} else {// No synchronization on session demanded at all...// 根本不需要会话同步……mav = invokeHandlerMethod(request, response, handlerMethod);}// if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);} else {prepareResponse(response);}return mav;
}

1.@SessionAttribute&@SessionAttributes

  1. 获取值
@RequestMapping("/user/get/{id}")
@ResponseBody
public User get(@PathVariable Integer id,//正常来说我们获取session的值需要session对象//springmvc提供给我们这个注解就是可以通过它进行获取@SessionAttribute("user") User user) {return userService.get(id);
}
  1. 设置值

    说明:最开始我用的时候直接使用注解,但是会报错说session没有创建成功,也就是说明当我们需要用session添加值得时候,那个handlerMethod方法必须在参数中添加一个HttpSession参数,当然我们可以不用这个session添加值,可以直接通过model或者map设置值,会自动添加到session中。

    @SessionAttributes({"user","hhh"})
    public class UserController {@RequestMapping("/user/list")@ResponseBodypublic Map<String, Object> list(Model model, HttpSession session) {List<User> list = userService.list();Map<String, Object> map = new HashMap<>();map.put("user", new User(1, "aaa", "aaa"));model.addAttribute("user", new User(1, "aaa", "aaa"));//把maps放到session中return map;}//也直接放到session中// 但是只要好像是只要初始化session其他的就不用了// 全局只需要初始化一个session就行@ModelAttribute("hhh")public String getHhh(){return "hhhhhhhhh";}
    }

2.执行handler方法invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception{// 在invokeHandleMethod方法中首先使用request和response创建了Servlet WebRequest类型的      // webRequest,在ArgumentResolver解析参数时使用的request就是这个webRequest,当然如果我们的处理    // 器需要HttpServletRequest类型的参数, ArgumentResolver会给我们设置原始的requestServletWebRequest webRequest = new ServletWebRequest(request, response);try {//请看下面得参数说明WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod =createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request,response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer)                                                                                   asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();...打印日志invocableMethod = invocableMethod.wrapConcurrentResult(result);}invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);} finally {webRequest.requestCompleted();}
}
1.WebDataBinderFactory

WebDataBinderFactory的作用从名字就可以看出是用来创建WebDataBinder的,WebDataBinder用于参数绑定,主要功能就是实现参数跟String之间的类型转换,ArgumentResolver在进行参数解析的过程中会用到WebDataBinder,另外ModelFactory在更新Model时也会用到它。WebDataBinderFactory的创建过程就是将符合条件的注释了@InitBinder的方法找出来,并使用它们新建出ServletRequestDataBinderFactory类型的WebDataBinderFactory。这里的InitBinder方法包括两部分:一部分是注释了@ControllerAdvice的并且符合要求的全局处理器,里面的InitBinder方法;第二部分就是处理器自身的InitBinder方法,添加的顺序是先添加全局的后添加自身的。第二类InitBinder方法会在第一次调用后保存到缓存中,以后直接从缓存获取就可以了。查找注释了@InitBinder方法的方法和以前一样,使用HandlerMethodSelector.selectMethods来找,而全局的InitBinder方法在创建RequestMappingHandlerAdapter的时候已经设置到缓存中了。WebDataBinderFactory创建代码如下:

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {Class<?> handlerType = handlerMethod.getBeanType();// 检查当前handler中得InitBinder方法是否已经存在缓存中Set<Method> methods = this.initBinderCache.get(handlerType);// 如果没要找到,则查找并设置到缓存中if (methods == null) {methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);this.initBinderCache.put(handlerType, methods);}// 定义保存InitBinder方法得临时变量List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();// Global methods first// 将所有符合条件的全局InitBinder方法添加到initBinderMethodsthis.initBinderAdviceCache.forEach((clazz, methodSet) -> {if (clazz.isApplicableToBeanType(handlerType)) {Object bean = clazz.resolveBean();for (Method method : methodSet) {initBinderMethods.add(createInitBinderMethod(bean, method));}}});// 将当前Handler中的InitBinder方法添加到initBinderMethodsfor (Method method : methods) {Object bean = handlerMethod.getBean();initBinderMethods.add(createInitBinderMethod(bean, method));}// 创建DataBinderFactory并返回return createDataBinderFactory(initBinderMethods);
}
2.ModelFactory

ModelFactory是用来处理Model的,主要包含两个功能: 1在处理器具体处理之前对Model进行初始化; ②在处理完请求后对Model参数进行更新。给Model初始化具体包括三部分内容: 1将原来的SessionAtributes中的值设置到Model; ②执行相应注释了@ModelAttribute的方法并将其值设置到Mode; ③处理器中注释了@ModelAttribute的参数如果同时在SessionAttributes中也配置了,而且在mavContainer中还没有值则从全部SessionAttributes (可能是其他处理器设置的值)中查找出并设置进去。对Model更新是先对SessionAttributes进行设置,设置规则是如果处理器里调用了SessionStatus#setComplete则将SessionAttributes清空,否则将mavContainer的defaultModel可以理解为Model,后面ModelAndViewContainer中会详细讲解)中相应的参数设置到SessionAttributes中,然后按需要给Model设置参数对应的BindingResult从这里可以看出调用SessionStatus#setComplete清空SessionAttributes是在整个处理执行完以后才执行的,也就是说这条语句在处理器中的位置并不重要,放在处理器的开头或者结尾都不会影响当前处理器对SessionAttributes的使用。ModelFactory的创建过程在getModelFactory方法中,代码如下:

private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {// 获取SessionAttributesHandlerSessionAttributesHandler sessionAttrHandler =                                                                                   getSessionAttributesHandler(handlerMethod);// 获取处理器的类型Class<?> handlerType = handlerMethod.getBeanType();// 获取处理器类中注释了@ModelAttribute而且没有注释@RequestMapping的类型,// 第一次获取后添加到缓存,以后直接从缓存中获取Set<Method> methods = this.modelAttributeCache.get(handlerType);if (methods == null) {methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);this.modelAttributeCache.put(handlerType, methods);}List<InvocableHandlerMethod> attrMethods = new ArrayList<>();// Global methods first// 先添加全局@ModelAttribute方法,后添加当前处理器定义的@ModelAttribute方法this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {if (clazz.isApplicableToBeanType(handlerType)) {Object bean = clazz.resolveBean();for (Method method : methodSet) {attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));}}});for (Method method : methods) {Object bean = handlerMethod.getBean();attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));}// 新建ModelFactoryreturn new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}

从最后一句新建ModelFactory中可以看出主要使用了三个参数,第一个是注释了@ModelAttribute的方法,第二个是WebDataBinderFactory,第三个是SessionAttributesHandler其中WebDataBinderFactory使用的就是上面创建出来的WebDataBinderFactory ; SessionAttributesHandler的创建方法getSessionAttributesHandler在前面已经介绍过了;注释了@ModelAttribute的方法分两部分:一部分是注释了@ControllerAdvice的类中定义的全局的@ModelAttribute方法;另一部分是当前处理器自身的@ModelAttribute方法,添加规则是先添加全局的后添加自己的。

3.ServletInvocableHandlerMethod

ServletInvocableHanderMethod类型非常重要,它继承自HandlerMethod,并且可以直接,执行。实际请求的处理就是通过它来执行的,参数绑定、处理请求以及返回值处理都在它里边完成.

4.接下来的过程

接下来这里的参加过程非常简单,首先使用handlerMethod新建类ServletinvocableHandlerMethod类,然后将argumentResolvers,returnValueHandlers,binderFactory和 parameterNameDiscoverer设置进去就完成了。这三个变量弄明白后invokeHandleMethod方法就容易理解了。这三个变量创建完之后的工作还有三步(这里省略了异步处理):

  1. 新建传递参数的ModelAndViewContainer容器,并将相应参数设置到其Model中;
  2. 执行请求;
  3. 请求处理完后进行一些后置处理。
5. ModelAndViewContainer

新建ModelAndViewContainer类型的mavContainer参数,用于保存Model和View,它贯穿于整个处理过程(注意,在处理请求时使用的并不是ModelAndView),然后对mavContainer进行了设置,主要包括三部分内容: D将FlashMap中的数据设置到Model ; ②2使用modelFactory将SessionAttributes和注释了@ModelAttribute的方法的参数设置到Model; ③根据配置对ignoreDefaultModelOnRedirect进行了设置,这个参数在分析ModelAndViewContainer的时候再详细讲解。设置代码如下:

ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);      mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

到这里传递参数的容器就准备好了。设置完mavContainer后又做了一些异步处理的相关的工作,异步处理在后面专门讲解。

执行请求,具体方法是直接调用ServletinvocableHandlerMethod里的invokeAndHandle方法执行的,代码如下:

6.invokeAndHandle执行请求
invocableMethod.invokeAndHandle(webRequest, mavContainer);

ServletInvocableHandlerMethod的invokeAndHandle方法执行请求的具体过程在后面分析ServletInvocableHandlerMethod的时候再详细讲解。

处理完请求后的后置处理,这是在getModelAndView方法中处理的。一共做了三件事:

  1. 调用ModelFactory的updateModel方法更新了Model (包括设置了SessionAttributes和给Model设置BindingResult);
  2. 根据mavContainer创建了ModelAndView;
  3. 如果mavContainer里的model是RedirectAttributes类型,则将其值设置到FlashMap。代码如下:
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {modelFactory.updateModel(webRequest, mavContainer);if (mavContainer.isRequestHandled()) {return null;}ModelMap model = mavContainer.getModel();ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());// 如果mavContainer里得view不是引用,也不是String类型,则设置到mv中if (!mavContainer.isViewReference()) {mav.setView((View) mavContainer.getView());}if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();HttpServletRequest request =                                                                                     webRequest.getNativeRequest(HttpServletRequest.class);if (request != null) {RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}}return mav;
}

这里的model只有处理器在返回redirect类型的视图时才可能是RedirectAttributes类型,否则不会是RedirectAttributes类型,也就是说在不返回redirect类型视图的处理器中即使使用RedirectAttributes设置了变量也不会保存到FalseMap中,具体细节介绍1ModelAndViewContainer的时候再详细分析。整个RequestMappingHandlerAdapter处理请求的过程(自身处理过程,不包含组件内部处理)就分析完了,接下来分析里面涉及的组件,在将这些组件全部分析完成后再返回来看就容易理解了。

3.小结

本节详细分析了RequestMappingHandlerAdapter自身的结构。作为一个HandlerAdapter,RequestMappingHandlerAdapter的作用就是使用处理器处理请求。它使用的处理器是HandlerMethod类型,处理请求的过程主要分为三步:绑定参数、执行请求和处理返回值。所绑定的参数的来源有6个地方:

  • request中相关的参数,主要包括url中的参数、post过来的参数以及请求头中的值;
  • cookie中的参数;
  • session中的参数;
  • 设置到FlashMap中的参数,这种主要用于redirect的参数传递;
  • SessionAttributes传递的参数;
  • 通过相应的注释了@ModelAttribute的方法进行设置的参数。

前三类参数通过request管理,后三类通过Model管理。前三类在request中获取,不需要做过多准备工作。第四类参数是直接在RequestMappingHandlerAdapter中进行处理的,在请求前将之前保存的设置到model,请求处理完成后如果需要则将model中的值设置到FlashMap。后两类参数使用ModelFactory管理。准备好参数源后使用ServletInvocableHandlerMethod组件具体执行请求,其中包括使用ArgumentResolver解析参数、执行请求、使用ReturnValueHandler处理返回值等内容,详细内容会在ServletInvocableHandlerMethod中具体讲解。ServletinvocableHandlerMethod执行完请求处理后还有一些扫尾工作,主要是Model中参数的缓存和ModelAndView的创建整个处理过程其实非常简单,只是里面使用了很多我们不熟悉的组件,这些组件如果理解了再返回来看就简单了。接下来就分别讲解这些组件。

3.ModelAndViewContainer

ModelAndViewContainer承担着整个请求过程中数据的传递工作。它除了保存Model和View外还有一些别的功能,如果不知道这些功能,很多代码就无法理解。先看一下它里面所包含的属性,定义如下:

// 如果为true则在处理器返回redirect视图时一定不使defaultModel
private boolean ignoreDefaultModelOnRedirect = false;//视图 Object类型的,可以是实际视图也可以是String类型的逻辑视图
@Nullable
private Object view;//默认使用的是Model
private final ModelMap defaultModel = new BindingAwareModelMap();// redirect类型的model
@Nullable
private ModelMap redirectModel;
// 处理器返回redirect视图的标志。
private boolean redirectModelScenario = false;//http状态
@Nullable
private HttpStatus status;private final Set<String> noBinding = new HashSet<>(4);private final Set<String> bindingDisabled = new HashSet<>(4);
//用于设置SessionAttribute使用完的标志。
private final SessionStatus sessionStatus = new SimpleSessionStatus();
// 请求是否已经处理完成的标志。
private boolean requestHandled = false;

先看一下defaultModel和redirectModel,这两个都是Model,前者是默认使用的Model,后者用于传递redirect时的参数。我们在处理器中使用了Model或者ModelMap时ArgumentResolver会传入defaultModel, 它是BindingAwareModelMap类型,既继承了ModelMap又实现了Model接口,所以在处理器中使用Model或者ModelMap其实使用的是同一个对象, Map参数传入的也是这个对象。处理器中RedirectAttributes类型的参数ArgumentResolver会传入redirectModel,它实际上是RedirectAttributesModelMap类型。ModelAndViewContainer的getModel方法会根据条件返回这两个Model里的一个,代码如下:

public ModelMap getModel() {if (useDefaultModel()) {return this.defaultModel;}else {if (this.redirectModel == null) {this.redirectModel = new ModelMap();}return this.redirectModel;}
}

判断逻辑在useDefaultModel中,如果redirectModelScenario为false,也就是返回的不是redirect视图的时候一定返回defaultModel,如果返回redirect视图的情况下需要根据redirectModel和ignoreDefaultModelOnRedirect的情况进一步,如果redirectModel不为空和ignoreDefaultModelOnRedirect设置为true这两个条件中有一个成立则返回redirectModel,否则返回defaultModelo总结如下返回defaultModel的情况: D处理器返回的不是redirect视图;②处理器返回的是redirect视图但是redirectModel为null,而且ignoreDefaultModelOnRedirect也是false返回redirectModel的情况: 0处理器返回redirect视图,并且redirectModel不为null;②处理器返回的是redirect视图,并且ignoreDefaultModelOnRedirect为true.ignoreDefaultModelOnRedirect可以在RequestMappingHandlerAdapter中设置。判断处理器返回的是不是redirect视图的标志设置在redirectModelScenario中,它是在Return ValueHandler中设置的, Return ValueHandler如果判断到是redirect视图就会将redirectModelScenario设置为true。也就是说在ReturnValueHandler处理前ModelAndViewContainer的getModel返回的一定是defaultModel,处理后才可能是redirectModel现在再返回去看RequestMappingHandlerAdapter中的getModelAndView方法getModel后判断Model是不是RedirectAttributes类型就清楚是怎么回事了。在getModel返回redirectModel的情况下,在处理器中设置到Model中的参数就不会被使用了(设置SessionAttribute除外)。这样也没有什么危害,因为只有redirect的情况才会返回redirectModel,而这种情况是不需要渲染页面的,所以defaultModel里的参数本来也没什么用。同样,如果返回defaultModel,设置到RedirectAttributes中的参数也将丢弃,也就是说在返回的View不是redirect类型时,即使处理器使用RedirectAttributes参数设置了值也不会传递到下一个请求。另外,通过@SessionAttribute传递的参数是在ModelFactory中的updateModel方法中设置的,那里使用了mavContainer.getDefaultModel方法,这样就确保无论在什么情况下都是使用defaultModel,也就是只有将参数设置到Model或者ModelMap里才能使用SessionAttribute缓存,设置到RedirectAttributes里的参数不可以。ModelAndViewContainer还提供了添加、合并和删除属性的方法,它们都是直接调用Model操作的,代码如下

public ModelAndViewContainer addAttribute(String name, @Nullable Object value) {getModel().addAttribute(name, value);return this;
}public ModelAndViewContainer addAttribute(Object value) {getModel().addAttribute(value);return this;
}public ModelAndViewContainer addAllAttributes(@Nullable Map<String, ?> attributes) {getModel().addAllAttributes(attributes);return this;
}

4.SessionAttributesHandler

SessionAttributesHandler用来处理@SessionAttributes注释的参数,不过它只是做一些宏观的事情,比如,哪个Handler都可以缓存哪些参数、某个参数在当前的SessionAttributes中是否存在、如何同时操作多个参数等,而具体的存储工作是交给SessionAttributeStore去做的,不过SessionAttributeStore并不是保存数据的容器,而是用于保存数据的工具,具体保存数据的容器默认使用的是Session,当然也可以使用别的容器,重写SessionAttributeStore然后设置到RequestMappingHandlerAdapter,在SessionAttributeStore中保存到别的容器就可以了,如果集群就可以考虑一下。SessionAttributesHandler里面有四个属性: attributeNames、 attributeTypes, knownAttributeNames和sessionAttributeStore, sessionAttributeStore是SessionAttributeStore类型的,用于具体执行Attribute的存储工作,前三个属性都是Set类型, attributeNames存储@SessionAttributes注释里value对应的值,也就是参数名, attributeTypes存储@SessionAttributes注释里types对应的值,也就是参数类型, knownAttributeNames用于存储所有已知可以被当前处理器处理的属性名,它来自两个地方,首先在构造方法里会将所有attributeNames的值设置到knownAttributeNames中,其次当调用isHandlerSessionAttribute方法检查,而且是当前Handler所管理的SessionAttributes的时候也会添加到knownAttributeNames,而保存属性的storeAttributes方法会在每个属性保存前调用isHandlerSessionAttribute方法判断是否支持要保存的属性,所以所有保存过的属性的名称都会被保存在knownAttributeNames里面。knownAttributeNames作用主要是保存了除了使用value配置的名称外还将通过types配置的已经保存过的属性名保存起来,这样在清空的时候只需要遍历knownAttributeNames就可以了。

public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null");this.sessionAttributeStore = sessionAttributeStore;SessionAttributes ann = AnnotatedElementUtils.findMergedAnnotation(handlerType,SessionAttributes.class);if (ann != null) {Collections.addAll(this.attributeNames, ann.names());Collections.addAll(this.attributeTypes, ann.types());}this.knownAttributeNames.addAll(this.attributeNames);
}

SessionAttributesHandler中除了保存属性storeAttributes的方法外还有取回属性值的retrieveAttributes方法和清空属性的cleanupAtributes方法,它们都是根据knownAttributeNames来操作的。另外还有按属性名取回属性的retrieveAttribute方法,代码如下:

public Map<String, Object> retrieveAttributes(WebRequest request) {Map<String, Object> attributes = new HashMap<>();for (String name : this.knownAttributeNames) {Object value = this.sessionAttributeStore.retrieveAttribute(request, name);if (value != null) {attributes.put(name, value);}}return attributes;
}
public void cleanupAttributes(WebRequest request) {for (String attributeName : this.knownAttributeNames) {this.sessionAttributeStore.cleanupAttribute(request, attributeName);}
}
Object retrieveAttribute(WebRequest request, String attributeName) {return this.sessionAttributeStore.retrieveAttribute(request, attributeName);
}

需要注意的是,在取出全部属性和清除属性时都遍历了knownAttributeNames,前面说过它里面保存着当前Handler注释里所有使用过的属性名,所以这两个方法的操作只是对当前处理器类的@SessionAttributes注释里配置的属性起作用,而按名称取属性的方法可以在整个SessionAttributes中查找,没有knownAttributeNames的限制。另外需要注意的是,如果在不同的Handler中用SessionAttributes保存的属性使用了相同的名称,它们会相互影响。具体对每个参数保存、取回和删除的工作是由SessionAttributeStore完成的。这是一个接口,里面的三个方法分别对应上述三个功能,它的默认实现类是DefaultSessionAtributeStore ,它将参数保存到Session中,代码如下:

public class DefaultSessionAttributeStore implements SessionAttributeStore {private String attributeNamePrefix = "";public void setAttributeNamePrefix(@Nullable String attributeNamePrefix) {this.attributeNamePrefix = (attributeNamePrefix != null ? attributeNamePrefix : "");}@Overridepublic void storeAttribute(WebRequest request, String attributeName, Object attributeValue) {String storeAttributeName = getAttributeNameInSession(request, attributeName);request.setAttribute(storeAttributeName, attributeValue, WebRequest.SCOPE_SESSION);}@Override@Nullablepublic Object retrieveAttribute(WebRequest request, String attributeName) {String storeAttributeName = getAttributeNameInSession(request, attributeName);return request.getAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);}@Overridepublic void cleanupAttribute(WebRequest request, String attributeName) {String storeAttributeName = getAttributeNameInSession(request, attributeName);request.removeAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);}protected String getAttributeNameInSession(WebRequest request, String attributeName){return this.attributeNamePrefix + attributeName;}}

需要注意的是,这里对Session的操作使用的是request的setAttribute, getAttribute以及removeAttribute,虽然是request调用的,但并不是设置到了request上面,通过最后一个参数指定了设置范围。前面介绍过,在这里使用的request实际是ServletwebRequest,这三个方法在其父类ServlekRequestAttributes里定义,它可以通过最后一个参数指定操作的范围,在分析FrameworkServlet时已经对ServletRequestAttributes介绍过了,下面再看一下它的setAttribute的代码:

@Override
public void setAttribute(String name, Object value, int scope) {if (scope == SCOPE_REQUEST) {if (!isRequestActive()) {...抛出异常}this.request.setAttribute(name, value);}else {HttpSession session = obtainSession();this.sessionAttributesToUpdate.remove(name);session.setAttribute(name, value);}
}

如果第三个参数传入的是SCOPEREQUEST则会存储到request,否则存储到SessionsessionAttributesToUpdate属性用于保存从Session中获取过的值,最后request使用完后再同步给Session,因为Session中的值可能已经使用别的方式修改过,如果不好理解可以忽略这里的sessionAttributesToUpdate属性,它对这里的逻辑没有影响。总之,使用SCOPESESSION会对Session进行操作,而不是对request操作。下面总结一下, SessionAttributesHandler与@SessionAttributes注释相对应,用于对SessionAttributes操作,其中包含判断某个参数是否可以被处理以及批量对多个参数进行处理等功能。具体对单个参数的操作是交给SessionAttributeStore去完成的,它的默认实现DefaultSessionAttributeStore使用ServletWebRequest将参数设置到了Session中。SessionAttributesHandler是在ModelFactory中使用的。

5.ModelFactory

​ ModelFactory是用来维护Model的,具体包含两个功能: 0初始化Model ; ②处理器执行后将Model中相应的参数更新到SessionAttributes中。

1.初始化Model

modelFactory.initModel(webRequest, mavContainer, invocableMethod);
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception {//从SessionAttributes中取出保存的参数,并合并到mavContainer中Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);container.mergeAttributes(sessionAttributes);// 执行注释了@ModelAttribute的方法并将结果设置到ModelinvokeModelAttributeMethods(request, container);// 遍历既注释了@ModelAttribute又在@sessionAttributes注释中的参数for (String name : findSessionAttributeArguments(handlerMethod)) {if (!container.containsAttribute(name)) {Object value = this.sessionAttributesHandler.retrieveAttribute(request,name);if (value == null) {...抛出异常}container.addAttribute(name, value);}}
}

这里一共分了三步:

  1. 从SessionAttributes中取出保存的参数,并合并到mavContainer中;
  2. 执行注释了@ModelAttribute的方法并将结果设置到Model ;
  3. 判断既注释了@ModelAttribute又在@SessionAttributes注释中(参数名或者参数类型在注释中设置着)的参数是否已经设置到了mavContainer中,如果没有则使用SessionAttributesHandler从SessionAttributes中获取并设置到mavContainer中

第一步是直接在initModel中完成的,代码也非常简单,这里就不解释了。

第二步是调用invokeModelAttributeMethods方法完成的,代码如下:

private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)throws Exception {while (!this.modelMethods.isEmpty()) {// 获取注释了@ModelAttribute的方法InvocableHandlerMethod modelMethod =getNextModelMethod(container).getHandlerMethod();// 获取注释@ModelAttribute中设置的value作为参数名ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);if (container.containsAttribute(ann.name())) {if (!ann.binding()) {container.setBindingDisabled(ann.name());}continue;}//这里是获取参数并且进行执行方法,获取返回值对象Object returnValue = modelMethod.invokeForRequest(request, container);if (!modelMethod.isVoid()){// 使用getNameForReturnvalue获取参数名String returnValueName = getNameForReturnValue(returnValue,modelMethod.getReturnType());if (!ann.binding()) {container.setBindingDisabled(returnValueName);}if (!container.containsAttribute(returnValueName)) {//设置container.addAttribute(returnValueName, returnValue);}}}
}

这里遍历每个注释了@ModelAttribute的方法,然后从注释中获取参数名,如果获取到了(注释中设置了value),而且在mavContainer中已经存在此参数了则跳过此方法,否则执行方法(这时的方法封装成了InvocableHandlerMethod类型,可以直接执行,后面会详细讲解),执行完之后判断返回值是不是Void类型,如果是则说明这个方法是自己将参数设置到Model中的,这里就不处理了,否则使用getNameForReturnValue方法获取到参数名,并判断是否已存在mavContainer中,如果不存在则设置进去。

这里的重点是获取参数名的规则,获取参数名的getNameForReturn Value方法代码如下:

public static String getNameForReturnValue(@Nullable Object returnValue, MethodParameter returnType) {ModelAttribute ann = returnType.getMethodAnnotation(ModelAttribute.class);if (ann != null && StringUtils.hasText(ann.value())) {return ann.value();}else {Method method = returnType.getMethod();Assert.state(method != null, "No handler method");Class<?> containingClass = returnType.getContainingClass();Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method,containingClass);return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);}
}

这里首先获取了返回值的@ModelAttribute注释,也就是方法的@ModelAttribute注释,如果设置了value则直接将其作为参数名返回,否则使用Conventions的静态方法getVariableNameForReturnTyp根据方法、返回值类型和返回值获取参数名,其代码如下:

public static String getVariableNameForReturnType(Method method, Class<?> resolvedType, @Nullable Object value) {Assert.notNull(method, "Method must not be null");if (Object.class == resolvedType) {if (value == null) {...抛出异常}return getVariableName(value);}Class<?> valueClass;boolean pluralize = false;String reactiveSuffix = "";if (resolvedType.isArray()) {valueClass = resolvedType.getComponentType();pluralize = true;}else if (Collection.class.isAssignableFrom(resolvedType)) {valueClass = ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric();if (valueClass == null) {if (!(value instanceof Collection)) {...抛出 异常}Collection<?> collection = (Collection<?>) value;if (collection.isEmpty()) {...抛出异常}Object valueToCheck = peekAhead(collection);valueClass = getClassForValue(valueToCheck);}pluralize = true;}else {valueClass = resolvedType;ReactiveAdapterRegistry reactiveAdapterRegistry =ReactiveAdapterRegistry.getSharedInstance();if (reactiveAdapterRegistry.hasAdapters()) {ReactiveAdapter adapter = reactiveAdapterRegistry.getAdapter(valueClass);if (adapter != null && !adapter.getDescriptor().isNoValue()) {reactiveSuffix = ClassUtils.getShortName(valueClass);valueClass = ResolvableType.forMethodReturnType(method).getGeneric().resolve(Object.class);}}}String name = ClassUtils.getShortNameAsProperty(valueClass);return (pluralize ? pluralize(name) : name + reactiveSuffix);
}

代码比较长,它的核心逻辑就是获取返回值类型的"ShortName", “ShortName"是使用. ClassUtils的getShortNameAsProperty方法获取的,具体逻辑是先获取到去掉包名之后的类名,然后再判断类名是不是大于一个字符,而且前两个字符都是大写,如果是则直接返回,否则将第一个字符变成小写返回。不过这里的方法返回值类型如果是Object则会使用返回值的实际类型,如果返回值为数组或者Collection类型时会使用内部实际包装的类型,并在最后加"List”。下面看几个例子。

  • string->string
  • ClassUtils-> classUtils
  • UFOModel->UFOMode1
  • List> doubleList
  • Set-> doubleList
  • Double[ ] ->doubleList

这一步的难点在解析参数名,如果理解了解析参数名的规则,这一步也就理解了。这一步的整个流程是首先会判断返回值是不是Void类型,如果是则不处理了,如果不是则先判断注释里有没有value,如果有则使用注释的value做参数名,如果没有则根据上面说的规则来解析出参数名,最后判断得到的参数名是否已经存在mavContainer中,如果不存在则将其和返回值添加到mavContainer中。第三步是遍历既注释了@ModelAttribute又在@SessionAttributes注释中的参数,判断是否已经存在mavContainer里了,如果没有则使用SessionAttributesHandler从整个SessionAttributes中获取(第一步获取的是当前处理器保存到SessionAttributes的属性),如果可以获取到则设置到mavContainer中,如果获取不到则抛出异常。这里获取同时有@ModelAttribute注释又在SessionAttributes注释中的参数的方法是findSessionAttribute-Arguments,代码如下:

private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {List<String> result = new ArrayList<>();for (MethodParameter parameter : handlerMethod.getMethodParameters()) {if (parameter.hasParameterAnnotation(ModelAttribute.class)) {String name = getNameForParameter(parameter);Class<?> paramType = parameter.getParameterType();if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, paramType)) {result.add(name);}}}return result;
}

它的逻辑是遍历方法里的每个参数,如果有@ModelAtribute注释则获取到它对应的参数名,然后用获取到的参数名和参数的类型检查是不是在@SessionAtributs 释中,如果在则符合要求。获取@ModelAtribute注释的参数对应参数名的方法是getNameForParameter,代码如下:

public static String getNameForParameter(MethodParameter parameter) {ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);String name = (ann != null ? ann.value() : null);return (StringUtils.hasText(name) ? name :Conventions.getVariableNameForParameter(parameter));
}
public static String getVariableNameForParameter(MethodParameter parameter) {Assert.notNull(parameter, "MethodParameter must not be null");Class<?> valueClass;boolean pluralize = false;String reactiveSuffix = "";if (parameter.getParameterType().isArray()) {valueClass = parameter.getParameterType().getComponentType();pluralize = true;}else if (Collection.class.isAssignableFrom(parameter.getParameterType())) {valueClass = ResolvableType.forMethodParameter(parameter).asCollection().resolveGeneric();if (valueClass == null) {...抛出异常}pluralize = true;}else {valueClass = parameter.getParameterType();ReactiveAdapterRegistry reactiveAdapterRegistry =ReactiveAdapterRegistry.getSharedInstance();if (reactiveAdapterRegistry.hasAdapters()) {ReactiveAdapter adapter = reactiveAdapterRegistry.getAdapter(valueClass);if (adapter != null && !adapter.getDescriptor().isNoValue()) {reactiveSuffix = ClassUtils.getShortName(valueClass);valueClass = parameter.nested().getNestedParameterType();}}}String name = ClassUtils.getShortNameAsProperty(valueClass);return (pluralize ? pluralize(name) : name + reactiveSuffix);
}

​ 这里跟第二步里的getVariableNameForReturnType方法的逻辑是一样的,就不细讲了。

​ 第三步跟第一步的区别是第一步是将当前处理器中保存的所有SessionAttributes属性合并到了mavContainer,而第三步可以使用其他处理器中保存的SessionAttributes属性来设置注释了@ModelAttribute的参数。

​ 这三步就讲完了,最后再总结一下,首先将当前处理器中保存的所有SessionAttributes 属性合并到mavContainer,然后执行注释了@ModelAttribute的方法并将结果合并到mavContainer,最后检查注释了@ModelAttribute而且在@SessionAttributes中也设置了的参数是否已经添加到了mavContainer中,如果没有则从整个SessionAttributes中获取出来并设置进去,如果获取不到则抛出异常。整个过程的重点和难点是获取设置到Model中的参数名的规则。

​ 从这里可以看出Model中参数的优先级是这样的:

  1. FlashMap中保存的参数优先级最高,它在ModelFactory前面执行; 2
  2. SessionAttributes中保存的参数的优先级第二,它不可以覆盖FlashMap中设置的参数;
  3. 通过注释了@ModelAttribute的方法设置的参数优先级第三;
  4. 注释了@ModelAttribute而且从别的处理器的SessionAttributes中获取的参数优先级最低。而且从前面创建ModelFactory的过程可以看出,注释了@ModelAttribute的方法是全局的优先,处理器自己定义的次之。

2.更新Model

return getModelAndView(mavContainer, modelFactory, webRequest);
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {//这里就是更新modelFactory.updateModel(webRequest, mavContainer);if (mavContainer.isRequestHandled()) {return null;}ModelMap model = mavContainer.getModel();ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());if (!mavContainer.isViewReference()) {mav.setView((View) mavContainer.getView());}if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();HttpServletRequest request =webRequest.getNativeRequest(HttpServletRequest.class);if (request != null) {RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}}return mav;
}
public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {ModelMap defaultModel = container.getDefaultModel();if (container.getSessionStatus().isComplete()){this.sessionAttributesHandler.cleanupAttributes(request);}else {this.sessionAttributesHandler.storeAttributes(request, defaultModel);}if (!container.isRequestHandled() && container.getModel() == defaultModel) {updateBindingResult(request, defaultModel);}
}

这里做了两件事:

  1. 对SessionAttributes进行设置,设置规则是如果处理器里调用了SessionStatustsetComplete则将SessionAttributes清空,否则将mavContainer的defaultModel中相应的参数设置到SessionAttributes中;
  2. 判断请求是否已经处理完或者是redirect类型的返回值,其实也就是判断需要不需要渲染页面,如果需要渲染则给Model中相应参数设置BindingResult

我们知道在处理器中绑定参数时如果参数注释了@Valid或者@Validated,则会将校验结果设置到跟其相邻的下一个Error或者BindingResult类型的参数中。如果参数没有@Valid和@Validated注释但在Model中,而且符合条件(具体条件下面具体讲解),这时为了渲染方便ModelFactory会给Model设置一个跟参数相对应的BindingResult,不过这里设置的BindingResult里面并没有校验的错误值,因为这里并没有调用Binder的validate方法来校验参数。具体设置的方法是updateBindingResult,代码如下:

private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {List<String> keyNames = new ArrayList<>(model.keySet());for (String name : keyNames) {Object value = model.get(name);if (value != null && isBindingCandidate(name, value)) {String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;if (!model.containsAttribute(bindingResultKey)) {WebDataBinder dataBinder = this.dataBinderFactory.createBinder(request,value,name);model.put(bindingResultKey, dataBinder.getBindingResult());}}}
}

这里首先取出Model中保存的所有参数进行遍历,然后通过isBindingCandidate方法判·断是否需要添加BindingResult,如果需要则使用WebDataBinder获取BindingResult并添加到Model,在添加前先检查Model中是否已经存在,如果已经存在就不添加了。判断是否需要添加BindingResult的isBindingCandidate方法代码如下:

private boolean isBindingCandidate(String attributeName, Object value) {if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {return false;}if (this.sessionAttributesHandler.isHandlerSessionAttribute(attributeName,value.getClass())) {return true;}return (!value.getClass().isArray() && !(value instanceof Collection) &&!(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
}

这里判断的逻辑是先判断他本身是不是其他参数绑定结果的BindingResult (通过固定的前缀判断),如果是则不需要再对它添加BindingResult了返回false,然后判断是不是SessionAttributes管理的属性,如果是则返回true,最后检查如果不是空值、数组、CollectionMap和简单类型(如boolean, byte,char, short, int, long、 foat, double, Boolean,Byte.Character, Short, Integer, Long、 Float, Double, Enum, Number, Date等)则返回true添加. BindingResulto也就是说如果不是BindingResult、空值、数组、Collection、Map和简单类型都会返回true,如果是这些类型但是在SessionAttributes中设置了, (除了BindingResult类型)也会返回true,其他情况就返回false。updateModel一共做了两件事,第一件事是维护SessionAttributes的数据,第二件事是给Model中需要的参数设置BindingResult,以备视图使用。

3.多学点

Redirect转发后的参数怎么校验

​ 在处理器中如果想对一个参数进行校验只需要在前面加上@Valid或者@Validated注释就可以了,但是Redirect转发后校验结果就丢失了,因为它默认是保存在ModelAndViewContainer的defaultModel中的,而FlashMap只会传递redirectModel里保存的参数,所以校验结果就会丢失。如果想传递校验参数可以有两种方法,一种是通过SessionAttributes传递,这时只需要在处理器类上注释@SessionAttributes(types={BindingResult.class})即可,这样BindingResult类型的参数就会被保存到SessionAttributes中,在redirect处理器方法中设置SessionStatus#setComplete就可以了。如果使用这种方法一定要及时清空SessionAttributes (给SessionStatus设置Complete),否则可能会造成混乱。另一种方法的思路是重新校验一遍,要使用这种方法首先需要明白Spring MVC是在哪里校验参数的。Spring MVC校验参数是在ModelAttributeMethodProcessor参数解析的时候判断有没有@Valid或者@Validated注释,如果有就会校验, ModelAttributeMethodProcessor解析器用于解析注释了@ ModelAttribute的参数和没注释的非通用类型(如自定义类型)的参数,所以可以在redirect后的处理器中将需要校验的参数写到处理器的参数中并注释@Valid或者@Validated,这种方法更加清晰,而且不容易出问题,所以建议大家尽量使用这种方式。下面来看个例子。

6.ServletInvocableHandlerMethod

继承结构图

​ 可以看出ServletInvocableHandlerMethod其实也是一种HandlerMethod,只是增加了方法执行的功能。当然相应地也增加了参数解析、返回值处理等相关功能。之前一直使用HandlerMethod,但还没有对它的结构进行过分析,本节就从HandlerMethod开始依次对这三个组件进行分析。

1.HandlerMethod

HandlerMethod前面已经介绍过了,用于封装Handler和其中具体处理请求的Method,分别对应其中的bean和method属性,除了这两个还有三个属性: beanFactory, bridgedMethod和parameters, beanFactory主要用于新建HandlerMethod时传入的Handler (也就是bean属性)是String的情况,这时需要使用beanFactory根据传入的String作为beanName获取到对应的bean,并设置为Handler ;bridgedMethod指如果method是bridge method则设置为其所对应的原有方法,否则直接设置为method;parameters代表处理请求的方法的参数。定义如下:

private final Object bean;@Nullable
private final BeanFactory beanFactory;private final Class<?> beanType;
// 参数所在的方法
private final Method method;private final Method bridgedMethod;private final MethodParameter[] parameters;@Nullable
private HttpStatus responseStatus;@Nullable
private String responseStatusReason;@Nullable
private HandlerMethod resolvedFromHandlerMethod;

这里所有的属性都是final的,所以创建后就不可以修改了,前面说的如果Handler是, String类型,将其变为容器中对应bean的过程在专门的方法createWithResolvedBean中来操作的,这里面是通过使用从容器中找到的bean和自己原来的属性新建一个HandlerMethod来完成的,代码如下:

public HandlerMethod createWithResolvedBean() {Object handler = this.bean;if (this.bean instanceof String) {String beanName = (String) this.bean;handler = this.beanFactory.getBean(beanName);}return new HandlerMethod(this, handler);
}
private HandlerMethod(HandlerMethod handlerMethod, Object handler) {this.bean = handler;this.beanFactory = handlerMethod.beanFactory;this.beanType = handlerMethod.beanType;this.method = handlerMethod.method;this.bridgedMethod = handlerMethod.bridgedMethod;this.parameters = handlerMethod.parameters;this.responseStatus = handlerMethod.responseStatus;this.responseStatusReason = handlerMethod.responseStatusReason;this.resolvedFromHandlerMethod = handlerMethod;
}

HandlerMethod中属性的含义除了bridgedMethod外都比较容易理解,只是保存参数的属性parameters使用了大家可能不太熟悉的类型MethodParameter.一个MethodParameter类型的对象表示一个方法的参数,对MethodParameter主要是理解其参数的含义,它们定义如下:

private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];private final Executable executable;
// 参数的序号,也就是第几个参数,从0开始计数。
private final int parameterIndex;@Nullable
private volatile Parameter parameter;
// 嵌套级别,如果是复合参数会用到,比如,有一个ListkString> params
// 参数,则params的嵌套级别为1, List内部的String的嵌套级别为2
private int nestingLevel;/** Map from Integer level to Integer type index保存每层嵌套参数的序数。
*/
@Nullable
Map<Integer, Integer> typeIndexesPerLevel;/** The containing class. Could also be supplied by overriding {@link #getContainingClass()}
容器的类型,也就是参数所属方法所在的类。
*/
@Nullable
private volatile Class<?> containingClass;
// 参数的类型。
@Nullable
private volatile Class<?> parameterType;
// Type型的参数类型,也是参数的类型,但它的类型是Type
@Nullable
private volatile Type genericParameterType;
// 参数注解
@Nullable
private volatile Annotation[] parameterAnnotations;//参数名称查找器
@Nullable
private volatile ParameterNameDiscoverer parameterNameDiscoverer;
// 参数名称
@Nullable
private volatile String parameterName;
//嵌套的方法参数
@Nullable
private volatile MethodParameter nestedMethodParameter;

​ MethodParameter里最重要的是method和parameterIndex,有了这两个参数后参数类型、注释等都可以获取到。不过在正常的反射技术里是不知道参数名的,所以这里专门使用了一个参数名查找的组件parameterNameDiscoverer,它可以查找出我们定义参数时参数的名称,这就给ArgumentResolver的解析提供了方便。

​ 在HandlerMethod中定义了两个内部类来封装参数,一个封装方法调用的参数,一个封装方法返回的参数,它们主要使用method和parameterIndex来创建MethodParameter,封装返回值的Return ValueMethodParameter继承自封装调用参数的HandlerMethodParameter,它们使用的method都是bridgedMethod,返回值使用的parameterlndex是-1,代码如下:

protected class HandlerMethodParameter extends SynthesizingMethodParameter {public HandlerMethodParameter(int index) {super(HandlerMethod.this.bridgedMethod, index);}protected HandlerMethodParameter(HandlerMethodParameter original) {super(original);}@Overridepublic Class<?> getContainingClass() {return HandlerMethod.this.getBeanType();}@Overridepublic <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {return HandlerMethod.this.getMethodAnnotation(annotationType);}@Overridepublic <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {return HandlerMethod.this.hasMethodAnnotation(annotationType);}@Overridepublic HandlerMethodParameter clone() {return new HandlerMethodParameter(this);}
}

多知道点

什么是bridge method

Bridge method是Java里的一个概念,不过正常情况下使用得并不多。在java说明书《The Java LanguageSpecification 》中"Method Invocation Expressions"-节介绍了bridgemethod。举例如下:

​ 在C类中定义了泛型,子类D中给泛型设置了String,使用时新建了D类型的C,调用时由于C中泛型没有指定具体的类型,所以可以给它的id方法传入任意类型的参数,所以上面的代码中传入Object编译并没有问题,但实际使用的是D类型的实例, D给泛型设置了String,这在运行时就出错了。

​ 在Java虚拟机中其实会给D创建两个id方法,除了我们定义的String为参数的id方,法,还会创建一个Object做参数的方法(这一点也可以通过调用D.class.getMethods)看到,只是D中的id方法需要是public的),创建的方法如下:

object id(object x) { return id((string). x); }

​ 这个Object为参数的方法就叫桥方法(bridge method),它作为一个桥将Object为参数的调用转换到了String为参数的方法。在HandlerMethod中的bridgedMethod指的是被桥的方法(注意是bridged而不是bridge),也就是原来的方法。比如, HandlerMethod中的method如果是Object类型的id方法, bridgedMethod就是String类型的id方法,如果method是String类型的id方法,bridgedMethod将和method代表同一个方法,如果不涉及泛型bridgedMethod和method都是同一个方法。在spring中获取原方法bridgedMethod是通过BridgeMethodResolver… findBridgedMethod来完成的,大概思路是按相同的方法名和相同的参数个数找不是桥的方法。

2.InvocableHandlerMethod

InvocableHandlerMethod继承自HandlerMethod,在父类基础上添加了调用的功能,也就是说InvocableHandlerMethod可以直接调用内部属性method对应的方法(严格来说应该是bridgedMethod)。

@Nullable
private WebDataBinderFactory dataBinderFactory;private HandlerMethodArgumentResolverComposite argumentResolvers = new                                                                  HandlerMethodArgumentResolverComposite();private ParameterNameDiscoverer parameterNameDiscoverer = new                                                                             DefaultParameterNameDiscoverer();
  • dataBinderFactory:WebDataBinderFactory类型,可以创建WebDataBinder,用于参数解析器ArgumentResolver中。
  • argumentResolvers:HandlerMethodArgumentResolverComposite类型,用于解析参数。
  • parameterNameDiscoverer:ParameterNameDiscoverer类型,用来获取参数名,用于MethodParameter中。
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);...打印日志Object returnValue = doInvoke(args);...打印日志return returnValue;
}

​ 这个方法非常简单,除了日志打印就两条代码了,一条是准备方法所需要的参数,使用的是getMethodArgumentValues方法,另一条用于具体调用Method,具体使用的方法是dolnvoke方法。dolnvoke方法是实际执行请求处理的方法,我们写的代码都是通过它来执行的,所以这个方法是整个HandlerMethod系列的处理器中最核心的方法,我们先来看一下

1.getMethodArgumentValues
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 得到当前方法的参数MethodParameter[] parameters = getMethodParameters();Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];// 初始化参数名称查找器parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);// 转换Provided参数args[i] = resolveProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (this.argumentResolvers.supportsParameter(parameter)) {try {// 这里才是真正的解析出参数// 进去看看args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);continue;}...抛出异常和打印日志}if (args[i] == null) {...抛出异常}}return args;
}
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 这里其实挺简单的,就是从缓存中获取一下// 如果缓存中没有 添加一下HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);if (resolver == null) {...抛出异常}// 这个才是核心return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception{NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();Object resolvedName = resolveStringValue(namedValueInfo.name);if (resolvedName == null) {...抛出异常}Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = resolveStringValue(namedValueInfo.defaultValue);}else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveStringValue(namedValueInfo.defaultValue);}if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}...捕捉异常并抛出}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;
}

​ 通过注释大家就容易理解了,这里首先调用父类的getMethodParameters方法获取到Method的所有参数,然后定义了Object数组变量args用于保存解析出的参数值。接下来遍历每个参数进行解析,解析的方法有两种,第一种是在providedArgs里面找,第二种是使用argumentResolvers解析,在RequestMappingHandlerAdapter中的调用并没有提供providedArgs,所以只有使用argumentResolvers解析。每个参数在解析前都初始化了三个属性: parameterNameDiscoverer, containingClass和parameterType, parameterNameDiscoverer用于获取参数名,可以在RequestMappingHandlerAdapter定义时配置,默认使用DefaultParameterNameDiscoverero containingClass和parameterType在前面已经介绍过了,分别表示容器类型(也就是所属的类)和参数类型。如果没有解析出参数值最后会抛出llegalStateException异常

​ InvocableHandlerMethod就分析完了,它就是在HandlerMethod的基础上添加的方法调用的功能,而方法调用又需要解析参数,所以又提供了解析参数的功能。实际上前面说过的注释了@InitBinder的方法和注释了@ModelAttribute的方法就是封装成了InvocableHandlerMethod对象,然后直接执行的。

2.doInvoke(args);
protected Object doInvoke(Object... args) throws Exception {ReflectionUtils.makeAccessible(getBridgedMethod());try {// 直接反射执行return getBridgedMethod().invoke(getBean(), args);}...全是异常处理
}

除了异常处理外,真正执行的方法就是直接调用bridgedMethod的invoke方法,在调用·前先使用ReflectionUtis.makeAccessible强制将它变为可调用,也就是说即使我们定义的处理器方法是private的也可以被调用。

3.ServletInvocableHandlerMethod

ServletinvocableHandlerMethod继承自InvocableHandlerMethod,在父类基础上增加了三个功能:

  • 对@ResponseStatus注释的支持;
  • 对返回值的处理;
  • 对异步处理结果的处理。

@ResponseStatus注释用于处理器方法或者返回值上,作用是对返回Response的Status进行设置,它有两个参数: value和reason, value是HttpStatus类型,不能为空, reason是String,类型,表示错误的原因,默认为空字符串(不是null)。当一个方法注释了@ResponseStatus后,返回的response会使用注释中的Status,如果处理器返回值为空或者reason不为空,则将中断处理直接返回(不再渲染页面)。实际环境中用的并不是很多。

对返回值的处理是使用return ValueHandlers属性完成的,它是HandlerMethodReturnValueHandler类型的属性。异步处理使用了两个内部子类,异步处理的相关内容后面专门讲解。ServletiInvocableHandlerMethod处理请求使用的是invokeAndHandle方法,代码如下:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 这里就已经执行了方法了,里面就是参数解析执行方法啥的Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}...抛出异常和打印日志
}

首先调用父类的invokeForRequest执行请求,接着处理@ResponseStatus注释,最后处理返回值。处理@ResponseStatus注释的方法是setResponsestatus,它会根据注释的值设置response的相关属性,代码如下:

private void setResponseStatus(ServletWebRequest webRequest) throws IOException {HttpStatus status = getResponseStatus();if (status == null) {return;}HttpServletResponse response = webRequest.getResponse();if (response != null) {String reason = getResponseStatusReason();if (StringUtils.hasText(reason)) {response.sendError(status.value(), reason);}else {response.setStatus(status.value());}}// To be picked up by RedirectView// 设置到request的属性,为了在redirect中使用webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}

​ 处理返回值的逻辑是先判断返回值是不是null,如果是null,只要request的notModified为真、注释了@ResponseStatus和mavContainer的requestHandled为true这三项中有一项成立则设置为请求已经处理并返回,如果返回值不为null,而@ResponseStatus注释里存在reason,也会将请求设置为已处理并返回。设置已处理的方法前面已经讲过,就是设置mavContainer的requestHandled属性为true,如果上面那些条件都不成立则将mavContainer的requestHandled设置为false,并使用return ValueHandlers处理返回值

​ 到这里调用处理器处理请求的过程就讲完了。接下来分析解析参数的HandlerMethodArgumentResolver和处理返回值的HandlerMethodReturn ValueHandler.

7.HandlerMethodArgumentResolver

HandlerMethodArgumentResolver是用来为处理器解析参数的,主要用在前面讲过的InvocableHandlerMethod中。每个Resolver对应一种类型的参数,所以它的实现类非常多,其继承结构如图所示。

​ 这里有一个实现类比较特殊,那就是HandlerMethodArgumentResolverComposite,它不具体解析参数,而是可以将多个别的解析器包含在其中,解析时调用其所包含的解析器具体解析参数,这种模式在前面已经见过好几次了,在此就不多说了。下面来看-下 HandlerMethodArgumentResolver接口的定义:

public interface HandlerMethodArgumentResolver {// 是否可以解析传入的参数boolean supportsParameter(MethodParameter parameter);// 实际解析参数@NullableObject resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;}

​ HandlerMethodArgumentResolver实现类一般有两种命名方式,一种是XXXMethodArgumentResolver,另一种是XXXMethodProcessor,前者表示一个参数解析器,后者除了可以解析参数外还可以处理相应类型的返回值,也就是同时还是后面要讲到的HandlerMethod-Return ValueHandle,其中的XXX表示用于解析的参数的类型。另外,还有个Adapter,它也不是直接解析参数的,而是用来兼容WebArgumentResolver类型的参数解析器的适配器。

  • AbstractMessageConverterMethodArgumentResolver:使用HtpMessageConverter解析request body类型参数的基类。

  • AbstractMessageConverterMethodProcessor:定义相关工具,不直接解析参数。

  • HttpEntityMethodProcessor:解析HtpEntity和 RequestEntity类型的参数。

  • RequestResponseBodyMethodProcessor:解析注释@RequestBody类型的参数。

  • RequestPartMethodArgumentResolver:解析注释了@RequestPart, MultipartFile类型以及javax.servlet.http.Part 类型的参数。

  • AbstractNamedValueMethodArgumentResolver:解析namedValue类型的参数(有name的参数,如cookie requestParam 、requestHeader,path Variable等)的基类,主要功能有:

    • 获取name ;
    • resolveDefaultValue, handleMissing Value, handleNullvalue ;
    • 调用模板方法resolveName, handleResolvedValue具体解析。
  • AbstractCookieValueMethodArgumentResolver:解析注释了@CookieValue的参数的基类。

  • ServletCookieValueMethodArgumentResolver :实现resolveName方法,具体解析cookieValue

  • ServletCookieValueMethodArgumentResolver :实现resolveName方法,具体解析cookie

  • ValueExpressionValueMethodArgumentResolver :解析注释@Value表达式的参数,主要设置了 beanFactory,并用它完成具体解析,解析过程在父类完成。

  • MatrixVariableMethodArgumentResolver :解析注释@MatrixVariable而且不是Map类型的参数(Map类型使用MatrixVariableMapMethodArgumentResolver解析)。

  • PathVariableMethodArgumentResolver :解析注释@PathVariable而且不是Map类型的参数(Map类型则使用PathVariableMapMethodArgumentResolver解析)。

  • RequestHeaderMethodArgumentResolver :解析注释了@RequestHeader而且不是Map类型的参数(Map类型则使用RequestHeaderMapMethodArgumentResolver解析)。

  • RequestParamMethodArgumentResolver :可以解析注释了@RequestParam的参数MultipartFile类型的参数和没有注释的通用类型(如int, long等)的参数。如果是注释了@RequestParam的Map类型的参数,则注释必须有name值(否则使用RequestParamMapMethodArgumentResolver解析)。

  • AbstractWebArgumentResolverAdapter:用作WebArgumentResolver解析器的适配器

  • ServletWebArgumentResolverAdapte:给父类提供了requestoErrorsMethodArgumentResolver :解析Errors类型的参数(一般是Errors或BindingResult),当一个参数绑定出现异常时会自动将异常设置到其相邻的下一个Errors类型的参数,设置方法就是使用了这个解析器,内部是直接从model中获取的。

  • HandlerMethodArgumentResolverComposite : argumentResolver的容器,可以封装多个Resolver,具体解析由封装的Resolver完成,主要为了方便调用。

  • MapMethodProcessor :解析Map型参数(包括ModelMap类型),直接返回mavContainer中的model

  • MatrixVariableMapMethodArgumentResolver :解析注释了@MatrixVariable的Map类型参数。

  • ModelAttributeMethodProcessor :解析注释了@ ModelAttribute的参数,如果其中的annotationNotRequired属性为true还可以解析没有注释的非通用类型的参数(RequestParamMethodArgumentResolver解析没有注释的通用类型的参数)。

  • ServletModelAttributeMethodProcessor :对父类添加了Servlet特性,使用ServletRequestDataBinder代替父类的WebDataBinder进行参数的绑定。

  • ModelMethodProcessor:解析Model类型参数,直接返回mavContainer中的model

  • PathVariableMapMethodArgumentResolver :解析注释了@PathVariable的Map类型的参数。

  • RedirectAttributesMethodArgumentResolver :解析RedirectAttributes类型的参数,新建RedirectAttributesModelMap类型的RedirectAttributes并设置到mavContainer中,然后返回给我们的参数。

  • RequestHeaderMapMethodArgumentResolver :解析注释了@RequestHeader的Map类型的参数。

  • RequestParamMapMethodArgumentResolver:解析注释了@RequestParam的Map类型,而且注释中有value的参数。

  • ServletRequestMethodArgumentResolver :解析WebRequest, ServletRequest, MultipartRequest, HttSession, Principal, Locale, TimeZone, InputStream, Reader, HttpMethod类型和"java.time.Zoneld"类型的参数,它们都是使用request获取的。

  • ServletResponseMethodArgumentResolver :解析ServletResponse,OutputStream, Writer类型的参数

  • SessionStatusMethodArgumentResolver :解析SessionStatus类型参数,直接返回mavContainer中的SessionStatus。

  • UriComponentsBuilderMethodArgumentResolver : 解析UriComponentsBuilder类型的参数。

    下面来分析一下解析Model类型参数的ModelMethodProcessor解析器和解析注释了@Path Variable的参数类型的PathVariableMethodArgumentResolver解析器,这两种类型的参数使,用得非常多。ModelMethodProcessor既可以解析参数也可以处理返回值,下面为解析参数相关的代码:

1.ModelMethodProcessor

public class ModelMethodProcessor implements HandlerMethodArgumentResolver,HandlerMethodReturnValueHandler {@Overridepublic boolean supportsParameter(MethodParameter parameter) {return Model.class.isAssignableFrom(parameter.getParameterType());}@Override@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)throws Exception{return mavContainer.getModel();}@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return Model.class.isAssignableFrom(returnType.getParameterType());}@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {if (returnValue == null) {return;}else if (returnValue instanceof Model) {mavContainer.addAllAttributes(((Model) returnValue).asMap());}else {...抛出异常}}}

可以看到这个实现非常简单, supportsParameter方法判断如果是Model类型就可以,resolveArgument方法是直接返回mavContainer里的Model,通过前面的分析知道,这时的Model可能已经保存了一些值,如SessionAttribute中的值、FlashMap中的值等,所以在处理器中如果定义了Model类型的参数,给我们传的Model中可能已经保存的有值了。

2.PathVariableMethodArgumentResolver

​ 再来看一下Path VariableMethodArgumentResolver,它用于解析url路径中的值,它的实现比较复杂,它继承自AbstractNamedValueMethodArgumentResolver,前面介绍过这个解析器是处理namedValue类型参数的基类, cookie, requestParam,requestHeader, pathVariable等类型参数的解析器都继承自它,它跟我们之前讲过的很多组件一样,使用的也是模板模式。其中没有实现supportsParameter方法,只实现了resolveArgument方法,代码如下:

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception{NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();Object resolvedName = resolveStringValue(namedValueInfo.name);if (resolvedName == null) {...抛出异常}Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = resolveStringValue(namedValueInfo.defaultValue);}else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = handleNullValue(namedValueInfo.name, arg,nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveStringValue(namedValueInfo.defaultValue);}if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}...处理异常}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;
}

8.HandlerMethodReturnValueHandler

还是首先看一下继承关系:

​ HandlerMethodReturnValueHandler用在ServletInvocableHandlerMethod中,作用是处理处理器执行后的返回值,主要有三个功能:

  • 将相应参数添加到Model ;
  • 设置View ;
  • 如果请求已经处理完则设置ModelAndViewContainer的requestHandled为true

​ HandlerMethodReturn ValueHandler的使用方式与HanderMethodArgumentResolver非常相似,接口里也是两个方法,一个用于判断是否支持,一个用于具体处理返回值,接口定义如下:

public interface HandlerMethodReturnValueHandler {boolean supportsReturnType(MethodParameter returnType);void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;}

实现类中也有一个特殊的类,那就是HandlerMethodReturn ValueHandlerComposite,它和前面说的HandlerMethodArgumentResolverComposite功能相同, 自己不具体处理返回值,而是使用内部封装的组件进行处理。其他实现类都和具体返回值类型相对应。

  • AbstractMessageConverterMethodProcessor:处理返回值需要使用HttpMessageConverter写入esponse的基类, 自己并未具体做处理,而是定义了相关工具。
  • HttpEntityMethodProcessor :处理HttpEntity类型,并且不是RequestEntity类型的返回值。
  • RequestResponseBodyMethodProcessor :处理当返回值或者处理请求的Handler类注释了@ResponseBody情况下的返回值。Async TaskMethodReturn ValueHandler :处理WebAsyncTask类型的返回值,用于异步请求,使用WebAsyncManager完成。
  • CallableMethodReturn ValueHandler :处理Callable类型的返回值,用于异步请求,使用WebAsyncManager完成。
  • DeferredResultMethodReturnValueHandler :处理DeferredResult类型的返回值,用于异步请求,使用WebAsyncManager完成。
  • HandlerMethodReturnValueHandlerComposite:用于封装其他处理器,方便调用。
  • HttpHeadersReturnValueHandler :处理HttpHeaders类型的返回值,将HttpHeaders的返回值添加到response的Headers并设置mavContainer的requestHandled为true.
  • ListenableFutureReturnValueHandler :处理ListenableFuture类型的返回值,用于异步请求,使用WebAsyncManager完成。
  • MapMethodProcessor:处理Map类型的返回值,将Map添加到mavContainer的Model中
  • ModelAndViewMethodReturnValueHandler :处理ModelAndView类型的返回值,将返回值中的View和Model设置到mavContainer中
  • ModelAndViewResolverMethodReturnValueHandler :可以处理所有返回值,一般设置在最后一个,当别的处理器都不能处理时使用它处理。它内部封装了一个List类型的ModelAndViewResolver和一个annotationNotRequired为true的ModelAttributeMethodProcessor, ModelAndViewResolver是一个将返回值解析为ModelAndView类型的通用接口,可以自定义后配置到RequestMappingHandlerAdapter中。处理返回值时先遍历所有的ModelAndViewResolver进行处理,如果有可以处理的,则用它处理并将结果返回,如果都无法处理则调用ModelAttributeMethodProcessor进行处理。
  • ModelAttributeMethodProcessor :处理注释了@ ModelAttribute类型的返回值,如果annotationNotRequired为true还可以处理没有注释的非通用类型的返回值。‘
  • ServletModelAttributeMethodProcessor :对返回值的处理同父类,这里只是修改了参数解析的功能,未对返回值处理功能做修改Model
  • MethodProcessor :处理Model类型返回值,将Model中的值添加到mavContainer的Model中。
  • ViewMethodReturnValueHandler :处理View类型返回值,如果返回值为空直接返回,否则将返回值设置到mavContainer的View中,并判断返回值是不是redirect类型,如果是则设置mavContainer的redirectModelScenario为true
  • ViewNameMethodReturnValueHandler :处理void和String类型返回值,如果返回值为空则直接返回,否则将返回值通过mavContainer的setViewName方法设置到其View中,并判断返回值是不是redirect类型,如果是则设置mavContainer的redirectModelScenario为true

返回值处理器的实现非常简单,这里分析一下使用得最多的ViewNameMethodReturnValueHandler。

public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {@Nullableprivate String[] redirectPatterns;public void setRedirectPatterns(@Nullable String... redirectPatterns) {this.redirectPatterns = redirectPatterns;}@Nullablepublic String[] getRedirectPatterns() {return this.redirectPatterns;}@Overridepublic boolean supportsReturnType(MethodParameter returnType) {Class<?> paramType = returnType.getParameterType();return (void.class == paramType ||CharSequence.class.isAssignableFrom(paramType));}@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {if (returnValue instanceof CharSequence) {String viewName = returnValue.toString();mavContainer.setViewName(viewName);if (isRedirectViewName(viewName)) {mavContainer.setRedirectModelScenario(true);}}else if (returnValue != null) {。。。抛出异常}}protected boolean isRedirectViewName(String viewName) {return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) ||viewName.startsWith("redirect:"));}}

​ supportsReturnType里通过判断返回值是不是Void或String类型来确定是否支持这种类型返回值的处理。具体处理的handleReturnValue方法先判断返回值是否为null,如果是则直接返回,否则设置到mavContainer的View中,并判断返回值是不是redirect类型,如果是则设置mavContainer的redirectModelScenario为true。如果既不为null也不是String类型则抛异常,不过因为只有void和string两种类型的返回值才可以进来,所以正常不会抛异常。

​ 这里的“是不是redirect类型”专门使用了一个isRedirectViewName方法进行判断,在这个方法中首先使用redirectPatterns 判断是否匹配,如果都不匹配在看是不是以“redirect:"开头,这里的redirectPatterns可以自己来设置,是一个String数组,也就是可以设置多个redirect匹配模板,不过这里匹配为redirect类型后只是给mavContainer的redirectModelScenario标志设置为了true,并不会做实际处理,如果想使用还需要定义相应的View,挺麻烦的,一般来说默认的就够了,不过这个属性是Spring 4.1新增的,可能以后会有相应的用途。

SpringMVC源码分析(二)相关推荐

  1. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  2. SpringMVC源码分析(4)剖析DispatcherServlet重要组件

    简单介绍了一个请求的处理过程, 简略描述了调用过程,并没有涉及过多细节,如url匹配,报文解析转换等. <SpringMVC源码分析(2)DispatcherServlet的初始化>:介绍 ...

  3. SpringBoot源码分析(二)之自动装配demo

    SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...

  4. gSOAP 源码分析(二)

    gSOAP 源码分析(二) 2012-5-24 flyfish 一 gSOAP XML介绍 Xml的全称是EXtensible Markup Language.可扩展标记语言.仅仅是一个纯文本.适合用 ...

  5. springMVC源码分析--访问请求执行ServletInvocableHandlerMethod和InvocableHandlerMethod

    在之前一篇博客中 springMVC源码分析--RequestMappingHandlerAdapter(五)我们已经简单的介绍到具体请求访问的执行某个Controller中的方法是在RequestM ...

  6. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现

    写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...

  7. 【投屏】Scrcpy源码分析二(Client篇-连接阶段)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  8. 简单直接让你也读懂springmvc源码分析(3.1)-- HandlerMethodReturnValueHandler

    该源码分析系列文章分如下章节: springmvc源码分析(1)-- DispatcherServlet springmvc源码分析(2)-- HandlerMapping springmvc源码分析 ...

  9. SpringMVC源码分析_框架原理图

                                                                                 SpringMVC源码分析_框架原理图     ...

  10. SpringMVC源码分析_1 SpringMVC容器启动和加载原理

                                                                    SpringMVC源码分析_1 SpringMVC启动和加载原理     ...

最新文章

  1. 如何高效的利用博客园?
  2. redis持久化的几种方式
  3. CSS position 属性
  4. 三维家可以导入别人的方案吗_三维激光扫描仪
  5. java jersey client,如何在Jersey-Client Java中实现重试机制
  6. javaScript输出指定的时间格式
  7. CCF202009-2 风险人群筛查
  8. 若微型计算机在工作时突然断电,16秋《计算机基础》作业1
  9. illegal text-relocation
  10. 【渝粤教育】国家开放大学2018年春季 0704-22T民法基础与实务 参考试题
  11. java 模式匹配_Java 14 模式匹配,非常赞的一个新特性!
  12. linux c语言播放midi,C语言实现--计算MIDI音符的频率
  13. blender风格化草地
  14. echarts2 的引入方式
  15. 利用扭力仪来检测特小公斤数电批输出扭矩
  16. 单维度量表验证性因子分析_(超详细)验证性因子分析步骤讲解
  17. 51单片机的键盘分类和运用
  18. Jenkins搭建Maven项目集成开发环境(源码库 Gitlab)
  19. 程序员应该了解的计算机知识(一)——基础理论
  20. java爬取论坛信息_Java爬取校内论坛新帖

热门文章

  1. 互联网快讯:粉笔科技OMO模式打造竞争壁垒;猿辅导学习闭环满足用户多元化需求;饿了么推出无人值守补给站
  2. Net core学习系列(一)——Net Core介绍
  3. 电子邮件(SMTP、MIME、POP3、IMAP)
  4. DJ14 简单接口电路及应用
  5. G761-3005B伺服阀放大器
  6. html svg 移动缩放,SVG缩放,移动,倾斜和旋转变换
  7. MYSQL实践基础-快速入门学习-纪录
  8. JAVA蘑菇西餐,小丫厨房:十分钟就可搞定西餐 蒜蓉蘑菇八步简单做
  9. 【Golang开发面经】知乎(两轮技术面)
  10. 企业能耗在线监测系统