news 2026/7/11 6:36:42

自己动手写一个spring之MVC_3

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自己动手写一个spring之MVC_3

写在前面

源码 。
截至当前,我们的mvc还不支持接收来自request中的参数,本文来实现这部分内容。最终实现如下效果:

1:正文

来定义类WebDataBinder作为数据绑定的总入口,如下:

// com.hc.minispring.web.v5_databind.WebDataBinder// 1:负责完成值绑定职责的类(大一统的类)publicclassWebDataBinder{// 要绑定的目标对象privateObjecttarget;privateClass<?>clz;privateStringobjectName;AbstractPropertyAccessorpropertyAccessor;publicWebDataBinder(Objecttarget){this(target,"");}publicWebDataBinder(Objecttarget,StringtargetName){// ...}publicvoidbind(HttpServletRequestrequest){PropertyValuesmpvs=assignParameters(request);// addBindValues(mpvs, request);doBind(mpvs);}privatevoiddoBind(PropertyValuesmpvs){applyPropertyValues(mpvs);}protectedvoidapplyPropertyValues(PropertyValuesmpvs){getPropertyAccessor().setPropertyValues(mpvs);}// ...}

这里我们通过bind方法完成绑定,首先解析request中的参数转换到PropertyValues(name,value等)中,然后通过doBind方法完成绑定。现在我们已经有了要绑定的对象,也有了需要绑定的数据,接下来就是怎么绑定的问题了。
首先request中的数据类型到目标对象的数据类型是需要一个转换操作的,为此定义接口:

