Spring MVC 自定义 PropertyEditor

image

image

PropertyEditor 隶属于java.beans包,允许用户编辑给定类型的属性值。

PropertyEditorRegistry 隶属于org.springframework.beans包,是PropertyEditor的注册表,提供了注册及寻找接口。

DataBinder 是PropertyEditorRegistry的具体实现。

接下来看看是findCustomerEditor是什么时候调用的?

package org.springframework.beans;
class TypeConverterDelegate {
	@Nullable
	public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
			@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
		// Custom editor for this type?
		// (1)
		PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
		
        // Value not of required type?
        // (2)
		if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
			if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
					convertedValue instanceof String) {
				TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
				if (elementTypeDesc != null) {
					Class<?> elementType = elementTypeDesc.getType();
					if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
						convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
					}
				}
			}
			if (editor == null) {
				editor = findDefaultEditor(requiredType);
			}
			convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
		}		
	}
}

(1) 寻找自定义PropertyEditor
(2) 如果找到自定义的PropertyEditor, 就采用该Editor进行Value转换。

所以只要我们注入自定义的PropertyEditor到注册表即可,那如何注入呢?

这里要用到Spring MVC里数据绑定的一个主要类,WebDataBinder

具体案例如下,要求对字符串进行去空格处理,使用于请求地址中的请求参数

定义自定义PropertyEditor

public class CustomStringPropertyEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (text != null) {
            text = text.trim();
        }
        super.setValue(text);
    }
}

public class CustomListPropertyEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (text == null) {
            super.setAsText(null);
        }
        String[] source = text.split(",");
        String[] target = new String[source.length];
        for (int i = 0; i < source.length; i++) {
            target[i] = source[i].trim();
        }
        super.setValue(Arrays.asList(target));
    }
}

public class CustomArrayPropertyEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (text == null) {
            super.setAsText(null);
        }
        String[] source = text.split(",");
        String[] target = new String[source.length];
        for (int i = 0; i < source.length; i++) {
            target[i] = source[i].trim();
        }
        super.setValue(target);
    }
}

定义了三个PropertyEditor,分别支持List,String,Array.

注入自定义PropertyEditor

@RestController
public class HomeController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(String.class, new CustomStringPropertyEditor());
        binder.registerCustomEditor(List.class, new CustomListPropertyEditor());
        binder.registerCustomEditor(java.lang.String[].class, new CustomArrayPropertyEditor());
    }

    @GetMapping("/testString")
    public void testList(@RequestParam(value = "a") String a) {
        System.out.println(":" + a + ":");
    }

    @GetMapping("/testList")
    public void testList(@RequestParam("names") List<String> params) {
        System.out.println(":" + String.join(",", params) + ":");
    }

    @GetMapping("/testArray")
    public void testArray(@RequestParam("names") String[] params) {
        System.out.println(":" + String.join(",", params) + ":");
    }

}

至此,整个自定义PropertyEditor功能完成

下面简单的整理下整个源码阅读的执行过程

package org.springframework.web.servlet;
public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        // 这里的ha的实际类是 RequestMappingHandlerAdapter
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    }
}

package org.springframework.web.servlet.mvc.method.annotation;
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
    protected ModelAndView handleInternal(HttpServletRequest request, 
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    	HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    	if (this.argumentResolvers != null) {
    	    // HandlerMethodArgumentResolverComposite
    		invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    	}
    	if (this.returnValueHandlers != null) {
    	    // HandlerMethodReturnValueHandlerComposite
    		invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    	}
    	// ServletRequestDataBinderFactory
        invocableMethod.setDataBinderFactory(binderFactory);
        // DefaultParameterNameDiscoverer
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        // 调用ServletInvocableHandlerMethod处理请求
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
    }
}
			
package org.springframework.web.servlet.mvc.method.annotation;
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	}
}

public class InvocableHandlerMethod extends HandlerMethod {
	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	}
    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		for (int i = 0; i < parameters.length; i++) {
		    // 这个resolvers是否还有印象,就是HandlerMethodReturnValueHandlerComposite, 进行参数解析
		    args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
		}
	}
}

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
		HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
		if (resolver == null) {
			throw new IllegalArgumentException("Unsupported parameter type [" +
					parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
		}
		// 调用RequestParamMethodArgumentResolver进行参数解析
		return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
	}
}

public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
		// WebDataBinder 我们注入自定义PropertyEditor的关键类
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
		try {
		    // 进行转换
			arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
		}
	}
}

package org.springframework.validation;
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
	public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
			@Nullable MethodParameter methodParam) throws TypeMismatchException {
		// 调用TypeConverter进行转换
		return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
	}
}

package org.springframework.beans;
public abstract class TypeConverterSupport extends PropertyEditorRegistrySupport implements TypeConverter {
	public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
			@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
		// 调用TypeConverterDelegatel来转换
		// 思考:为何要另起一个类来转换???
		return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
	}
}

package org.springframework.beans;
class TypeConverterDelegate {
	public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
			@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
		// 重点:根据类型,获取Custome Property Editor
		PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
		
		// Value not of required type?
		if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
			convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
		}
	}
	private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue,
			@Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {
		if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
			// Convert String array to a comma-separated String.
			// Only applies if no PropertyEditor converted the String array before.
			// The CSV String will be passed into a PropertyEditor's setAsText method, if any.
			if (logger.isTraceEnabled()) {
				logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]");
			}
			convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);
		}

		if (convertedValue instanceof String) {
			if (editor != null) {
				// Use PropertyEditor's setAsText in case of a String value.
				if (logger.isTraceEnabled()) {
					logger.trace("Converting String to [" + requiredType + "] using property editor [" + editor + "]");
				}
				String newTextValue = (String) convertedValue;
				return doConvertTextValue(oldValue, newTextValue, editor);
			}
			else if (String.class == requiredType) {
				returnValue = convertedValue;
			}
		}

		return returnValue;
	}
}

posted @ 2019-09-03 09:27  白眉大虾  阅读(554)  评论(0)    收藏  举报