前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【工作篇】再次熟悉 SpringMVC 参数绑定

【工作篇】再次熟悉 SpringMVC 参数绑定

作者头像
玖柒的小窝
发布于 2021-12-17 15:04:21
发布于 2021-12-17 15:04:21
68600
代码可运行
举报
文章被收录于专栏:各类技术文章~各类技术文章~
运行总次数:0
代码可运行

前言

主要现在项目中使用的参数绑定五花八门的,搞得很头大,例如有些用字符串接收日期,用字符串接受数组等等,完全没有利用好 SpringMVC 的优势,这里自己也总结一下,免得到时又要百度谷歌查找。

以下实践的 Spring 版本是:5.2.7.RELEASE

一、SpringMVC 中不同类型的数据绑定

1.1、基础数据类型
  • 默认参数名
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码123456JAVA  // http://localhost:8080/baseType3?a=123
  @GetMapping("/baseType")
  @ResponseBody
  public String baseType(int a) {
      return "baseType " + a;
  }
  • 使用@RequestParam 自定义请求参数名称
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码123456JAVA  //  http://localhost:8080/baseType3?b=123
  @GetMapping("/baseType3")
  @ResponseBody
  public String baseType3(@RequestParam(value = "b", required = true) Integer a) {
      return "baseType3 " + a;
  }
  • 多个参数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345JAVA  //  http://localhost:8080/baseType4?age=10&name=Java
  @GetMapping("/baseType4")
  public String baseType3(@RequestParam Integer age, String name) {
      return "baseType4  age:" + age + "  name="+name;
  }
1.2、 对象类型

超过三个参数及以上,则推荐使用对象来接收传递的参数

  • 定义简单对象接收参数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011JAVA  @Data //这里使用了 lombok 插件
  public class User {
      Integer id;
      String name;
  }

  // http://localhost:8080/objectType?id=1&name=Java
  @GetMapping("/objectType")
  public String objectType(User user) {
      return "objectType " + user;
  }
  • 内嵌对象接收参数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011JAVA  @Data
  public class Order {
      Integer id;
      User user;
  }

  // http://localhost:8080/objectType2?id=1&user.name=Java&user.id=2
  @GetMapping("/objectType2")
  public String objectType2(Order order) {
      return "objectType2 " + order;
  }
  • 使用 DataBinder 解决不同对象,参数名相同覆盖问题
    • 定义对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011JAVA  @Data
  public class Friend {
      Integer id;
      String name;  //与User 对象name 名称冲突
  }
  
  @Data
  public class User {
      Integer id;
      String name;
  }
  • InitBinder 配置 在 Controller 中定义,只对当前 Controller 有效,也可以在 @ControllerAdvice 类中全局定义
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678910111213141516171819JAVA/**
     * 初始化绑定参数user 标识前缀
     *
     * @param binder
     */
@InitBinder("user")
public void initBinderUser(WebDataBinder binder) {
    binder.setFieldDefaultPrefix("user.");
}

/**
     * 初始化绑定参数friend 标识前缀
     *
     * @param binder
     */
@InitBinder("friend")
public void initBinderFriend(WebDataBinder binder) {
    binder.setFieldDefaultPrefix("friend.");
}
  • 编写请求
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码123456JAVA//http://localhost:8080/objectType3?name=Java  name会同时填充到User 和Friend对象上
//http://localhost:8080/objectType3?user.name=Java&friend.name=Python 分别填充数据到各自的对象中去
@GetMapping("/objectType3")
public String objectType3(User user, Friend friend) {
    return "objectType3  user" + user + "  friend " + friend;
}
1.3、 日期类型

日期类型的参数传递方式比较多,正式项目中建议统一规定日期类型的参数绑定的格式

