91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring?@InitBinder注解如何使用

發布時間:2023-03-13 16:39:05 來源:億速云 閱讀:129 作者:iii 欄目:開發技術

這篇文章主要講解了“Spring @InitBinder注解如何使用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Spring @InitBinder注解如何使用”吧!

    一. @InitBinder注解使用說明

    以前言中提到的字符串轉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模擬客戶端向服務端發起請求,程序如下。

    @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。

    @ControllerAdvice
    public 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。

    @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模擬客戶端向服務端發起請求。

    @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對象。

    @ControllerAdvice
    public 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注解如何使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    广西| 宜都市| 镇沅| 万荣县| 道真| 元谋县| 措勤县| 阳江市| 扬州市| 怀仁县| 宁强县| 建平县| 綦江县| 巩留县| 海宁市| 广宁县| 富平县| 南涧| 天镇县| 麻城市| 沙坪坝区| 云龙县| 凤山县| 宁强县| 平利县| 佛冈县| 隆安县| 萍乡市| 松阳县| 调兵山市| 娱乐| 旬阳县| 宜春市| 开化县| 张家川| 商洛市| 上蔡县| 瓦房店市| 桐梓县| 淮阳县| 西乌|