10 数据绑定: 如何自动转换传入的参数?
你好,我是郭屹。今天我们继续手写MiniSpring,这节课我们讨论传入参数的转换问题。
上节课,我们已经基本完成了对Dispatcher的扩展,用HanderMapping来处理映射关系,用HandlerAdapter来处理映射后具体方法的调用。
在处理请求的过程中,我们用ServletRequest接收请求参数,而获取参数用的是getParameter()方法,它的返回值是String字符串,这也意味着无论是获取字符串参数、数字参数,还是布尔型参数,它获取到的返回值都是字符串。而如果要把请求参数转换成Java对象,就需要再处理,那么每一次获取参数后,都需要显式地编写大量重复代码,把String类型的参数转换成其他类型。显然这不符合我们对框架的期望,我们希望框架能帮助我们自动处理这些常规数据格式的转换。
再扩大到整个访问过程,后端处理完毕后,返回给前端的数据再做返回,也存在格式转换的问题,传入传出两个方向我们都要处理。而这节课我们讨论的重点是“传入”方向。
传入参数的绑定
我们先考虑传入方向的问题:请求参数怎么和Java对象里的属性进行自动映射?
这里,我们引入WebDataBinder来处理。这个类代表的是一个内部的目标对象,用于将Request请求内的字符串参数转换成不同类型的参数,来进行适配。所以比较自然的想法是这个类里面要持有一个目标对象target,然后还要定义一个bind()方法,通过来绑定参数和目标对象,这是WebDataBinder里的核心。
public void bind(HttpServletRequest request) {
PropertyValues mpvs = assignParameters(request);
addBindValues(mpvs, request);
doBind(mpvs);
}
通过bind方法的实现,我们可以看出,它主要做了三件事。
- 把Request里的参数解析成PropertyValues。
- 把Request里的参数值添加到绑定参数中。
- 把两者绑定在一起。
你可以看一下WebDataBinder的详细实现。
package com.minis.web;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.minis.beans.PropertyValues;
import com.minis.util.WebUtils;
public class WebDataBinder {
private Object target;
private Class<?> clz;
private String objectName;
public WebDataBinder(Object target) {
this(target, "");
}
public WebDataBinder(Object target, String targetName) {
this.target = target;
this.objectName = targetName;
this.clz = this.target.getClass();
}
//核心绑定方法,将request里面的参数值绑定到目标对象的属性上
public void bind(HttpServletRequest request) {
PropertyValues mpvs = assignParameters(request);
addBindValues(mpvs, request);
doBind(mpvs);
}
private void doBind(PropertyValues mpvs) {
applyPropertyValues(mpvs);
}
//实际将参数值与对象属性进行绑定的方法
protected void applyPropertyValues(PropertyValues mpvs) {
getPropertyAccessor().setPropertyValues(mpvs);
}
//设置属性值的工具
protected BeanWrapperImpl getPropertyAccessor() {
return new BeanWrapperImpl(this.target);
}
//将Request参数解析成PropertyValues
private PropertyValues assignParameters(HttpServletRequest request) {
Map<String, Object> map = WebUtils.getParametersStartingWith(request, "");
return new PropertyValues(map);
}
protected void addBindValues(PropertyValues mpvs, HttpServletRequest request) {
}
}
从这个实现方法里可以看出,先是调用了assignParameters(),把Request里的参数换成内存里的一个map对象,这一步用到了底层的WebUtils工具类,这个转换对我们来说比较简单。而最核心的方法是getPropertyAccessor().setPropertyValues(mpvs);,这个getPropertyAccessor则是内置了一个BeanWrapperImpl对象,内部包含了target。由名字可以看出它是Bean的包装实现类,把属性map绑定到目标对象上去。
有了这个大流程,我们再来探究一下一个具体的参数是如何转换的,我们知道Request的转换都是从字符串转为其他类型,所以我们可以定义一个通用接口,名叫PropertyEditor,内部提供一些方法可以让字符串和Obejct之间进行双向灵活转换。
package com.minis.beans;
public interface PropertyEditor {
void setAsText(String text);
void setValue(Object value);
Object getValue();
Object getAsText();
}
现在我们来定义两个PropertyEditor的实现类:CustomNumberEditor和StringEditor,分别处理Number类型和其他类型,并进行类型转换。你可以看一下CustomNumberEditor的相关源码。
package com.minis.beans;
import java.text.NumberFormat;
import com.minis.util.NumberUtils;
import com.minis.util.StringUtils;
public class CustomNumberEditor implements PropertyEditor{
private Class<? extends Number> numberClass; //数据类型
private NumberFormat numberFormat; //指定格式
private boolean allowEmpty;
private Object value;
public CustomNumberEditor(Class<? extends Number> numberClass, boolean allowEmpty) throws IllegalArgumentException {
this(numberClass, null, allowEmpty);
}
public CustomNumberEditor(Class<? extends Number> numberClass, NumberFormat numberFormat, boolean allowEmpty) throws IllegalArgumentException {
this.numberClass = numberClass;
this.numberFormat = numberFormat;
this.allowEmpty = allowEmpty;
}
//将一个字符串转换成number赋值
public void setAsText(String text) {
if (this.allowEmpty && !StringUtils.hasText(text)) {
setValue(null);
}
else if (this.numberFormat != null) {
// 给定格式
setValue(NumberUtils.parseNumber(text, this.numberClass, this.numberFormat));
}
else {
setValue(NumberUtils.parseNumber(text, this.numberClass));
}
}
//接收Object作为参数
public void setValue(Object value) {
if (value instanceof Number) {
this.value = (NumberUtils.convertNumberToTargetClass((Number) value, this.numberClass));
}
else {
this.value = value;
}
}
public Object getValue() {
return this.value;
}
//将number表示成格式化串
public Object getAsText() {
Object value = this.value;
if (value == null) {
return "";
}
if (this.numberFormat != null) {
// 给定格式.
return this.numberFormat.format(value);
}
else {
return value.toString();
}
}
}
整体实现也比较简单,在内部定义一个名为value的域,接收传入的格式化text或者value值。如果遇到的值是Number类型的子类,比较简单,就进行强制转换。这里我们用到了一个底层工具类NumberUtils,它提供了一个NumberUtils.parseNumber(text, this.numberClass, this.numberFormat)方法,方便我们在数值和文本之间转换。
你可以看下StringEditor实现的相关源代码。
package com.minis.beans;
import java.text.NumberFormat;
import com.minis.util.NumberUtils;
import com.minis.util.StringUtils;
public class StringEditor implements PropertyEditor{
private Class<String> strClass;
private String strFormat;
private boolean allowEmpty;
private Object value;
public StringEditor(Class<String> strClass,
boolean allowEmpty) throws IllegalArgumentException {
this(strClass, "", allowEmpty);
}
public StringEditor(Class<String> strClass,
String strFormat, boolean allowEmpty) throws IllegalArgumentException {
this.strClass = strClass;
this.strFormat = strFormat;
this.allowEmpty = allowEmpty;
}
public void setAsText(String text) {
setValue(text);
}
public void setValue(Object value) {
this.value = value;
}
public String getAsText() {
return value.toString();
}
public Object getValue() {
return this.value;
}
}
StringEditor的实现类就更加简单了,因为它是字符串本身的处理,但它的构造函数有些不一样,支持传入字符串格式strFormat,这也是为后续类型转换格式留了一个“口子”。
有了两个基本类型的Editor作为工具,现在我们再来看关键的类BeanWapperImpl的实现。
package com.minis.web;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.minis.beans.PropertyEditor;
import com.minis.beans.PropertyEditorRegistrySupport;
import com.minis.beans.PropertyValue;
import com.minis.beans.PropertyValues;
public class BeanWrapperImpl extends PropertyEditorRegistrySupport {
Object wrappedObject; //目标对象
Class<?> clz;
PropertyValues pvs; //参数值
public BeanWrapperImpl(Object object) {
registerDefaultEditors(); //不同数据类型的参数转换器editor
this.wrappedObject = object;
this.clz = object.getClass();
}
public void setBeanInstance(Object object) {
this.wrappedObject = object;
}
public Object getBeanInstance() {
return wrappedObject;
}
//绑定参数值
public void setPropertyValues(PropertyValues pvs) {
this.pvs = pvs;
for (PropertyValue pv : this.pvs.getPropertyValues()) {
setPropertyValue(pv);
}
}
//绑定具体某个参数
public void setPropertyValue(PropertyValue pv) {
//拿到参数处理器
BeanPropertyHandler propertyHandler = new BeanPropertyHandler(pv.getName());
//找到对该参数类型的editor
PropertyEditor pe = this.getDefaultEditor(propertyHandler.getPropertyClz());
//设置参数值
pe.setAsText((String) pv.getValue());
propertyHandler.setValue(pe.getValue());
}
//一个内部类,用于处理参数,通过getter()和setter()操作属性
class BeanPropertyHandler {
Method writeMethod = null;
Method readMethod = null;
Class<?> propertyClz = null;
public Class<?> getPropertyClz() {
return propertyClz;
}
public BeanPropertyHandler(String propertyName) {
try {
//获取参数对应的属性及类型
Field field = clz.getDeclaredField(propertyName);
propertyClz = field.getType();
//获取设置属性的方法,按照约定为setXxxx()
this.writeMethod = clz.getDeclaredMethod("set" +
propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1), propertyClz);
//获取读属性的方法,按照约定为getXxxx()
this.readMethod = clz.getDeclaredMethod("get" +
propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1), propertyClz);
} catch (Exception e) {
e.printStackTrace();
} }
//调用getter读属性值
public Object getValue() {
Object result = null;
writeMethod.setAccessible(true);
try {
result = readMethod.invoke(wrappedObject);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
//调用setter设置属性值
public void setValue(Object value) {
writeMethod.setAccessible(true);
try {
writeMethod.invoke(wrappedObject, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这个类的核心在于利用反射对Bean属性值进行读写,具体是通过setter和getter方法。但具体的实现,则有赖于继承的PropertyEditorRegistrySupport这个类。我们再来看看PropertyEditorRegistrySupport是如何实现的。
package com.minis.beans;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class PropertyEditorRegistrySupport {
private Map<Class<?>, PropertyEditor> defaultEditors;
private Map<Class<?>, PropertyEditor> customEditors;
//注册默认的转换器editor
protected void registerDefaultEditors() {
createDefaultEditors();
}
//获取默认的转换器editor
public PropertyEditor getDefaultEditor(Class<?> requiredType) {
return this.defaultEditors.get(requiredType);
}
//创建默认的转换器editor,对每一种数据类型规定一个默认的转换器
private void createDefaultEditors() {
this.defaultEditors = new HashMap<>(64);
// Default instances of collection editors.
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
this.defaultEditors.put(String.class, new StringEditor(String.class, true));
}
//注册客户化转换器
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
if (this.customEditors == null) {
this.customEditors = new LinkedHashMap<>(16);
}
this.customEditors.put(requiredType, propertyEditor);
}
//查找客户化转换器
public PropertyEditor findCustomEditor(Class<?> requiredType) {
Class<?> requiredTypeToUse = requiredType;
return getCustomEditor(requiredTypeToUse);
}
public boolean hasCustomEditorForElement(Class<?> elementType) {
return (elementType != null && this.customEditors != null && this.customEditors.containsKey(elementType));
}
//获取客户化转换器
private PropertyEditor getCustomEditor(Class<?> requiredType) {
if (requiredType == null || this.customEditors == null) {
return null;
}
PropertyEditor editor = this.customEditors.get(requiredType);
return editor;
}
}
从这段源码里可以看到,PropertyEditorRegistrySupport 的核心实现是createDefaultEditors方法,它里面内置了大量基本类型或包装类型的转换器Editor,还定义了可以定制化的转换器Editor,这也是WebDataBinder能做不同类型转换的原因。不过我们目前的实现,只支持数字和字符串几个基本类型的转换,暂时不支持数组、列表、map等格式。
现在,我们已经实现了一个完整的WebDataBinder,用来绑定数据。我们接下来将提供一个WebDataBinderFactory,能够更方便、灵活地操作WebDataBinder。
package com.minis.web;
import javax.servlet.http.HttpServletRequest;
public class WebDataBinderFactory {
public WebDataBinder createBinder(HttpServletRequest request, Object target, String objectName) {
WebDataBinder wbd = new WebDataBinder(target, objectName);
initBinder(wbd, request);
return wbd;
}
protected void initBinder(WebDataBinder dataBinder, HttpServletRequest request) {
}
}
有了上面一系列工具之后,我们看怎么使用它们进行数据绑定。从前面的讲解中我们已经知道,这个 HTTP Request请求最后会找到映射的方法上,也就是通过RequestMappingHandlerAdapter里提供的handleInternal 方法,来调用invokeHandlerMethod 方法,所以我们从这个地方切入,改造 invokeHandlerMethod 方法,实现参数绑定。
protected void invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
WebDataBinderFactory binderFactory = new WebDataBinderFactory();
Parameter[] methodParameters =
handlerMethod.getMethod().getParameters();
Object[] methodParamObjs = new Object[methodParameters.length];
int i = 0;
//对调用方法里的每一个参数,处理绑定
for (Parameter methodParameter : methodParameters) {
Object methodParamObj = methodParameter.getType().newInstance();
//给这个参数创建WebDataBinder
WebDataBinder wdb = binderFactory.createBinder(request,
methodParamObj, methodParameter.getName());
wdb.bind(request);
methodParamObjs[i] = methodParamObj;
i++;
}
Method invocableMethod = handlerMethod.getMethod();
Object returnObj = invocableMethod.invoke(handlerMethod.getBean(), methodParamObjs);
response.getWriter().append(returnObj.toString());
}
在invokeHandlerMethod 方法的实现代码中,methodParameters 变量用来存储调用方法的所有参数,针对它们进行循环,还有一个变量methodParamObj,是一个新创建的空对象,也是我们需要进行绑定操作的目标,binderFactory.createBinder则是创建了WebDtaBinder,对目标对象进行绑定。整个循环结束之后,Request里面的参数就绑定了调用方法里的参数,之后就可以被调用。
我们从这个绑定过程中可以看到,循环过程就是按照参数在方法中出现的次序逐个绑定的,所以这个次序是很重要的。
客户化转换器
现在我们已经实现了Request数据绑定过程,也提供了默认的CustomNumberEditor和StringEditor,来进行数字和字符串两种类型的转换,从而把ServletRequest里的请求参数转换成Java对象里的数据类型。但这种默认的方式比较固定,如果你希望转换成自定义的类型,那么原有的两个Editor就没办法很好地满足需求了。
因此我们要继续探讨,如何支持自定义的Editor,让我们的框架具有良好的扩展性。其实上面我们看到PropertyEditorRegistrySupport里,已经提前准备好了客户化转换器的地方,你可以看下代码。
public class PropertyEditorRegistrySupport {
private Map<Class<?>, PropertyEditor> defaultEditors;
private Map<Class<?>, PropertyEditor> customEditors;
我们利用客户化Editor这个“口子”,新建一个部件,把客户自定义的Editor注册进来就可以了。
我们先在原有的WebDataBinder 类里,增加registerCustomEditor方法,用来注册自定义的Editor,你可以看一下相关代码。
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
getPropertyAccessor().registerCustomEditor(requiredType, propertyEditor);
}
在这里,可以自定义属于我们自己的CustomEditor ,比如在com.test 包路径下,自定义CustomDateEditor,这是一个自定义的日期格式处理器,来配合我们的测试。
package com.test;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import com.minis.beans.PropertyEditor;
import com.minis.util.NumberUtils;
import com.minis.util.StringUtils;
public class CustomDateEditor implements PropertyEditor {
private Class<Date> dateClass;
private DateTimeFormatter datetimeFormatter;
private boolean allowEmpty;
private Date value;
public CustomDateEditor() throws IllegalArgumentException {
this(Date.class, "yyyy-MM-dd", true);
}
public CustomDateEditor(Class<Date> dateClass) throws IllegalArgumentException {
this(dateClass, "yyyy-MM-dd", true);
}
public CustomDateEditor(Class<Date> dateClass,
boolean allowEmpty) throws IllegalArgumentException {
this(dateClass, "yyyy-MM-dd", allowEmpty);
}
public CustomDateEditor(Class<Date> dateClass,
String pattern, boolean allowEmpty) throws IllegalArgumentException {
this.dateClass = dateClass;
this.datetimeFormatter = DateTimeFormatter.ofPattern(pattern);
this.allowEmpty = allowEmpty;
}
public void setAsText(String text) {
if (this.allowEmpty && !StringUtils.hasText(text)) {
setValue(null);
}
else {
LocalDate localdate = LocalDate.parse(text, datetimeFormatter);
setValue(Date.from(localdate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
}
}
public void setValue(Object value) {
this.value = (Date) value;
}
public String getAsText() {
Date value = this.value;
if (value == null) {
return "";
}
else {
LocalDate localDate = value.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
return localDate.format(datetimeFormatter);
}
}
public Object getValue() {
return this.value;
}
}
程序也比较简单,用DateTimeFormatter来转换字符串和日期就可以了。
接下来我们定义一个WebBindingInitializer,其中有一个initBinder实现方法,为自定义的CustomEditor注册做准备。
下面,我们再实现WebBindingInitializer接口,在实现方法initBinder里,注册自定义的CustomDateEditor,你可以看下相关代码。
package com.test;
import java.util.Date;
import com.minis.web.WebBindingInitializer;
import com.minis.web.WebDataBinder;
public class DateInitializer implements WebBindingInitializer{
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new CustomDateEditor(Date.class,"yyyy-MM-dd", false));
}
}
通过上述实现可以看到,我们自定义了“yyyy-MM-dd”这样一种日期格式,也可以根据具体业务需要,自定义其他日期格式。
然后,我们要使用它们,回到RequestMappingHandlerAdapter 这个类里,新增WebBindingInitializer 的属性定义,调整原有的RequestMappingHandlerAdapter(WebApplicationContext wac)这个构造方法的具体实现,你可以看下调整后的代码。
public RequestMappingHandlerAdapter(WebApplicationContext wac) { this.wac = wac;
this.webBindingInitializer = (WebBindingInitializer)
this.wac.getBean("webBindingInitializer");
}
其实也就是增加了webBindingInitializer属性的设置。
然后再利用IoC容器,让这个构造方法,支持用户通过applicationContext.xml 配置webBindingInitializer,我们可以在applicationContext.xml里新增下面这个配置。
最后我们只需要在BeanWrapperImpl 实现类里,修改setPropertyValue(PropertyValue pv)这个方法的具体实现,把最初我们直接获取DefaultEditor的代码,改为先获取CustomEditor ,如果它不存在,再获取DefaultEditor,你可以看下相关实现。
public void setPropertyValue(PropertyValue pv) {
BeanPropertyHandler propertyHandler = new BeanPropertyHandler(pv.getName());
PropertyEditor pe = this.getCustomEditor(propertyHandler.getPropertyClz());
if (pe == null) {
pe = this.getDefaultEditor(propertyHandler.getPropertyClz());
}
pe.setAsText((String) pv.getValue());
propertyHandler.setValue(pe.getValue());
}
改造后,就能支持用户自定义的CustomEditor ,增强了扩展性。同样的类型,如果既有用户自定义的实现,又有框架默认的实现,那用户自定义的优先。
到这里,传入参数的处理问题我们就探讨完了。
小结
这节课,我们重点探讨了MVC里前后端参数的自动转换,把Request里的参数串自动转换成调用方法里的参数对象。
为了完成传入参数的自动绑定,我们使用了WebDataBinder,它内部用BeanWrapperImpl对象,把属性值的map绑定到目标对象上。绑定的过程中,要对每一种数据类型分别进行格式转换,对基本的标准数据类型,由框架给定默认的转换器,但是对于别的数据类型或者是文化差异很大的数据类型,如日期型,我们可以通过CustomEditor机制让用户自定义。
通过数据的自动绑定,我们不用再通过request.getParameter()方法手动获取参数值,再手动转成对象了,这些HTTP请求里的参数值就自动变成了后端方法里的参数对象值,非常便利。实际上后面我们会看到,这种两层之间的数据自动绑定和转换,在许多场景中都非常有用,比如Jdbc Template。所以这节课的内容需要你好好消化,灵活运用。
完整源代码参见 https://github.com/YaleGuo/minis
课后题
学完这节课的内容,我也给你留一道思考题。我们现在的实现是把Request里面的参数值,按照内部的次序隐含地自动转成后台调用方法参数对象中的某个属性值,那么可不可以使用一个手段,让程序员手动指定某个调用方法的参数跟哪个Request参数进行绑定呢?欢迎你在留言区和我交流讨论,也欢迎你把这节课分享给需要的朋友。我们下节课见!
- 梦某人 👍(2) 💬(1)
目前这个似乎并不能完成对基本类型的转换,反而似乎要处理的是复合类型的转换?当然也可能是我代码和理解存在问题。 目前逻辑上是: Adapter 中对参数进行处理,对于每个参数都有一个 WebDataBinder 进行处理,而这个类在做类型绑定的时候,主要是通过 BeanWrapperImpl 类来进行处理,此时,每个 WebDataBinder 和 BeanWrapperImpl 内的 clazz 指向的都是这个参数的类,基本类型在这里会是一个 String、Long 之类的。 在 BeanWrapperImpl 的 setPropertyValue 方法中,主要是借助于由请求转换而来的 PropertyValue 类,这个PropertyValue 主要有 name 和value 是请求中的请求名和参数,并调用了 BeanPropertyHandler 以 PropertyValue 的 name 值进行处理。 BeanPropertyHandler 首先 根据请求名找到这个请求参数的类里面对应名称的 Field,再根据 Field 获取对应的 clazz,然后使用 Editor 的 getValue 来进行类型转换, 使用 set 方法进行赋值,然后使用对应属性的 get 进行取值操作。 但是基本类型没有 setString 和 getString 之类的情况。。。。。所以他反而没办法处理相对基础的类型。 解决方案应该是考虑配置默认类型的 writemethod 和 readmethod,,优化 BeanPropertyHandler 类。 现在给我的感觉就是 卡在了两头中间,类型转换可以完成,也能从请求中取到值,可以根据方法的参数列表构建对应的类型,但是中间基础类型的绑定的这一块是卡住无法处理的,就是基本类型是有问题的。 最后是思考题,如果要处理顺序问题,那么应该是在 apater中处理方法参数上的标记,根据注解或者标记来调整顺序。比如设置一个注解,指定同一类型的不同参数的名称,或者是指定顺序。然后在 adapter 中拿到方法后,根据注解重排这个方法的参数列表顺序。
2023-04-07 - C. 👍(2) 💬(3)
this.webBindingInitializer = (WebBindingInitializer) this.webApplicationContext.getBean("webBindingInitializer"); 这段代码错误的原因是上一章节的DispatcherServlet文件 protected void refresh() { // 初始化 controller initController(); initHandlerMappings(this.webApplicationContext); initHandlerAdapters(this.parentApplicationContext); } 这个地方传递的容器不正确导致的,因为这两个容器一个是配置文件applicationContext.xml解析bean容器,一个是包扫描controller的bean容器。而webBindingInitializer这个bean的定义在applicationContext.xml配置文件中,所以传入webApplicationContext这个容器对象是获取不到的。改为parentApplicationContext就可以正确执行下去。可能后面章节还会改,但是本章节的内容结束出现了这个问题。
2023-04-04 - lmnsds 👍(1) 💬(1)
WebDataBinder类决定了本文的效果: - controller函数只能是一个自定义对象类型,且只能有一个参数 - request中的参数不能名不能是参数对象中没有的成员名称,否则会报错
2023-05-16 - 风轻扬 👍(0) 💬(2)
这一讲,学到了以下知识点: 1)、如何将请求参数封装到实体类的字段上 2)、用接口,不要用实现类,增加扩展性 Spring框架真是应了现在很流行的一句服务标语:把困难留给自己,把方便带给客户。 另外,有2个问题,请教老师: 1)、为什么要翻译成客户化转换器呢?翻译成自定义转换器是不是更容易理解一点? 2)、BeanWrapperImpl中的getValue方法中,是一个笔误吗?应该是readMethod.setAccessible吧?另外,正常情况下,对外提供的set、get方法都是public的,不需要setAccessible为true了吧?
2023-04-15 - 袁帅 👍(0) 💬(2)
@RequestMapping("/test3") public String doTest3(Integer a) {} 参数是Integer methodParameter.getType().newInstance(); 这行会报错啊 java.lang.NoSuchMethodException: java.lang.Integer.<init>()
2023-04-10 - 不是早晨,就是黄昏 👍(0) 💬(1)
所以我们应该怎么测试呢,在浏览器中输入的地址中参数应该是什么,应该给以下测试程序,来梳理整个流程,否则很乱
2023-04-09 - 马儿 👍(0) 💬(2)
感觉这节课的内容还是有点难懂.目前自己梳理了一下逻辑。大概有以下几个问题,希望老师能抽空解答一下 1.现在的代码似乎不能够解析基本类型,只能解析复杂类型。按照代码逻辑应该是将所有属性绑定到一个复杂类型中去,如果方法参数是基本类型就会报错NoSuchField 2.如果是复杂类型controller中每个参数都通过createbinder创建了WebDataBinder,但是WebDataBinder#BeanWrapperImpl每次都重新创建了BeanWrapperImpl对象,导致初始注册的CustomEditor在后续注册的时候并没有生效。
2023-04-04 - Geek_xbye50 👍(0) 💬(1)
增加一个类似requestParam的注解
2023-04-03 - lmnsds 👍(1) 💬(0)
http://localhost:8080/test4?name=yourname&id=2&birthday=2023-05-16 可以使用如上url进行测试 “/test4”对应的controller method 要以User为参数。User是定义在test.entity下的类。
2023-05-16 - 云从 👍(0) 💬(0)
自定义CustomEditor时,记得在RequestMappingHandlerAdapter的invokeHandlerMethod() 方法里面初始化一下 //注册binder中的editor-- 自定义editer webBindingInitializer.initBinder(wdb);
2023-06-14 - C. 👍(0) 💬(0)
this.webBindingInitializer = (WebBindingInitializer) this.wac.getBean("webBindingInitializer");这段代码在这个章节结束执行报错,No bean,而且出现了这个情况getBean了好多次HelloWorldBean。 webBindingInitializer bean created. com.minis.test.DateInitializer : com.minis.test.DateInitializer@305bb0d handle properties for bean: webBindingInitializer bean registerded............. webBindingInitializer Context Refreshed... com.minis.test.controller.HelloWorldBean bean created. com.minis.test.controller.HelloWorldBean : com.minis.test.controller.HelloWorldBean@4852cf8d handle properties for bean: com.minis.test.controller.HelloWorldBean bean registerded............. com.minis.test.controller.HelloWorldBean [2023-04-04 02:29:06,605] 工件 mini-spring:war: 工件已成功部署 [2023-04-04 02:29:06,605] 工件 mini-spring:war: 部署已花费 553 毫秒 com.minis.test.controller.HelloWorldBean bean created. com.minis.test.controller.HelloWorldBean : com.minis.test.controller.HelloWorldBean@71e1b60b handle properties for bean: com.minis.test.controller.HelloWorldBean bean registerded............. com.minis.test.controller.HelloWorldBean com.minis.test.controller.HelloWorldBean bean created. com.minis.test.controller.HelloWorldBean : com.minis.test.controller.HelloWorldBean@4ab383d7 handle properties for bean: com.minis.test.controller.HelloWorldBean bean registerded............. com.minis.test.controller.HelloWorldBean com.minis.test.controller.HelloWorldBean bean created. com.minis.test.controller.HelloWorldBean : com.minis.test.controller.HelloWorldBean@6962cf89 handle properties for bean: com.minis.test.controller.HelloWorldBean bean registerded............. com.minis.test.controller.HelloWorldBean com.minis.test.controller.HelloWorldBean bean created. com.minis.test.controller.HelloWorldBean : com.minis.test.controller.HelloWorldBean@1256dc01 handle properties for bean: com.minis.test.controller.HelloWorldBean bean registerded............. com.minis.test.controller.HelloWorldBean
2023-04-04