写在前面
源码 。
本文看下如何返回对象,以及页面到前端。
当前生成页面的方式有两种,一种是前后端分离的方式,也就是后端返回对象到前端,第二种方式是后端将生成好的html页面直接返回到浏览器,本文就看下这两种情况。
1:返回对象
我们不能真的就返回一个对象,而是要将对象转换为某种格式后返回,为了标识方法需要转换后返回,定义注解@ResponseBody:
/** * 对象-》string返回 的标记注解 */@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public@interfaceResponseBody{}为了完成转换定义接口HttpMessageConverter:
// com.hc.minispring.web.v6_modelview.converter.HttpMessageConverter/** * 负责将object转换为某种数据格式(如json,xml等)并写回客户端的接口类 */publicinterfaceHttpMessageConverter{voidwrite(Objectobj,HttpServletResponseresponse)throwsIOException;}提供实现类DefaultHttpMessageConverter:
publicclassDefaultHttpMessageConverterimplementsHttpMessageConverter{StringdefaultContentType="text/json;charset=UTF-8";StringdefaultCharacterEncoding="UTF-8";ObjectMapperobjectMapper;publicObjectMappergetObjectMapper(){returnobjectMapper;}publicvoidsetObjectMapper(ObjectMapperobjectMapper){this.objectMapper=objectMapper;}publicDefaultHttpMessageConverter(){}@Overridepublicvoidwrite(Objectobj,HttpServletResponseresponse)throwsIOException{response.setContentType(defaultContentType);response.setCharacterEncoding(defaultCharacterEncoding);writeInternal(obj,response);response.flushBuffer();}privatevoidwriteInternal(Objectobj,HttpServletResponseresponse)throwsIOException{StringsJsonStr=this.objectMapper.writeValuesAsString(obj);PrintWriterpw=response.getWriter();pw.write(sJsonStr);}}ObjectMapper是一个接口,负责将对象转换为特定数据类型,定义如下:
/** * 将object转换为特定数据格式的接口 */publicinterfaceObjectMapper{// 专门为日期格式定义的接口voidsetDateFormat(StringdateFormat);// 专门为数字格式定义的接口voidsetDecimalFormat(StringdecimalFormat);StringwriteValuesAsString(Objectobj);}转json实现类:
/** * 默认实现类 */publicclassDefaultObjectMapperimplementsObjectMapper{StringdateFormat="yyyy-MM-dd";DateTimeFormatterdatetimeFormatter=DateTimeFormatter.ofPattern(dateFormat);StringdecimalFormat="#,##0.00";DecimalFormatdecimalFormatter=newDecimalFormat(decimalFormat);// ...@OverridepublicStringwriteValuesAsString(Objectobj){StringsJsonStr="{";Class<?>clz=obj.getClass();Field[]fields=clz.getDeclaredFields();for(Fieldfield:fields){// ...if(sJsonStr.equals("{")){sField="\""+name+"\":\""+strValue+"\"";}else{sField=",\""+name+"\":\""+strValue+"\"";}sJsonStr+=sField;}sJsonStr+="}";returnsJsonStr;}}2:返回页面
因为返回页面是需要用户进行定制的,为了方便用户的使用,定义类ModelAndView来维护页面信息以及页面需要的数据信息:
// com.hc.minispring.web.v6_modelview.ModelAndView/** * 页面以及页面需要的数据,通过该类设置页面信息,以及页面需要的数据信息 */publicclassModelAndView{// 页面的逻辑名称privateObjectview;privateMap<String,Object>model=newHashMap<>();publicModelAndView(){}// ...}有了页面的逻辑名称还不够,还要能够解析出实际的页面(html,jsp,pdf 等等),因此定义接口:
/** * 负责根据用户指定的viewname解析实际页面地址的接口 */publicinterfaceViewResolver{ViewresolveViewName(StringviewName)throwsException;}实现类:
publicclassInternalResourceViewResolverimplementsViewResolver{privateClass<?>viewClass=null;privateStringviewClassName="";privateStringprefix="";privateStringsuffix="";privateStringcontentType;// ...protectedViewbuildView(StringviewName)throwsException{Class<?>viewClass=getViewClass();Viewview=(View)viewClass.newInstance();view.setUrl(getPrefix()+viewName+getSuffix());StringcontentType=getContentType();view.setContentType(contentType);returnview;}}我们还需要视图渲染的组件,负责渲染页面到前端,也就是这里的View,如下:
/** * 负责渲染页面 */publicinterfaceView{voidrender(Map<String,?>model,HttpServletRequestrequest,HttpServletResponseresponse)throwsException;// ...}实现类:
/** * jsp视图渲染 */publicclassJstlViewimplementsView{publicstaticfinalStringDEFAULT_CONTENT_TYPE="text/html;charset=ISO-8859-1";// ...@Overridepublicvoidrender(Map<String,?>model,HttpServletRequestrequest,HttpServletResponseresponse)throwsException{for(Entry<String,?>e:model.entrySet()){request.setAttribute(e.getKey(),e.getValue());}request.getRequestDispatcher(getUrl()).forward(request,response);}}接着来修改RequestMappingHandlerAdapter执行方法返回ModelView:
// com.hc.minispring.web.v4_split_dispatcher.RequestMappingHandlerAdapterpublicclassRequestMappingHandlerAdapterimplementsHandlerAdapter{@Override// public void handle(HttpServletRequest request, HttpServletResponse response, Object handler)publicModelAndViewhandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{// handleInternal(request, response, (HandlerMethod) handler);returnhandleInternal(request,response,(HandlerMethod)handler);}// private void handleInternal(HttpServletRequest request, HttpServletResponse response,privateModelAndViewhandleInternal(HttpServletRequestrequest,HttpServletResponseresponse,HandlerMethodhandlerMethod)throwsException{try{returninvokeHandlerMethod(request,response,handlerMethod);}catch(Exceptione){e.printStackTrace();}returnnull;}// private void invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {privateModelAndViewinvokeHandlerMethod(HttpServletRequestrequest,HttpServletResponseresponse,HandlerMethodhandlerMethod)throwsException{// ...ObjectreturnObj=invocableMethod.invoke(handlerMethod.getBean(),methodParamObjs);ModelAndViewmav=null;// 如果是设置responsebody注解则返回json格式if(invocableMethod.isAnnotationPresent(ResponseBody.class)){System.out.println("convert to json and return!");this.messageConverter.write(returnObj,response);// return;}elseif(returnObjinstanceofModelAndView){// 在controller方法中直接返回了modelandview,说明用户希望展示某个页面mav=(ModelAndView)returnObj;returnmav;}elseif(returnObjinstanceofString){//在controller 中直接返回了string,说明用户希望按照某种规则返回某个页面mav=newModelAndView();mav.setViewName((String)returnObj);returnmav;}returnmav;// response.getWriter().append(returnObj.toString());}}修改dispatcherservlet支持渲染页面:
packagecom.hc.minispring.web.v6_modelview;// ...publicclassDispatcherServletextendsHttpServlet{privatestaticfinallongserialVersionUID=1L;publicstaticfinalStringWEB_APPLICATION_CONTEXT_ATTRIBUTE=DispatcherServlet.class.getName()+".CONTEXT";privatestaticfinalStringVIEW_RESOLVER_BEAN_NAME="viewResolver";// ...privateViewResolverviewResolver;@Overridepublicvoidinit(ServletConfigconfig)throwsServletException{// ...}protectedvoidRefresh(){// ...initViewResolvers(this.webApplicationContext);}// ...protectedvoidinitViewResolvers(WebApplicationContextwac){try{this.viewResolver=(ViewResolver)wac.getBean(VIEW_RESOLVER_BEAN_NAME);}catch(BeansExceptione){e.printStackTrace();}}@Overrideprotectedvoidservice(HttpServletRequestrequest,HttpServletResponseresponse){request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,this.webApplicationContext);try{doDispatch(request,response);}catch(Exceptione){// ...}protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{// ...ModelAndViewmav=ha.handle(processedRequest,response,handlerMethod);this.render(request,response,mav);}protectedvoidrender(HttpServletRequestrequest,HttpServletResponseresponse,ModelAndViewmv)throwsException{if(mv==null){response.getWriter().flush();response.getWriter().close();return;}// 获取逻辑上的目标视图StringsTarget=mv.getViewName();// 获取视图需要的数据Map<String,Object>modelMap=mv.getModel();// 通过逻辑的目标视图,获取目标视图的渲染器Viewview=resolveViewName(sTarget,modelMap,request);// 执行真正的渲染view.render(modelMap,request,response);}protectedViewresolveViewName(StringviewName,Map<String,Object>model,HttpServletRequestrequest)throwsException{if(this.viewResolver!=null){Viewview=viewResolver.resolveViewName(viewName);if(view!=null){returnview;}}returnnull;}}3:测试
在applicationContext.xml中定义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><beanid="messageConverter"class="com.hc.minispring.web.v6_modelview.converter.DefaultHttpMessageConverter"><propertytype="com.hc.minispring.web.v6_modelview.util.ObjectMapper"name="objectMapper"ref="objectMapper"/></bean><beanid="objectMapper"class="com.hc.minispring.web.v6_modelview.util.DefaultObjectMapper"><propertytype="String"name="dateFormat"value="yyyy/MM/dd"/><propertytype="String"name="decimalFormat"value="###.##"/></bean><beanid="handlerAdapter"class="com.hc.minispring.web.v4_split_dispatcher.RequestMappingHandlerAdapter"><propertytype="com.hc.minispring.web.v6_modelview.converter.HttpMessageConverter"name="messageConverter"ref="messageConverter"/><!-- <property type="com.minis.web.WebBindingInitializer" name="webBindingInitializer" ref="webBindingInitializer"/>--></bean><beanid="viewResolver"class="com.hc.minispring.web.v6_modelview.InternalResourceViewResolver"><propertytype="String"name="viewClassName"value="com.hc.minispring.web.v6_modelview.JstlView"/><propertytype="String"name="prefix"value="/jsp/"/><propertytype="String"name="suffix"value=".jsp"/></bean></beans>定义测试controller:
packagecom.hc.minispring.web.v6_modelview.controller;// ...publicclassHelloController{@AutowiredprivateAServiceaservice;@RequestMapping("/hello1658")@ResponseBody// public String doTest(String name) {publicUserdoTest(/*User user*/){Useruser=newUser();user.setAge(45);user.setName("里斯");user.setBirthday(newDate());returnuser;// return aservice.sayHello() + "--" + user;}@RequestMapping("/hellojsp")// @ResponseBody// public String doTest(String name) {publicStringhellojsp(/*User user*/){return"index";// return aservice.sayHello() + "--" + user;}}一个返回对象,一个返回页面index.jsp,index.jsp如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> index jsp!!!!!!!!!! </body> </html>启动测试返回对象和页面:
C:\Users\dell9020>curl http://localhost:8080/minispring_Web_exploded/hello1658 {"name":"里斯","age":"45","birthday":"2026/07/14"} C:\Users\dell9020>curl http://localhost:8080/minispring_Web_exploded/hellojsp <html> <head> <title>Title</title> </head> <body> index jsp!!!!!!!!!! </body> </html> C:\Users\dell9020>写在后面
参考文章列表
手把手带你写一个 MiniSpring 。