1.3.1、使用时间戳传递(不是参数绑定方式)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345JAVA// http://localhost:8080/dateType6?date=1628752881
@GetMapping("/dateType6")
public String dateType5(Long date) {
	return "dateType6  date" + new Date(date);
}
1.3.2、使用字符串接收(不是参数绑定方式)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345JAVA// http://localhost:8080/dateType7?date=2021-08-12
@GetMapping("/dateType7")
public String dateType7(String date) throws ParseException {
    return "dateType7  date" + new SimpleDateFormat("yyyy-MM-dd").parse(date);
}
1.3.3、使用 SpringMVC 默认提供的 @DateTimeFormat (对于 json 参数无效)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345JAVA// http://localhost:8080/dateType2?date1=2020-01-01
@GetMapping("/dateType2")
public String dateType2(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date1) {
    return "dateType2  date " + date1;
}
1.3.4、使用 @InitBinder 注册转换器
  • 添加转换器
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码123456789101112131415161718192021JAVA    /**
     * 注册日期转换 date
     *
     * @param binder
     */
    @InitBinder
    public void initBinderDate(WebDataBinder binder) {
        binder.addCustomFormatter(new Formatter<Date>() {
            @Override
            public Date parse(String text, Locale locale) throws ParseException {
                System.out.println("InitBinder addCustomFormatter String to Date  ");
                return new SimpleDateFormat("yyyy-MM-dd").parse(text);
            }

            @Override
            public String print(Date date, Locale locale) {
                System.out.println("InitBinder addCustomFormatter  Date to String  ");
                return new SimpleDateFormat("yyyy-MM-dd").format(date);
            }
        });
    }
  • 请求
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345JAVA// http://localhost:8080/dateType?date=2020-01-01
@GetMapping("/dateType")
public String dateType(Date date) {
    return "dateType  date" + date;
}
1.3.5、全局配置 Formatter

对于 json 参数(@RequestBody 修饰的参数)无效

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011121314JAVA@Configuration
public class WebConfig implements WebMvcConfigurer {

    /**
     * 注册 Converters 和 Formatters
     *
     * @param registry
     */
        @Override
    public void addFormatters(FormatterRegistry registry) {
        //参数传出格式化
        registry.addFormatter(new DateFormatter("yyyy-MM-dd"));
    }
}
1.3.6、@JsonFormat 单独配置字段格式化

只对 @RequestBody 修饰的参数有效

  • 定义实体
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码123456JAVA@Data
public class UserDate {

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM")
    private Date birthday;
}
  • 请求
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011JAVA/**
  * http://localhost:8080/dateType4
  * {
  *     "birthday": "2020-08"
  * }
*/
@PostMapping("/dateType4")
@ResponseBody
public UserDate dateType4(@RequestBody UserDate userDate) {
    return userDate;
}
1.3.7、全局配置 JSON 参数日期格式化

注意: 全局配置后,依然可以使用 @JsonFormat 注解,用来接收特殊的日期参数格式。

  • 配置
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码123456789101112JAVA@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                //指定时区
                .timeZone(TimeZone.getTimeZone("GMT+8:00"))
                //日期格式化
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        converters.add(0, new MappingJackson2HttpMessageConverter(builder.build()));
    }
}
  • 实体
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678JAVA@Data
public class UserDate {

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM")
    private Date birthday;

    private Date date;
}
  • 请求
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678910111213JAVA/**
  * http://localhost:8080/dateType4
  * {
  *     "birthday": "2020-08",
  *     "date": "2021-08-13"
  * }
*/
@PostMapping("/dateType4")
@ResponseBody
public UserDate dateType4(@RequestBody UserDate userDate) {
    return userDate;
}
1.4、 复杂类型

复杂类型包括数组和集合类型,像 List、Set、Map。以下以 List 为例

  • 使用逗号分割形式
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码123456789JAVA    /**
     * 请求形式
     * http://localhost:8080/complexType2_1?list=1,2,3
     */
    @GetMapping("/complexType2_1")
    public String complexType2_1(@RequestParam("list") List<String> list) {
        return "complexType2_1 " + list;
    }
  • 相同参数明传递多次
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678JAVA    /**
     * 请求形式
     * http://localhost:8080/complexType2?list=1&list=2
     */
    @GetMapping("/complexType2")
    public String complexType2(@RequestParam("list") List<String> list) {
        return "complexType2 " + list;
    }
  • 使用 JSON 字符串传递
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011JAVA/**
     * 请求形式
     * http://localhost:8080/complexType4
     * <p>
     * 请求体
     * [1,2,3]
     */
@PostMapping("/complexType4")
public String complexType4(@RequestBody List<String> list) {
    return "complexType4 " + list;
}
1.5、 特殊类型
  • xml
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011121314151617181920JAVA@Data
@XmlRootElement(name ="user")
public class User {
    Integer id;
    String name;
}

