欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?@InitBinder注解使用及原理詳解

 更新時間:2023年03月13日 16:02:02   作者:半夏之沫  
這篇文章主要為大家介紹了Spring?@InitBinder注解使用及原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

由@InitBinder注解修飾的方法用于初始化WebDataBinder對象,能夠?qū)崿F(xiàn):從request獲取到handler方法中由@RequestParam注解或@PathVariable注解修飾的參數(shù)后,假如獲取到的參數(shù)類型與handler方法上的參數(shù)類型不匹配,此時可以使用初始化好的WebDataBinder對獲取到的參數(shù)進行類型處理。

一個經(jīng)典的例子就是handler方法上的參數(shù)類型為Date,而從request中獲取到的參數(shù)類型是字符串,SpringMVC在默認情況下無法實現(xiàn)字符串轉(zhuǎn)Date,此時可以在由@InitBinder注解修飾的方法中為WebDataBinder對象注冊CustomDateEditor,從而使得WebDataBinder能將從request中獲取到的字符串再轉(zhuǎn)換為Date對象。

通常,如果在@ControllerAdvice注解修飾的類中使用@InitBinder注解,此時@InitBinder注解修飾的方法所做的事情全局生效(前提是@ControllerAdvice注解沒有設(shè)置basePackages字段);如果在@Controller注解修飾的類中使用@InitBinder注解,此時@InitBinder注解修飾的方法所做的事情僅對當前Controller生效。本篇文章將結(jié)合簡單例子,對@InitBinder注解的使用,原理進行學(xué)習(xí)。

SpringBoot版本:2.4.1

一. @InitBinder注解使用說明

以前言中提到的字符串轉(zhuǎn)Date為例,對@InitBinder的使用進行說明。

@RestController
public 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模擬客戶端向服務(wù)端發(fā)起請求,程序如下。

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DateControllerTest {
    @Autowired
    private TestRestTemplate restTemplate;
    @Test
    void 測試Date字符串轉(zhuǎn)換為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'

由于無法將字符串轉(zhuǎn)換為Date,導(dǎo)致了參數(shù)類型不匹配的異常。

下面使用@ControllerAdvice注解和@InitBinder注解為WebDataBinder添加CustomDateEditor對象,使SpringMVC框架為我們實現(xiàn)字符串轉(zhuǎn)Date。

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

此時再執(zhí)行測試程序,斷言通過。

小節(jié):由@InitBinder注解修飾的方法返回值類型必須為void,入?yún)⒈仨殲?code>WebDataBinder對象實例。如果在@Controller注解修飾的類中使用@InitBinder注解則配置僅對當前類生效,如果在@ControllerAdvice注解修飾的類中使用@InitBinder注解則配置全局生效。

二. 實現(xiàn)自定義Editor

現(xiàn)在假如需要將日期字符串轉(zhuǎn)換為LocalDate,但是SpringMVC框架并沒有提供類似于CustomDateEditor這樣的Editor時,可以通過繼承PropertyEditorSupport類來實現(xiàn)自定義Editor。首先看如下的一個Controller。

@RestController
public 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模擬客戶端向服務(wù)端發(fā)起請求。

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class LocalDateControllerTest {
    @Autowired
    private TestRestTemplate restTemplate;
    @Test
    void 測試LocalDate字符串轉(zhuǎn)換為LocalDate對象() {
        ResponseEntity<String> response = restTemplate
                .getForEntity("/api/v1/localdate/add?localdate=20200620", String.class);
        assertThat(response.getStatusCodeValue(), is(HttpStatus.OK.value()));
    }
}

此時直接執(zhí)行測試程序斷言會不通過,會報錯類型轉(zhuǎn)換異?!,F(xiàn)在實現(xiàn)一個自定義的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() 方法可以實現(xiàn)一個自定義Editor。通常,自定義的轉(zhuǎn)換邏輯在setAsText() 方法中實現(xiàn),并將轉(zhuǎn)換后的值通過調(diào)用父類PropertyEditorSupport的setValue() 方法完成設(shè)置。

同樣的,使用@ControllerAdvice注解和@InitBinder注解為WebDataBinder添加CustomLocalDateEditor對象。

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

此時再執(zhí)行測試程序,斷言全部通過。

小節(jié):通過繼承PropertyEditorSupport類并重寫setAsText()方法可以實現(xiàn)一個自定義Editor。

三. WebDataBinder初始化原理解析

