当前位置: 首页 > news >正文

自己动手写一个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 。

http://www.jsqmd.com/news/1165747/

相关文章:

  • 直流负载管理优化:G6D-ASI继电器与CEC1302控制器的应用
  • Davinci
  • 消息发送的速率自适应:从固定间隔到 429 自愈的演进实践
  • 终极暗黑破坏神2现代优化方案:D2DX技术深度解析与实战指南
  • 大数据毕设选题推荐:基于 Python 的高校招聘薪资数据统计分析系统的设计与实现 基于 Python 的高校岗位分类与智能分析平台【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 手机版行情列表里品种太多太乱,福汇 手机端 MT4 交易列表怎么精简?
  • Edge 148工作区V2与Copilot新页:AI原生浏览器架构解析
  • TLA2518 ADC与MK64FX512VDC12微控制器的信号采集方案
  • AI泡沫与信贷风险:技术热潮下的金融风险分析
  • USB 2.0 协议栈实战:从域、包到事务的3层数据流解析(附抓包分析)
  • STM32F745ZG与AD5593R的硬件协同设计与驱动开发
  • 2026年AI应用开发技术路线:从LLM到企业级智能应用
  • 2026 年 7 月新发布:武陵源诚信的爬坡式输送带订制厂家选型指南,谁把它卡在45度角?传送带崩裂的瞬间,老板脸色煞白 - 企业推荐官【认证官方】
  • GESP2026年6月认证C++八级( 第三部分编程题(1、线网建设))精讲
  • KMP next 数组 4 种实现对比:从 -1 到 nextval 的 3 种代码差异与性能分析
  • 全球化软件定制服务商,怎么选,看什么?
  • Unity渲染命令生成全流程解析:从Draw Call原理到性能优化实战
  • GPT-5.6 三档模型详解:Sol、Terra、Luna 区别、场景与选型建议
  • 企业微信API二次开发:集团并购几十万私域客户大迁移,你的ID映射引擎会乱码吗?
  • 高精度ADC与MCU在信号采集系统中的应用设计
  • 激光驱动电路 3 类方案对比:从 1A 压降到电容放电,实测 5% 电压波动
  • 学龄儿童防晒霜选哪款?2026实测:兼顾防护力与清爽肤感不闷痘
  • 盘点天花板skills-Superpowers中的坑
  • 愿你毕业之时,仍怀滚烫初心——给毕业时我的一封信
  • 梆梤加固逆向实战:从特征识别到内存Dump的完整脱壳分析
  • ANSYS Meshing 与 ICEM CFD 网格对比:3 类复杂几何的生成效率与质量实测
  • 人工智能核心技术解析:从机器学习到工程实践完整指南
  • 系统分析师备考:从海量笔记到高效知识图谱的3步构建法
  • AI技术普及下的成本控制:从本地部署到商业化落地的实践指南
  • 从0到1搭建企业级AI Agent:RAG、MCP、LangGraph和工具调用怎么选?