/**
     * http://localhost:8080/xmlType

 <?xml version="1.0" encoding="utf-8"?>
 <user>
 <id>1</id>
 <name>Java</name>
 </user>
     */
@PostMapping(path = "/xmlType", consumes = "application/xml;charset=UTF-8")
public String xmlType(@RequestBody User user) {
    return "xmlType " + user;
}
  • json
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678910111213141516JAVA
/**
     * 请求
     * http://localhost:8080/jsonType
     * 请求体
{
    "id": 1,
    "name": "Java"
}
     *
     * @RequestBody 不支持GET请求
     */
@PostMapping(value = "/jsonType", consumes = "application/json")
public String jsonType(@RequestBody User user) {
    return "jsonType " + user;
}

二、了解底层实现

2.1、SpringMVC 方法参数绑定
2.1.1、认识 HandlerMethodArgumentResolver 接口
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678910JAVApublic interface HandlerMethodArgumentResolver {

	//该解析器是否支持parameter参数的解析
	boolean supportsParameter(MethodParameter parameter);

	//从给定请求(webRequest)解析为参数值并填充到指定对象中
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
2.1.2、内置的 HandlerMethodArgumentResolver
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758JAVA//在初始化RequestMappingHandlerAdapter 时会默认加载参数解析器
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
   List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

   // Annotation-based argument resolution

   //处理 @RequestParam 注解标识的参数
   resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
   //处理@RequestParam 注解标识的Map参数且不能指定参数名称
   resolvers.add(new RequestParamMapMethodArgumentResolver());
   //处理@PathVariable 注解标识路径参数 如/pathVariable/{a}
   resolvers.add(new PathVariableMethodArgumentResolver());
   //处理@PathVariable 注解标识的Map参数且不能指定参数名称
   resolvers.add(new PathVariableMapMethodArgumentResolver());

   //处理@MatrixVariable注解标识的参数
   resolvers.add(new MatrixVariableMethodArgumentResolver());
   resolvers.add(new MatrixVariableMapMethodArgumentResolver());


   resolvers.add(new ServletModelAttributeMethodProcessor(false));

   //处理@RequestBody 注解
   resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
   resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
   //处理请求头
   resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
   resolvers.add(new RequestHeaderMapMethodArgumentResolver());
   //处理Cookie 值
   resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));

   resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
   resolvers.add(new SessionAttributeMethodArgumentResolver());
   resolvers.add(new RequestAttributeMethodArgumentResolver());

   // Type-based argument resolution
   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());
   }

   // Catch-all
   resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
   resolvers.add(new ServletModelAttributeMethodProcessor(true));

   return resolvers;
}
2.1.2、执行过程
  • 初始化解析器到 RequestMappingHandlerAdapter 中
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455JAVA// org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerAdapter
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
    @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
    @Qualifier("mvcConversionService") FormattingConversionService conversionService,
    @Qualifier("mvcValidator") Validator validator) {

    RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
    adapter.setContentNegotiationManager(contentNegotiationManager);
    adapter.setMessageConverters(getMessageConverters());
    adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));


    //可以实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer接口
    //设置自定义的参数解析器
    adapter.setCustomArgumentResolvers(getArgumentResolvers());

    adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
    if (jackson2Present) {
        adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
        adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
    }
    AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
    configureAsyncSupport(configurer);
    if (configurer.getTaskExecutor() != null) {
        adapter.setTaskExecutor(configurer.getTaskExecutor());
    }
    if (configurer.getTimeout() != null) {
        adapter.setAsyncRequestTimeout(configurer.getTimeout());
    }
    adapter.setCallableInterceptors(configurer.getCallableInterceptors());
    adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
    return adapter;
}

// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet
@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    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);
    }
}
  • 寻找合适的解析器
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码123456789101112131415161718192021222324252627282930313233343536373839404142JAVA//1. org.springframework.web.servlet.DispatcherServlet#doDispatch
//2. org.springframework.web.servlet.HandlerAdapter#handle
//3. org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
//4. org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
                                           Object... providedArgs) throws Exception {
	//获取方法参数
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }

    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        //判断是否支持解析该参数
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            //HandlerMethodArgumentResolverComposite 组合模式
            //使用具体HandlerMethodArgumentResolver 解析参数
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
            // Leave stack trace for later, exception may actually be resolved and handled...
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    return args;
}
  • 解析参数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253JAVA// @RequestParam 注解的参数