已經(jīng)知道,由@InitBinder注解修飾的方法用于初始化WebDataBinder,并且在詳解SpringMVC-RequestMappingHandlerAdapter這篇文章中提到:從request獲取到handler方法中由@RequestParam注解或@PathVariable注解修飾的參數(shù)后,便會使用WebDataBinderFactory工廠完成對WebDataBinder的初始化。下面看一下具體的實現(xiàn)。

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

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // ...
    // 獲取到參數(shù)
    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工廠類,該類的類圖如下所示。

createBinder() 是由接口WebDataBinderFactory聲明的方法,ServletRequestDataBinderFactory的父類DefaultDataBinderFactory對其進行了實現(xiàn),實現(xiàn)如下。

public final WebDataBinder createBinder(
        NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
    // 創(chuàng)建WebDataBinder實例
    WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
    if (this.initializer != null) {
        // 調(diào)用WebBindingInitializer對WebDataBinder進行初始化
        this.initializer.initBinder(dataBinder, webRequest);
    }
    // 調(diào)用由@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)) {
            // 執(zhí)行由@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注解修飾的方法并執(zhí)行,從而完成對WebDataBinder的初始化。

小節(jié):WebDataBinder的初始化是由WebDataBinderFactory先創(chuàng)建WebDataBinder實例,然后遍歷WebDataBinderFactory加載好的由@InitBinder注解修飾的方法并執(zhí)行,以完成WebDataBinder的初始化。

四. @InitBinder注解修飾的方法的加載

由第三小節(jié)可知,WebDataBinder的初始化是由WebDataBinderFactory先創(chuàng)建WebDataBinder實例,然后遍歷WebDataBinderFactory加載好的由@InitBinder注解修飾的方法并執(zhí)行,以完成WebDataBinder的初始化。本小節(jié)將學(xué)習(xí)WebDataBinderFactory如何加載由@InitBinder注解修飾的方法。

WebDataBinderFactory的獲取是發(fā)生在RequestMappingHandlerAdapter的invokeHandlerMethod() 方法中,在該方法中是通過調(diào)用getDataBinderFactory() 方法獲取WebDataBinderFactory。下面看一下其實現(xiàn)。

RequestMappingHandlerAdapter#getDataBinderFactory源碼如下所示。

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
    // 獲取handler的Class對象
    Class<?> handlerType = handlerMethod.getBeanType();
    // 從initBinderCache中根據(jù)handler的Class對象獲取緩存的initBinder方法集合
    Set<Method> methods = this.initBinderCache.get(handlerType);
    // 從initBinderCache沒有獲取到initBinder方法集合,則執(zhí)行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));
    }
    // 創(chuàng)建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對應(yīng)的initBinder方法集合時,會先從initBinderCache中獲取,如果獲取不到才會調(diào)用MethodIntrospector#selectMethods方法獲取,然后再將獲取到的handler對應(yīng)的initBinder方法集合緩存到initBinderCache中。

initBinderAdviceCache的key是ControllerAdviceBean,value是ControllerAdviceBean的initBinder方法集合,initBinderAdviceCache的值是在RequestMappingHandlerAdapter初始化時調(diào)用的afterPropertiesSet() 方法中完成加載的,具體的邏輯在詳解SpringMVC-RequestMappingHandlerAdapter有詳細說明。

因此WebDataBinderFactory中的initBinder方法由兩部分組成,一部分是寫在當前handler中的initBinder方法(這解釋了為什么寫在handler中的initBinder方法僅對當前handler生效),另外一部分是寫在由@ControllerAdvice注解修飾的類中的initBinder方法,所有的這些initBinder方法均會對WebDataBinderFactory創(chuàng)建的WebDataBinder對象進行初始化。

最后,看一下createDataBinderFactory() 的實現(xiàn)。

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

可以發(fā)現(xiàn),最終創(chuàng)建的WebDataBinderFactory實際上是ServletRequestDataBinderFactory,并且在執(zhí)行ServletRequestDataBinderFactory的構(gòu)造函數(shù)時,會調(diào)用其父類InitBinderDataBinderFactory的構(gòu)造函數(shù),在這個構(gòu)造函數(shù)中,會將之前獲取到的生效范圍內(nèi)的initBinder方法賦值給InitBinderDataBinderFactory的binderMethods變量,最終完成了initBinder方法的加載。