/** * string->其他类型 接口 * 如string->Integer,通过setText方法完成转换,通过getValue方法获取转换结果 * 这里方法名称起的有些欠考虑,不是特别的见名知意!!! */publicinterfacePropertyEditor{// 先调用该方法完成转换,通过该方法完成转换,具体实现类需要在本地定义变量存储转化结果,已被用voidsetAsText(Stringtext);// 不依赖输入,直接设置一个合理的数据(比如期望获取方法执行的时间场景???)voidsetValue(Objectvalue);// 通过该方法获取转换后的结果,必须在setAsText方法后调用ObjectgetValue();// 获取原始值StringgetAsText();}

接着我们就可以定义各种实现类了,为了管理这些实现类,再来定义类PropertyEditorRegistrySupport负责维护并返回这些实现类们:

packagecom.hc.minispring.web.v5_databind.beans;// ...publicclassPropertyEditorRegistrySupport{privateMap<Class<?>,PropertyEditor>defaultEditors;privateMap<Class<?>,PropertyEditor>customEditors;publicPropertyEditorRegistrySupport(){registerDefaultEditors();}protectedvoidregisterDefaultEditors(){createDefaultEditors();}publicPropertyEditorgetDefaultEditor(Class<?>requiredType){returnthis.defaultEditors.get(requiredType);}privatevoidcreateDefaultEditors(){this.defaultEditors=newHashMap<>(64);// Default instances of collection editors.this.defaultEditors.put(int.class,newCustomNumberEditor(Integer.class,false));this.defaultEditors.put(Integer.class,newCustomNumberEditor(Integer.class,true));this.defaultEditors.put(long.class,newCustomNumberEditor(Long.class,false));this.defaultEditors.put(Long.class,newCustomNumberEditor(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,newStringEditor(String.class,true));}// ...}

defaultEditors作为了内置的属性转换实现,customEditors作为用户自定义的属性转换实现(这种预留扩展口子的方式在我们日常工作中也要考虑用起来!!!)。再来定义PropertyEditorRegistrySupport类的子类com.hc.minispring.web.v5_databind.BeanWrapperImpl完成真正的绑定工作:

packagecom.hc.minispring.web.v5_databind;importjava.lang.reflect.Field;importjava.lang.reflect.InvocationTargetException;importjava.lang.reflect.Method;importcom.hc.minispring.web.v5_databind.beans.AbstractPropertyAccessor;importcom.hc.minispring.web.v5_databind.beans.PropertyValue;publicclassBeanWrapperImplextendsAbstractPropertyAccessor{ObjectwrappedObject;Class<?>clz;publicBeanWrapperImpl(Objectobject){super();this.wrappedObject=object;this.clz=object.getClass();}@OverridepublicvoidsetPropertyValue(PropertyValuepv){BeanPropertyHandlerpropertyHandler=newBeanPropertyHandler(pv.getName());PropertyEditorpe=this.getCustomEditor(propertyHandler.getPropertyClz());if(pe==null){pe=this.getDefaultEditor(propertyHandler.getPropertyClz());}if(pe==null){thrownewIllegalArgumentException("can not find property editor for type: "+propertyHandler.getPropertyClz()+" ,please consider register one!");}if(pe!=null){pe.setAsText((String)pv.getValue());propertyHandler.setValue(pe.getValue());}else{propertyHandler.setValue(pv.getValue());}}// 负责设置值到目标对象中(反射)classBeanPropertyHandler{MethodwriteMethod=null;MethodreadMethod=null;Class<?>propertyClz=null;publicClass<?>getPropertyClz(){returnpropertyClz;}publicBeanPropertyHandler(StringpropertyName){try{Fieldfield=clz.getDeclaredField(propertyName);propertyClz=field.getType();this.writeMethod=clz.getDeclaredMethod("set"+propertyName.substring(0,1).toUpperCase()+propertyName.substring(1),propertyClz);this.readMethod=clz.getDeclaredMethod("get"+propertyName.substring(0,1).toUpperCase()+propertyName.substring(1));}catch(NoSuchMethodExceptione){// ...}}publicvoidsetValue(Objectvalue){writeMethod.setAccessible(true);try{writeMethod.invoke(wrappedObject,value);}catch(IllegalAccessExceptione){// ...}}}}

类BeanPropertyHandler负责通过反射设置值到目标对象中,接着就是通过具体数据类型获取对应的属性编辑器转换为目标类型值,最后反射设置,搞定!
还有一个问题,就是如何预留自定义编辑器的口子,为此定义接口:

/** * 负责注册自定义编辑器 */publicinterfaceWebBindingInitializer{voidinitBinder(WebDataBinderbinder);}

string转java.util.Date实现类:

publicclassDateInitializerimplementsWebBindingInitializer{publicvoidinitBinder(WebDataBinderbinder){binder.registerCustomEditor(Date.class,newCustomDateEditor(Date.class,"yyyy-MM-dd",false));}}

CustomDateEditor自定义类:

packagecom.hc.minispring.web.v5_databind;// ...publicclassCustomDateEditorimplementsPropertyEditor{privateClass<Date>dateClass;privateDateTimeFormatterdatetimeFormatter;privatebooleanallowEmpty;privateDatevalue;publicCustomDateEditor()throwsIllegalArgumentException{this(Date.class,"yyyy-MM-dd",true);}// ...}

注册到bean中:

<?xml version="1.0" encoding="UTF-8"?><beans><beanid="aservice"class="com.hc.minispring.web.v5_databind.test.AServiceImpl"/><beanid="webBindingInitializer"class="com.hc.minispring.web.v5_databind.DateInitializer"></bean></beans>

接着我们还需要完成对应类型初始化器的注册工作,这个工作我们在webdatabinder的工厂类中完成,创建webdatabinder类后完成注册,如下:

// com.hc.minispring.web.v5_databind.WebDataBinderFactorypackagecom.hc.minispring.web.v5_databind;// ...publicclassWebDataBinderFactory{// public WebDataBinder createBinder(HttpServletRequest request, Object target, String objectName) {publicWebDataBindercreateBinder(HttpServletRequestrequest,Objecttarget,StringobjectName,WebApplicationContextwac){WebDataBinderwbd=newWebDataBinder(target,objectName);// initBinder(wbd, request);initBinder(wbd,request,wac);returnwbd;}protectedvoidinitBinder(WebDataBinderdataBinder,HttpServletRequestrequest,WebApplicationContextwac){try{// WebBindingInitializer webBindingInitializer = (WebBindingInitializer) wac.getBean("webBindingInitializer");// Map<String, WebBindingInitializer> webBindingInitializerMap = wac.getBeansOfType(WebBindingInitializer.class);String[]beanNames=wac.getBeanDefinitionNames();String[]parentBeanNames=((AnnotationConfigWebApplicationContext)wac).getParentApplicationContext().getBeanDefinitionNames();String[]mergedBeanNames=newString[beanNames.length+parentBeanNames.length];System.arraycopy(beanNames,0,mergedBeanNames,0,beanNames.length);System.arraycopy(parentBeanNames,0,mergedBeanNames,beanNames.length,parentBeanNames.length);// 注册自定义数据绑定编辑器for(StringmergedBeanName:mergedBeanNames){if(mergedBeanName.indexOf("webBindingInitializer")>=0){((WebBindingInitializer)wac.getBean("webBindingInitializer")).initBinder(dataBinder);}}/*for (Map.Entry<String, WebBindingInitializer> stringWebBindingInitializerEntry : webBindingInitializerMap.entrySet()) { stringWebBindingInitializerEntry.getValue().initBinder(dataBinder); }*/// webBindingInitializer.initBinder(dataBinder);}catch(BeansExceptione){e.printStackTrace();}}}

那么我们应该在哪里切入修改呢?因为是调用目标方法时设置参数,所以自然应该是在负责方法执行的HandlerAdapter中了,这里是基于RequestMapping的实现类RequestMappingHandlerAdapter,修改如下:

// com.hc.minispring.web.v4_split_dispatcher.RequestMappingHandlerAdapterpackagecom.hc.minispring.web.v4_split_dispatcher;// .../** * 基于@RequestMapping注解的具具体执行方法实现类 */publicclassRequestMappingHandlerAdapterimplementsHandlerAdapter{WebApplicationContextwac;// 负责注册数据绑定,自定义编辑器WebBindingInitializerwebBindingInitializer;publicRequestMappingHandlerAdapter(WebApplicationContextwac){this.wac=wac;try{this.webBindingInitializer=(WebBindingInitializer)this.wac.getBean("webBindingInitializer");}catch(BeansExceptione){e.printStackTrace();}}@Overridepublicvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{handleInternal(request,response,(HandlerMethod)handler);}privatevoidhandleInternal(HttpServletRequestrequest,HttpServletResponseresponse,HandlerMethodhandlerMethod)throwsException{/* Method method = handler.getMethod(); Object obj = handler.getBean(); Object objResult = null; try { objResult = method.invoke(obj); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } try { response.getWriter().append(objResult.toString()); } catch (IOException e) { e.printStackTrace(); }*/WebDataBinderFactorybinderFactory=newWebDataBinderFactory();Parameter[]methodParameters=handlerMethod.getMethod().getParameters();Object[]methodParamObjs=newObject[methodParameters.length];inti=0;//对调用方法里的每一个参数,处理绑定for(ParametermethodParameter:methodParameters){ObjectmethodParamObj=methodParameter.getType().newInstance();//给这个参数创建WebDataBinderWebDataBinderwdb=binderFactory.createBinder(request,methodParamObj,methodParameter.getName(),wac);// // 注册自定义数据绑定编辑器// this.webBindingInitializer.initBinder(wdb);wdb.bind(request);methodParamObjs[i]=methodParamObj;i++;}MethodinvocableMethod=handlerMethod.getMethod();ObjectreturnObj=invocableMethod.invoke(handlerMethod.getBean(),methodParamObjs);response.getWriter().append(returnObj.toString());}}

定义一个controller测试下:

publicclassHelloController{@AutowiredprivateAServiceaservice;@RequestMapping("/hello1527")// public String doTest(String name) {publicStringdoTest(Useruser){returnaservice.sayHello()+"--"+user;}}
publicclassUser{privateStringname;privateIntegerage;privateDatebirthday;// ...}

启动测试:

写在后面

参考文章列表

手把手带你写一个 MiniSpring 。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/11 6:35:48

5分钟掌握Photoshop图层导出利器:设计师的效率革命

5分钟掌握Photoshop图层导出利器&#xff1a;设计师的效率革命 【免费下载链接】Photoshop-Export-Layers-to-Files-Fast This script allows you to export your layers as individual files at a speed much faster than the built-in script from Adobe. 项目地址: https:…

作者头像 李华
网站建设 2026/7/11 6:34:20

ThreadPoolTaskExecutor vs ThreadPoolTaskScheduler 完整对比

一、核心定位区分1. ThreadPoolTaskExecutor普通异步线程池&#xff0c;负责一次性异步任务 对应注解&#xff1a;Async 底层&#xff1a;ThreadPoolExecutor 用途&#xff1a;接口异步、后台一次性业务、并发批量处理&#xff0c;无定时调度能力2. ThreadPoolTaskScheduler定时…

作者头像 李华
网站建设 2026/7/11 6:33:28

手撕多头注意力

“手撕”&#xff1a;不依赖任何现成的库&#xff08;比如 nn.MultiheadAttention&#xff09;&#xff0c;让你在白板或者在线代码编辑器里把代码完整敲出来。面试官想通过这个题目考察你两点&#xff1a;你是否真正理解了 Transformer 的核心底层数学逻辑。你对深度学习框架中…

作者头像 李华
网站建设 2026/7/11 6:31:17

十天STM32裸机实战:从点灯到FreeRTOS的寄存器级攻坚路线

1. 这不是速成神话&#xff0c;而是一份被验证过的“十天STM32攻坚路线图”“十天学完STM32”——看到这个标题&#xff0c;我第一反应是皱眉。十年前刚带第一批实习生时&#xff0c;我也信过“七天入门单片机”的宣传语&#xff0c;结果第三天就有人对着LED不亮抓耳挠腮&#…

作者头像 李华
网站建设 2026/7/11 6:30:35

UE5语音识别通用方案:云端、离线与混合架构实战解析

1. 项目概述&#xff1a;为什么UE5需要一套通用的语音识别方案&#xff1f;在UE5项目开发中&#xff0c;尤其是涉及到交互叙事、虚拟培训、数字孪生或者无障碍功能时&#xff0c;语音输入正从一个“锦上添花”的特性&#xff0c;变成一个“雪中送炭”的核心交互层。想象一下&am…

作者头像 李华