// org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument
//不同解析器实现不一样
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                                    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	//根据参数定义创建一个NamedValueInfo对象
    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    //如果参数是使用Optional包裹,则获取内嵌的参数对象
    MethodParameter nestedParameter = parameter.nestedIfOptional();
	// 处理参数名称
    Object resolvedName = resolveStringValue(namedValueInfo.name);
    if (resolvedName == null) {
        throw new IllegalArgumentException(
            "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    }
	//解析请求参数值
    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
        WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
        try {
            //转换请求参数为对应方法形参
            arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
        }
        catch (ConversionNotSupportedException ex) {
            throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                                                                    namedValueInfo.name, parameter, ex.getCause());
        }
        catch (TypeMismatchException ex) {
            throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                                                          namedValueInfo.name, parameter, ex.getCause());
        }
    }
	//处理路径参数
    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

    return arg;
}
2.2、WebDataBinder 原理
2.2.1、初始化 WebDataBinder 方式
  • @Controller 在每个控制器中定义(或者提取到 BaseController )
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011JAVApublic class BaseController {
    // @InitBinder 注解的方法,返回值需要声明为void
    @InitBinder
    public void initBinderUser(WebDataBinder binder) {
        System.out.println("BaseController  WebDataBinder 执行" );
    }
}

@RestController
public class DemoDataBindingController extends BaseController {
}
  • @ControllerAdvice 类 中定义,每个请求都会执行,适合全局配置
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678@ControllerAdvice
public class ControllerAdviceConfig {

    @InitBinder
    public void initBinderUser(WebDataBinder binder) {
        System.out.println("ControllerAdvice  WebDataBinder 执行" );
    }
}
  • 自定义 WebBindingInitializer
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011JAVA//默认实现 ConfigurableWebBindingInitializer
public interface WebBindingInitializer {
	// org.springframework.web.bind.support.DefaultDataBinderFactory#createBinder 创建时调用
    // 比@InitBinder 注解的方法先执行
	void initBinder(WebDataBinder binder);

	@Deprecated
	default void initBinder(WebDataBinder binder, WebRequest request) {
		initBinder(binder);
	}
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码1234567891011121314JAVA@Configuration
public class CustomConfigurableWebBindingInitializer extends ConfigurableWebBindingInitializer {

    @Override
    public void initBinder(WebDataBinder binder) {
        super.initBinder(binder);

        System.out.println("CustomConfigurableWebBindingInitializer  initBinder");
    }
}

//发起请求时,控制台输出
//CustomConfigurableWebBindingInitializer  initBinder
//ControllerAdvice  WebDataBinder 执行
2.2.2、WebDataBinder 有什么作用?
  • 用于绑定请求参数(Form 表单参数,query 参数)到模型对象中
  • 用于转换 字符串参数(请求参数、路径参数、header 属性、Cookie) 为 Controller 方法形参的对应类型
  • 格式化对象为指定字符串格式
2.2.3、WebDataBinder 执行过程
  • 定义初始化 WebDataBinder 方式(#2.2.1)
  • 创建 DataBinderFactory
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码123456789101112131415161718192021222324252627282930JAVA//1. org.springframework.web.servlet.DispatcherServlet#doDispatch
//2. org.springframework.web.servlet.HandlerAdapter#handle
//3. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.initBinderCache.get(handlerType);
    if (methods == null) {
        // 查找@Controller中 @InitBinder 注解的方法
        methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
        this.initBinderCache.put(handlerType, methods);
    }
    List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
    // Global methods first
    // initBinderAdviceCache 在 RequestMappingHandlerAdapter#afterPropertiesSet 里初始化
    // 1. 先加载 在@ControllerAdvice类定义的 @InitBinder 注解的方法
    for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
        if (entry.getKey().isApplicableToBeanType(handlerType)) {
            Object bean = entry.getKey().resolveBean();
            for (Method method : entry.getValue()) {
                initBinderMethods.add(createInitBinderMethod(bean, method));
            }
        }
    }
    //2. 再加载@Controller中 @InitBinder 注解的方法
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        initBinderMethods.add(createInitBinderMethod(bean, method));
    }
    return createDataBinderFactory(initBinderMethods);
}
  • 执行 initBinder 方法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
