流程图解Spring Framework(十二)Spring MVC @InitBinder 原理

文章目录

    • 介绍
    • 使用
      • 全局 WebBinder
      • Controller 个性化的Binder
    • 原理
      • binder 初始化
      • binder 使用

介绍

在日常的项目中,经常会遇到页面传参和系统的实体不一致的情况,比如Date类型,页面传入参数是20180112,但是后面接受类型是Date,那么后面的接收不到这个参数,并且会报400错误。

在Spring中可以使用注解@InitBinder来解决这个问题,它可以注册一系列的属性编辑器java.beans.PropertyEditor,这些编辑器向处理器设置一些规则,比如刚才的date类型。就可以注册CustomerDateEditor。详细的类型有如下表示
流程图解Spring Framework(十二)Spring MVC @InitBinder 原理_第1张图片

使用

全局 WebBinder

@ControllerAdvice
public class GlobalWebBinderAdvice {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        // 注册
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

}

Controller 个性化的Binder

@Controller
public class WebBinderTestController {

    // 个性化 initBinder
    // value 表示只绑定表单的固定key
    @InitBinder("date")
    protected void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }


    // http://localhost:8080/?date=20180819&date2=2018-09-19
    // date 使用个性化binder date2使用全局binder
    @RequestMapping("/")
    @ResponseBody
    private String hello(Date date, Date date2){
        System.out.println(date);
        return "hello";
    }

}

原理

binder 初始化

RequestMappingHandlerAdapter#afterPropertiesSet => RequestMappingHandlerAdapter#initControllerAdviceCache

    private void initControllerAdviceCache() {
    		if (getApplicationContext() == null) {
    			return;
    		}
    
            // 获取所有加了 @ControllerAdvice 注解的Controller
    		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) {
    				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
    			}
    			Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
    			if (!attrMethods.isEmpty()) {
    				this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
    			}
    			// 搜索所有的@InitBinder注解,说明是用ControllerAdvice实现的InitBinder,这里是全局的InitBinder
    			Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
    			if (!binderMethods.isEmpty()) {
    				this.initBinderAdviceCache.put(adviceBean, binderMethods);
    			}
    			if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
    				requestResponseBodyAdviceBeans.add(adviceBean);
    			}
    			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
    				requestResponseBodyAdviceBeans.add(adviceBean);
    			}
    		}
    
    		if (!requestResponseBodyAdviceBeans.isEmpty()) {
    			this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
    		}
    
    		if (logger.isDebugEnabled()) {
    			int modelSize = this.modelAttributeAdviceCache.size();
    			int binderSize = this.initBinderAdviceCache.size();
    			int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
    			int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
    			if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
    				logger.debug("ControllerAdvice beans: none");
    			}
    			else {
    				logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
    						" @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
    			}
    		}
    	}

binder 使用

请求进入RequestMappingHandlerAdapter之后,请求体进行了一系列的改变

RequestMappingHandlerAdapter#invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
		    // 1. 获取binderFactory
			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();
				LogFormatUtils.traceDebug(logger, traceOn -> {
					String formatted = LogFormatUtils.formatValue(result, !traceOn);
					return "Resume with async result [" + formatted + "]";
				});
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}
			
			// 2. 调用WebBinder,convert WebRequest内部的请求参数
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}
  1. 获取binderFactory
    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);
		}
		// 获取 全局,即ControllerAdvice 下面的 initBinder 的所有方法
		List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
		// 全局的initBinder
		this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
		    // 测试是否匹配
		    // 匹配条件: controller在Advice所在的包下面同一个包、同一个类型、controller上面有注解
			if (clazz.isApplicableToBeanType(handlerType)) {
				Object bean = clazz.resolveBean();
				for (Method method : methodSet) {
					initBinderMethods.add(createInitBinderMethod(bean, method));
				}
			}
		});
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			initBinderMethods.add(createInitBinderMethod(bean, method));
		}
		// 创建一个DataBinderFactory
		return createDataBinderFactory(initBinderMethods);
	}

	private InvocableHandlerMethod createInitBinderMethod(Object bean, Method method) {
		InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
		if (this.initBinderArgumentResolvers != null) {
			binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
		}
		// 设置 WebBinder ===> this.webBindingInitializer=ConfigurableWebBindingInitializer
		binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
		binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
		return binderMethod;
	}
	
	protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
    			throws Exception {
            // 创建一个binder
            //  getWebBindingInitializer() ==> ConfigurableWebBindingInitializer
    		return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
    }

  1. 使用WebBinder的逻辑
    InvocableHandlerMethod#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);
		Assert.state(this.returnValueHandlers != null, "No return value handlers");
		try {
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(formatErrorForReturnValue(returnValue), ex);
			}
			throw ex;
		}
	}

InvocableHandlerMethod#invokeForRequest

@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);
	}

InvocableHandlerMethod#getMethodArgumentValues

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		if (ObjectUtils.isEmpty(getMethodParameters())) {
			return EMPTY_ARGS;
		}
		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);
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
				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 error = ex.getMessage();
					if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, error));
					}
				}
				throw ex;
			}
		}
		return args;
	}

真正逻辑在下面

AbstractNamedValueMethodArgumentResolver

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) {
			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 binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
			try {
			    // 使用webBinder 转换
				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;
	}

  1. 使用webBinder 转换
    DataBinder#convertIfNecessary
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
			@Nullable MethodParameter methodParam) throws TypeMismatchException {

		return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
	}

TypeConverterSupport#convertIfNecessary

@Override
	@Nullable
	public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException {
		return doConvert(value, requiredType, null, null);
	}
@Nullable
	private <T> T doConvert(@Nullable Object value,@Nullable Class<T> requiredType,
			@Nullable MethodParameter methodParam, @Nullable Field field) throws TypeMismatchException {

		Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
		try {
			if (field != null) {
				return this.typeConverterDelegate.convertIfNecessary(value, requiredType, field);
			}
			else {
				return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam);
			}
		}
		catch (ConverterNotFoundException | IllegalStateException ex) {
			throw new ConversionNotSupportedException(value, requiredType, ex);
		}
		catch (ConversionException | IllegalArgumentException ex) {
			throw new TypeMismatchException(value, requiredType, ex);
		}
	}

你可能感兴趣的:(spring,framework,Spring)