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