复制代码12345678910111213141516JAVA// org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument
// org.springframework.web.bind.support.DefaultDataBinderFactory#createBinder
@Override
@SuppressWarnings("deprecation")
public final WebDataBinder createBinder(
    NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {

    WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
    if (this.initializer != null) {
        //执行 WebBindingInitializer 定义的initBinder方法
        this.initializer.initBinder(dataBinder, webRequest);
    }
    //执行 @InitBinder 注解的方法
    initBinder(dataBinder, webRequest);
    return dataBinder;
}

到此,对 SpringMVC 的参数绑定讲解完成了。

项目地址

参考

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
一文读懂SpringMVC中的数据绑定
Struts2 和 SpringMVC 都是 Web 开发中视图层的框架,两者都实现了数据的自动绑定,都不需要我们手动获取参数然后关联到对应的属性上,下面就谈谈两者的区别。
Wizey
2018/09/29
9420
Spring自定义参数解析器设计
我们在开发Controller接口时经常会用到此类参数注解,那这些注解的作用是什么?我们真的了解吗?
程序猿川子
2023/05/04
6460
spring mvc之HandlerMethodArgumentResolver
因为:1.加重了我们对请求传过来来的值的取值代码,会使控制器中request.getParamater()之类的代码越来越多;2.不利于测试;3.request.getParamater()只能获取string,如果是Long等其他类型的参数还需要强转,使用起来非常不方便。
BUG弄潮儿
2022/06/30
2710
SpringMVC 解毒3
第三章将 HandlerMapping的时候,我们看到抽象类 AbstractUrlHandlerMapping 中的映射的handler都是Object类型的,而抽象类 AbstractHandlerMethodMapping 的最终实现类的handler必须是方法,类必须有@Controller或@RequestMapping注解,对应的方法也得有@RequestMapping注解。
zhangheng
2020/04/28
4810
HandlerMethodArgumentResolver(一):Controller方法入参自动封装器(将参数parameter解析为值)【享学Spring MVC】
在享受Spring MVC带给你便捷的时候,你是否曾经这样疑问过:Controller的handler方法参数能够自动完成参数封装(有时即使没有@PathVariable、@RequestParam、@RequestBody等注解都可),甚至在方法参数任意位置写HttpServletRequest、HttpSession、Writer…等类型的参数,它自动就有值了便可直接使用。 对此你是否想问一句:Spring MVC它是怎么办到的?那么本文就揭开它的神秘面纱,还你一片"清白"。
YourBatman
2019/09/03
2.5K2
HandlerMethodArgumentResolver(一):Controller方法入参自动封装器(将参数parameter解析为值)【享学Spring MVC】
SpringMVC 九大组件之 HandlerAdapter 深入分析
松哥原创的 Spring Boot 视频教程已经杀青,感兴趣的小伙伴戳这里-->Spring Boot+Vue+微人事视频教程
江南一点雨
2021/04/02
5810
web九大组件之---RequestMappingHandlerAdapter详尽解析【享学Spring MVC】
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
YourBatman
2019/10/22
4.2K0
web九大组件之---RequestMappingHandlerAdapter详尽解析【享学Spring MVC】
从原理层面掌握@InitBinder的使用【享学Spring MVC】
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
YourBatman
2019/09/18
3.5K0
记一次springboot项目自定义HandlerMethodArgumentResolver不生效原因与解法
本文素材的来源自业务部门技术负责人一次代码走查引发的故事,技术负责人在某次走查成员的代码时,发现他们的业务控制层大量充斥着如下的代码
lyb-geek
2022/10/25
1.1K0
记一次springboot项目自定义HandlerMethodArgumentResolver不生效原因与解法
SpringMVC参数绑定-细致总结(通俗易懂)
前面已经写过 SSM 三大框架的一些入门文章,在 SpringMVC 部分,关于参数的绑定提的不是太多,重新整理了一下,就当做一个补充,时间匆匆,可能会有一些错误,大家可以共同交流,一起探讨!
BWH_Steven
2020/05/11
1.2K0
SpringMVC参数绑定-细致总结(通俗易懂)
深入分析Spring MVC中RequestBody与ResponseBody
  在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换。在Spring MVC内部是如何做到的呢?先记住下面这张图,然后对里面的每个对象进行分析:
良辰美景TT
2018/09/11
2.2K0
深入分析Spring MVC中RequestBody与ResponseBody
mvc配置指定参数处理
人原来是这样健忘的,同样的一个人在短短的时间内竟然变换了两个面目,过后他又想,大概正是因为这样健忘,所以才能够在痛苦中生活下去罢。——巴金 今天遇到这样一个情况,我想使用parameter也就是?
阿超
2022/08/17
3700
mvc配置指定参数处理
springMVC参数绑定
处理器形参中添加如下类型的参数处理注解适配器会默认识别并进行赋值。 1 HttpServletRequest 通过request对象获取请求信息 2 HttpServletResponse 通过response处理响应信息 3 HttpSession 通过session对象得到session中存放的对象 4 Model 通过model向页面传递数据,如下: //调用service查询商品信息 Items item = itemService.findItemById(id); model.addAttribute("item", item); 页面通过${item.XXXX}获取item对象的属性值。 model也可以通过modelMap或map将数据传到页面(这是因为底层就是这个类型,具体可以看看底层代码)。
intsmaze-刘洋
2018/08/29
6820
三歪肝出了期待已久的SpringMVC
本文公众号来源:Java3y 作者:三歪 本文已收录至我的GitHub/Gitee
Java3y
2020/05/25
5940
三歪肝出了期待已久的SpringMVC
自定义Controller方法参数解析器
这个接口中有两个方法,supportsParameter用于判断是否通过本解析器解析该参数,resolveArgument用于编写解析的逻辑,返回的对象赋值给方法的相对应的参数。
DH镔
2019/12/19
8780
HandlerMethodArgumentResolver(四):自定参数解析器处理特定应用场景,介绍PropertyNamingStrategy的使用【享学Spring MVC】
前面通过三篇文章介绍了HandlerMethodArgumentResolver这个参数解析器以及它的所有内置实现,相信看过的小伙伴对它的加载、初始化、处理原理等等已能够做到了心中有数了。 Spring MVC内置注册了灰常多的处理器给我们的使用,不客气说几乎100%的case我们都是足够用了的。但既然我们已经理解到了HandlerMethodArgumentResolver它深层的作用原理,那么本文就通过自定义参数处理器,来做到屏蔽(隔离)基础实现、更高效的编写业务编码(提效是本文的关注点)。
YourBatman
2019/09/03
11.7K1
HandlerMethodArgumentResolver(四):自定参数解析器处理特定应用场景,介绍PropertyNamingStrategy的使用【享学Spring MVC】
如何妙用Spring 数据绑定机制
在剖析完 Spring Boot 返回统一数据格式是怎样实现的?文章之后,一直觉得有必要说明一下 Spring's Data Binding Mechanism 「Spring 数据绑定机制」。
用户4172423
2019/12/25
1.2K0
如何妙用Spring 数据绑定机制
spring 之 spring-mvc
spring-mvc的核心便是DispatcherServlet,所以初始化也是围绕其展开的。类图:
MickyInvQ
2021/10/22
1.1K0
spring 之 spring-mvc
@RequestParam等参数绑定注解是怎么实现的?自定义参数绑定注解的妙用
SpringMVC参数绑定的注解有很多,如@RequestParam,@RequestBody,@PathVariable,@RequestHeader,@CookieValue等。这些注解的实现方式很类似,都是有一个对应的解析器,解析完返回一个对象,放在方法的参数上。对参数绑定注解不熟悉的看推荐阅读
Java识堂
2019/05/22
4.2K0
Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上
前面已经详细介绍过了RequestMappingHandlerMapping是如何在初始化方法中搜集容器中所有标注了@Controller或者@RequestMapping注解的Bean的,然后解析将映射关系保存到映射中心。
大忽悠爱学习
2023/02/13
8180
Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上
推荐阅读
相关推荐
一文读懂SpringMVC中的数据绑定
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验