我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Spring @InitBinder注解如何使用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Spring @InitBinder注解如何使用

这篇文章主要讲解了“Spring @InitBinder注解如何使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring @InitBinder注解如何使用”吧!

    一. @InitBinder注解使用说明

    以前言中提到的字符串转Date为例,对@InitBinder的使用进行说明。

    @RestControllerpublic class DateController {    private static final String SUCCESS = "success";    private static final String FAILED = "failed";    private final List<Date> dates = new ArrayList<>();    @RequestMapping(value = "/api/v1/date/add", method = RequestMethod.GET)    public ResponseEntity<String> addDate(@RequestParam("date") Date date) {        ResponseEntity<String> response;        try {            dates.add(date);            response = new ResponseEntity<>(SUCCESS, HttpStatus.OK);        } catch (Exception e) {            e.printStackTrace();            response = new ResponseEntity<>(FAILED, HttpStatus.INTERNAL_SERVER_ERROR);        }        return response;    }}

    上面写好了一个简单的Controller,用于获取Date并存储。然后在单元测试中使用TestRestTemplate模拟客户端向服务端发起请求,程序如下。

    @ExtendWith(SpringExtension.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)class DateControllerTest {    @Autowired    private TestRestTemplate restTemplate;    @Test    void 测试Date字符串转换为Date对象() {        ResponseEntity<String> response = restTemplate                .getForEntity("/api/v1/date/add?date=20200620", String.class);        assertThat(response.getStatusCodeValue(), is(HttpStatus.OK.value()));    }}

    由于此时并没有使用@InitBinder注解修饰的方法向WebDataBinder注册CustomDateEditor对象,运行测试程序时断言会无法通过,报错会包含如下信息。

    Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'

    由于无法将字符串转换为Date,导致了参数类型不匹配的异常。

    下面使用@ControllerAdvice注解和@InitBinder注解为WebDataBinder添加CustomDateEditor对象,使SpringMVC框架为我们实现字符串转Date。

    @ControllerAdvicepublic class GlobalControllerAdvice {    @InitBinder    public void setDateEditor(WebDataBinder binder) {        binder.registerCustomEditor(Date.class,                new CustomDateEditor(new SimpleDateFormat("yyyyMMdd"), false));    }}

    此时再执行测试程序,断言通过。

    小节:由@InitBinder注解修饰的方法返回值类型必须为void,入参必须为WebDataBinder对象实例。如果在@Controller注解修饰的类中使用@InitBinder注解则配置仅对当前类生效,如果在@ControllerAdvice注解修饰的类中使用@InitBinder注解则配置全局生效。

    二. 实现自定义Editor

    现在假如需要将日期字符串转换为LocalDate,但是SpringMVC框架并没有提供类似于CustomDateEditor这样的Editor时,可以通过继承PropertyEditorSupport类来实现自定义Editor。首先看如下的一个Controller。

    @RestControllerpublic class LocalDateController {    private static final String SUCCESS = "success";    private static final String FAILED = "failed";    private final List<LocalDate> localDates = new ArrayList<>();    @RequestMapping(value = "/api/v1/localdate/add", method = RequestMethod.GET)    public ResponseEntity<String> addLocalDate(@RequestParam("localdate") LocalDate localDate) {        ResponseEntity<String> response;        try {            localDates.add(localDate);            response = new ResponseEntity<>(SUCCESS, HttpStatus.OK);        } catch (Exception e) {            e.printStackTrace();            response = new ResponseEntity<>(FAILED, HttpStatus.INTERNAL_SERVER_ERROR);        }        return response;    }}

    同样的在单元测试中使用TestRestTemplate模拟客户端向服务端发起请求。

    @ExtendWith(SpringExtension.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)class LocalDateControllerTest {    @Autowired    private TestRestTemplate restTemplate;    @Test    void 测试LocalDate字符串转换为LocalDate对象() {        ResponseEntity<String> response = restTemplate                .getForEntity("/api/v1/localdate/add?localdate=20200620", String.class);        assertThat(response.getStatusCodeValue(), is(HttpStatus.OK.value()));    }}

    此时直接执行测试程序断言会不通过,会报错类型转换异常。现在实现一个自定义的Editor。

    public class CustomLocalDateEditor extends PropertyEditorSupport {    private static final DateTimeFormatter dateTimeFormatter            = DateTimeFormatter.ofPattern("yyyyMMdd");    @Override    public void setAsText(String text) throws IllegalArgumentException {        if (StringUtils.isEmpty(text)) {            throw new IllegalArgumentException("Can not convert null.");        }        LocalDate result;        try {            result = LocalDate.from(dateTimeFormatter.parse(text));            setValue(result);        } catch (Exception e) {            throw new IllegalArgumentException("CustomDtoEditor convert failed.", e);        }    }}

    CustomLocalDateEditor是自定义的Editor,最简单的情况下,通过继承PropertyEditorSupport并重写setAsText() 方法可以实现一个自定义Editor。通常,自定义的转换逻辑在setAsText() 方法中实现,并将转换后的值通过调用父类PropertyEditorSupport的setValue() 方法完成设置。

    同样的,使用@ControllerAdvice注解和@InitBinder注解为WebDataBinder添加CustomLocalDateEditor对象。

    @ControllerAdvicepublic class GlobalControllerAdvice {    @InitBinder    public void setLocalDateEditor(WebDataBinder binder) {        binder.registerCustomEditor(LocalDate.class,                new CustomLocalDateEditor());    }}

    此时再执行测试程序,断言全部通过。

    小节:通过继承PropertyEditorSupport类并重写setAsText()方法可以实现一个自定义Editor

    三. WebDataBinder初始化原理解析

    已经知道,由@InitBinder注解修饰的方法用于初始化WebDataBinder,并且在详解SpringMVC-RequestMappingHandlerAdapter这篇文章中提到:从request获取到handler方法中由@RequestParam注解或@PathVariable注解修饰的参数后,便会使用WebDataBinderFactory工厂完成对WebDataBinder的初始化。下面看一下具体的实现。

    AbstractNamedValueMethodArgumentResolver#resolveArgument部分源码如下所示。

    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {    // ...    // 获取到参数    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);    // ...    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());        }        if (arg == null && namedValueInfo.defaultValue == null &&                namedValueInfo.required && !nestedParameter.isOptional()) {            handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);        }    }    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);    return arg;}

    实际上,上面方法中的binderFactory是ServletRequestDataBinderFactory工厂类,该类的类图如下所示。

    Spring @InitBinder注解如何使用

    createBinder() 是由接口WebDataBinderFactory声明的方法,ServletRequestDataBinderFactory的父类DefaultDataBinderFactory对其进行了实现,实现如下。

    public final WebDataBinder createBinder(        NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {    // 创建WebDataBinder实例    WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);    if (this.initializer != null) {        // 调用WebBindingInitializer对WebDataBinder进行初始化        this.initializer.initBinder(dataBinder, webRequest);    }    // 调用由@InitBinder注解修饰的方法对WebDataBinder进行初始化    initBinder(dataBinder, webRequest);    return dataBinder;}

    initBinder() 是DefaultDataBinderFactory的一个模板方法,InitBinderDataBinderFactory对其进行了重写,如下所示。

    public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {    for (InvocableHandlerMethod binderMethod : this.binderMethods) {        if (isBinderMethodApplicable(binderMethod, dataBinder)) {            // 执行由@InitBinder注解修饰的方法,完成对WebDataBinder的初始化            Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);            if (returnValue != null) {                throw new IllegalStateException(                        "@InitBinder methods must not return a value (should be void): " + binderMethod);            }        }    }}

    如上,initBinder() 方法中会遍历加载的所有由@InitBinder注解修饰的方法并执行,从而完成对WebDataBinder的初始化。

    小节:WebDataBinder的初始化是由WebDataBinderFactory先创建WebDataBinder实例,然后遍历WebDataBinderFactory加载好的由@InitBinder注解修饰的方法并执行,以完成WebDataBinder的初始化。

    四. @InitBinder注解修饰的方法的加载

    由第三小节可知,WebDataBinder的初始化是由WebDataBinderFactory先创建WebDataBinder实例,然后遍历WebDataBinderFactory加载好的由@InitBinder注解修饰的方法并执行,以完成WebDataBinder的初始化。本小节将学习WebDataBinderFactory如何加载由@InitBinder注解修饰的方法。

    WebDataBinderFactory的获取是发生在RequestMappingHandlerAdapter的invokeHandlerMethod() 方法中,在该方法中是通过调用getDataBinderFactory() 方法获取WebDataBinderFactory。下面看一下其实现。

    RequestMappingHandlerAdapter#getDataBinderFactory源码如下所示。

    private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {    // 获取handler的Class对象    Class<?> handlerType = handlerMethod.getBeanType();    // 从initBinderCache中根据handler的Class对象获取缓存的initBinder方法集合    Set<Method> methods = this.initBinderCache.get(handlerType);    // 从initBinderCache没有获取到initBinder方法集合,则执行MethodIntrospector.selectMethods()方法获取handler的initBinder方法集合,并缓存到initBinderCache中    if (methods == null) {        methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);        this.initBinderCache.put(handlerType, methods);    }    // initBinderMethods是WebDataBinderFactory需要加载的initBinder方法集合    List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();    // initBinderAdviceCache中存储的是全局生效的initBinder方法    this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {        // 如果ControllerAdviceBean有限制生效范围,则判断其是否对当前handler生效        if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {            Object bean = controllerAdviceBean.resolveBean();            // 如果对当前handler生效,则ControllerAdviceBean的所有initBinder方法均需要添加到initBinderMethods中            for (Method method : methodSet) {                initBinderMethods.add(createInitBinderMethod(bean, method));            }        }    });    // 将handler的所有initBinder方法添加到initBinderMethods中    for (Method method : methods) {        Object bean = handlerMethod.getBean();        initBinderMethods.add(createInitBinderMethod(bean, method));    }    // 创建WebDataBinderFactory,并同时加载initBinderMethods中的所有initBinder方法    return createDataBinderFactory(initBinderMethods);}

    上面的方法中使用到了两个缓存,initBinderCache和initBinderAdviceCache,表示如下。

    private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64);private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap<>();

    其中initBinderCache的key是handler的Class对象,value是handler的initBinder方法集合,initBinderCache一开始是没有值的,当需要获取handler对应的initBinder方法集合时,会先从initBinderCache中获取,如果获取不到才会调用MethodIntrospector#selectMethods方法获取,然后再将获取到的handler对应的initBinder方法集合缓存到initBinderCache中。

    initBinderAdviceCache的key是ControllerAdviceBean,value是ControllerAdviceBean的initBinder方法集合,initBinderAdviceCache的值是在RequestMappingHandlerAdapter初始化时调用的afterPropertiesSet() 方法中完成加载的,具体的逻辑在详解SpringMVC-RequestMappingHandlerAdapter有详细说明。

    因此WebDataBinderFactory中的initBinder方法由两部分组成,一部分是写在当前handler中的initBinder方法(这解释了为什么写在handler中的initBinder方法仅对当前handler生效),另外一部分是写在由@ControllerAdvice注解修饰的类中的initBinder方法,所有的这些initBinder方法均会对WebDataBinderFactory创建的WebDataBinder对象进行初始化。

    最后,看一下createDataBinderFactory() 的实现。

    RequestMappingHandlerAdapter#createDataBinderFactory

    protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)        throws Exception {    return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());}

    ServletRequestDataBinderFactory#ServletRequestDataBinderFactory

    public ServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods,        @Nullable WebBindingInitializer initializer) {    super(binderMethods, initializer);}

    InitBinderDataBinderFactory#InitBinderDataBinderFactory

    public InitBinderDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods,        @Nullable WebBindingInitializer initializer) {    super(initializer);    this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList());}

    可以发现,最终创建的WebDataBinderFactory实际上是ServletRequestDataBinderFactory,并且在执行ServletRequestDataBinderFactory的构造函数时,会调用其父类InitBinderDataBinderFactory的构造函数,在这个构造函数中,会将之前获取到的生效范围内的initBinder方法赋值给InitBinderDataBinderFactory的binderMethods变量,最终完成了initBinder方法的加载。

    小节:由@InitBinder注解修饰的方法的加载发生在创建WebDataBinderFactory时,在创建WebDataBinderFactory之前,会先获取对当前handler生效的initBinder方法集合,然后在创建WebDataBinderFactory的构造函数中将获取到的initBinder方法集合加载到WebDataBinderFactory中。

    感谢各位的阅读,以上就是“Spring @InitBinder注解如何使用”的内容了,经过本文的学习后,相信大家对Spring @InitBinder注解如何使用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    Spring @InitBinder注解如何使用

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    Spring @InitBinder注解如何使用

    这篇文章主要讲解了“Spring @InitBinder注解如何使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring @InitBinder注解如何使用”吧!一. @InitBin
    2023-07-05

    Spring@InitBinder注解使用及原理详解

    这篇文章主要为大家介绍了Spring@InitBinder注解使用及原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-13

    SpringMVC中@InitBinder注解怎么使用

    这篇文章主要讲解了“SpringMVC中@InitBinder注解怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SpringMVC中@InitBinder注解怎么使用”吧!简介@Co
    2023-07-02

    Spring @Profile注解如何使用

    这篇文章主要介绍“Spring @Profile注解如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring @Profile注解如何使用”文章能帮助大家解决问题。使用带有@Profile
    2023-07-06

    Spring @ComponentScan注解如何使用

    今天小编给大家分享一下Spring @ComponentScan注解如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。一
    2023-07-05

    如何使用注解开发spring

    本篇文章为大家展示了如何使用注解开发spring,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。在Spring4之后,要使用注解开发,必须要保证aop的包导入了。使用注解需要导入context约束,增
    2023-06-15

    Java中如何使用Spring注解

    Java中如何使用Spring注解,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。在Spring4之后,要使用注解开发,必须要保证aop的包导入了使用注解需要导入contex
    2023-06-20

    Spring中@ModelAttribute注解如何使用

    这期内容当中小编将会给大家带来有关Spring中@ModelAttribute注解如何使用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1.@ModelAttribute注释方法   例子(1),(2),
    2023-06-02

    spring中如何使用@Service注解

    本篇文章为大家展示了spring中如何使用@Service注解,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。@Service注解的使用要说明@Service注解的使用,就得说一下我们经常在sprin
    2023-06-20

    如何在Spring中使用@Transactional注解

    这期内容当中小编将会给大家带来有关如何在Spring中使用@Transactional注解,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。@Transactionalservice A(){try{inse
    2023-06-15

    使用Spring MVC4 如何配置注解

    使用Spring MVC4 如何配置注解?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。在传统的Spring项目中,我们要写一堆的XML文件。而这些XML文件格式
    2023-05-31

    @profile注解如何在spring中使用

    本篇文章给大家分享的是有关@profile注解如何在spring中使用,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。首先是新建maven工程mvn archetype:gene
    2023-05-30

    如何使用注解配置Spring容器

    这篇文章给大家分享的是有关如何使用注解配置Spring容器的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。具体如下:@Configuration标注在类上,相当于将该类作为spring的xml的标签@Configu
    2023-05-30

    使用Spring Aop如何配置AspectJ注解

    这篇文章将为大家详细讲解有关使用Spring Aop如何配置AspectJ注解,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。采用的jdk代理,接口和实现类代码请参考上篇博文。主要是将Aspe
    2023-05-31

    Spring注解@Configuration与@Bean注册组件如何使用

    今天小编给大家分享一下Spring注解@Configuration与@Bean注册组件如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一
    2023-07-02

    Spring Security中的权限注解如何使用

    今天小编给大家分享一下Spring Security中的权限注解如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Spr
    2023-06-30

    如何在Spring中使用@Override和@Autowired注解

    如何在Spring中使用@Override和@Autowired注解?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、Override首先,@Override 注解是伪代码
    2023-06-15

    Spring核心注释如何使用

    本篇内容主要讲解“Spring核心注释如何使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Spring核心注释如何使用”吧!这是所有已知的Spring核心注释的列表。@Autowired我们可
    2023-06-02

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录