小節(jié):由@InitBinder注解修飾的方法的加載發(fā)生在創(chuàng)建WebDataBinderFactory時,在創(chuàng)建WebDataBinderFactory之前,會先獲取對當前handler生效的initBinder方法集合,然后在創(chuàng)建WebDataBinderFactory的構(gòu)造函數(shù)中將獲取到的initBinder方法集合加載到WebDataBinderFactory中。

總結(jié)

由@InitBinder注解修飾的方法用于初始化WebDataBinder,從而實現(xiàn)請求參數(shù)的類型轉(zhuǎn)換適配,例如日期字符串轉(zhuǎn)換為日期Date類型,同時可以通過繼承PropertyEditorSupport類來實現(xiàn)自定義Editor,從而增加可以轉(zhuǎn)換適配的類型種類。

以上就是Spring @InitBinder注解使用及原理詳解的詳細內(nèi)容,更多關(guān)于Spring @InitBinder注解原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Sentinel實現(xiàn)動態(tài)配置的集群流控的方法

    Sentinel實現(xiàn)動態(tài)配置的集群流控的方法

    這篇文章主要介紹了Sentinel實現(xiàn)動態(tài)配置的集群流控,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • IDEA無法識別相關(guān)module模塊問題的解決過程

    IDEA無法識別相關(guān)module模塊問題的解決過程

    這篇文章主要給大家介紹了關(guān)于IDEA無法識別相關(guān)module模塊問題的解決過程,文中通過圖文介紹的非常詳細,對大家學(xué)習(xí)或者使用IDEA具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-07-07
  • Java注解之Repeatable解讀

    Java注解之Repeatable解讀

    這篇文章主要介紹了Java注解之Repeatable,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • spring中對象注入的三種實現(xiàn)方式

    spring中對象注入的三種實現(xiàn)方式

    本文主要介紹了spring中對象注入的三種實現(xiàn)方式,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • Java的MyBatis框架中對數(shù)據(jù)庫進行動態(tài)SQL查詢的教程

    Java的MyBatis框架中對數(shù)據(jù)庫進行動態(tài)SQL查詢的教程

    這篇文章主要介紹了Java的MyBatis框架中對數(shù)據(jù)庫進行動態(tài)SQL查詢的教程,講解了MyBatis中一些控制查詢流程的常用語句,需要的朋友可以參考下
    2016-04-04
  • spring boot開發(fā)遇到坑之spring-boot-starter-web配置文件使用教程

    spring boot開發(fā)遇到坑之spring-boot-starter-web配置文件使用教程

    Spring Boot支持容器的自動配置,默認是Tomcat,當然我們也是可以進行修改的。這篇文章給大家介紹了spring boot開發(fā)遇到坑之spring-boot-starter-web配置文件使用教程,需要的朋友參考下吧
    2018-01-01
  • 關(guān)于RabbitMQ的Channel默認線程

    關(guān)于RabbitMQ的Channel默認線程

    這篇文章主要介紹了關(guān)于RabbitMQ的Channel默認線程,通過jvm工具觀察rabbitmq的線程使用情況,發(fā)現(xiàn)生產(chǎn)者每發(fā)一條消息,消費者這邊就會創(chuàng)建一條線程,言下之意,一個channel當消息來到時就會異步處理這些消息,需要的朋友可以參考下
    2023-09-09
  • JVM?運行時數(shù)據(jù)區(qū)與JMM?內(nèi)存模型

    JVM?運行時數(shù)據(jù)區(qū)與JMM?內(nèi)存模型

    這篇文章主要介紹了JVM?運行時數(shù)據(jù)區(qū)與JMM?內(nèi)存模型,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值。需要的朋友可以參考一下
    2022-07-07
  • 關(guān)于Android觸摸事件分發(fā)的原理詳析

    關(guān)于Android觸摸事件分發(fā)的原理詳析

    觸摸事件分發(fā)機制一直以來都是Android中比較重要的一大塊,自定義view,各種復(fù)雜的自定義手勢交互都與觸摸事件分發(fā)機制關(guān)系密,下面這篇文章主要給大家介紹了關(guān)于Android觸摸事件分發(fā)原理的相關(guān)資料,需要的朋友可以參考下
    2022-01-01
  • Java反射機制,反射相關(guān)API,反射API使用方式(反射獲取實體類字段名和注解值)

    Java反射機制,反射相關(guān)API,反射API使用方式(反射獲取實體類字段名和注解值)

    這篇文章主要介紹了Java反射機制,反射相關(guān)API,反射API使用方式(反射獲取實體類字段名和注解值),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07

最新評論