30个类手写Spring核心原理之MVC映射功能

开发 前端
接下来我们来完成MVC模块的功能,应该不需要再做说明。Spring MVC的入口就是从DispatcherServlet开始的,而前面的章节中已完成了web.xml的基础配置。下面就从DispatcherServlet开始添砖加瓦

[[442180]]

接下来我们来完成MVC模块的功能,应该不需要再做说明。Spring MVC的入口就是从DispatcherServlet开始的,而前面的章节中已完成了web.xml的基础配置。下面就从DispatcherServlet开始添砖加瓦。

1 MVC顶层设计

1.1 GPDispatcherServlet

我们已经了解到Servlet的生命周期由init()到service()再到destory()组成,destory()方法我们不做实现。前面我们讲过,这是J2EE中模板模式的典型应用。下面先定义好全局变量:

  1. package com.tom.spring.formework.webmvc.servlet; 
  2.  
  3. import com.tom.spring.formework.annotation.GPController; 
  4. import com.tom.spring.formework.annotation.GPRequestMapping; 
  5. import com.tom.spring.formework.context.GPApplicationContext; 
  6. import com.tom.spring.formework.webmvc.*; 
  7. import lombok.extern.slf4j.Slf4j; 
  8.  
  9. import javax.servlet.ServletConfig; 
  10. import javax.servlet.ServletException; 
  11. import javax.servlet.http.HttpServlet; 
  12. import javax.servlet.http.HttpServletRequest; 
  13. import javax.servlet.http.HttpServletResponse; 
  14. import java.io.File; 
  15. import java.io.IOException; 
  16. import java.lang.reflect.Method; 
  17. import java.util.*; 
  18. import java.util.regex.Matcher; 
  19. import java.util.regex.Pattern; 
  20.  
  21. //Servlet只是作为一个MVC的启动入口 
  22. @Slf4j 
  23. public class GPDispatcherServlet extends HttpServlet { 
  24.  
  25.     private  final String LOCATION = "contextConfigLocation"
  26.  
  27.     //读者可以思考一下这样设计的经典之处 
  28.     //GPHandlerMapping最核心的设计,也是最经典的 
  29.     //它直接干掉了Struts、Webwork等MVC框架 
  30.     private List<GPHandlerMapping> handlerMappings = new ArrayList<GPHandlerMapping>(); 
  31.  
  32.     private Map<GPHandlerMapping,GPHandlerAdapter> handlerAdapters = new HashMap<GPHandlerMapping, GPHandlerAdapter>(); 
  33.  
  34.     private List<GPViewResolver> viewResolvers = new ArrayList<GPViewResolver>(); 
  35.  
  36.     private GPApplicationContext context; 
  37.  
  38.  
  39. 下面实现init()方法,我们主要完成IoC容器的初始化和Spring MVC九大组件的初始化。 
  40.     @Override 
  41.     public void init(ServletConfig config) throws ServletException { 
  42.         //相当于把IoC容器初始化了 
  43.         context = new GPApplicationContext(config.getInitParameter(LOCATION)); 
  44.         initStrategies(context); 
  45.     } 
  46.  
  47.     protected void initStrategies(GPApplicationContext context) { 
  48.  
  49.         //有九种策略 
  50.         //针对每个用户请求,都会经过一些处理策略处理,最终才能有结果输出 
  51.         //每种策略可以自定义干预,但是最终的结果都一致 
  52.  
  53.         // =============  这里说的就是传说中的九大组件 ================ 
  54.         initMultipartResolver(context);//文件上传解析,如果请求类型是multipart,将通过MultipartResolver进行文件上传解析 
  55.         initLocaleResolver(context);//本地化解析 
  56.         initThemeResolver(context);//主题解析 
  57.  
  58.         /** 我们自己会实现 */ 
  59.         //GPHandlerMapping 用来保存Controller中配置的RequestMapping和Method的对应关系 
  60.         initHandlerMappings(context);//通过HandlerMapping将请求映射到处理器 
  61.         /** 我们自己会实现 */ 
  62.         //HandlerAdapters 用来动态匹配Method参数,包括类转换、动态赋值 
  63.         initHandlerAdapters(context);//通过HandlerAdapter进行多类型的参数动态匹配 
  64.  
  65.         initHandlerExceptionResolvers(context);//如果执行过程中遇到异常,将交给HandlerExceptionResolver来解析 
  66.         initRequestToViewNameTranslator(context);//直接将请求解析到视图名 
  67.  
  68.         /** 我们自己会实现 */ 
  69.         //通过ViewResolvers实现动态模板的解析 
  70.         //自己解析一套模板语言 
  71.         initViewResolvers(context);//通过viewResolver将逻辑视图解析到具体视图实现 
  72.  
  73.         initFlashMapManager(context);//Flash映射管理器 
  74.     } 
  75.  
  76.     private void initFlashMapManager(GPApplicationContext context) {} 
  77.     private void initRequestToViewNameTranslator(GPApplicationContext context) {} 
  78.     private void initHandlerExceptionResolvers(GPApplicationContext context) {} 
  79.     private void initThemeResolver(GPApplicationContext context) {} 
  80.     private void initLocaleResolver(GPApplicationContext context) {} 
  81.     private void initMultipartResolver(GPApplicationContext context) {} 
  82.  
  83.     //将Controller中配置的RequestMapping和Method进行一一对应 
  84.     private void initHandlerMappings(GPApplicationContext context) { 
  85.         //按照我们通常的理解应该是一个Map 
  86.         //Map<String,Method> map; 
  87.         //map.put(url,Method) 
  88.  
  89.         //首先从容器中获取所有的实例 
  90.         String [] beanNames = context.getBeanDefinitionNames(); 
  91.         try { 
  92.             for (String beanName : beanNames) { 
  93.                 //到了MVC层,对外提供的方法只有一个getBean()方法 
  94.                 //返回的对象不是BeanWrapper,怎么办? 
  95.                 Object controller = context.getBean(beanName); 
  96.                 //Object controller = GPAopUtils.getTargetObject(proxy); 
  97.                 Class<?> clazz = controller.getClass(); 
  98.  
  99.                 if (!clazz.isAnnotationPresent(GPController.class)) { 
  100.                     continue
  101.                 } 
  102.  
  103.                 String baseUrl = ""
  104.  
  105.                 if (clazz.isAnnotationPresent(GPRequestMapping.class)) { 
  106.                     GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class); 
  107.                     baseUrl = requestMapping.value(); 
  108.                 } 
  109.  
  110.                 //扫描所有的public类型的方法 
  111.                 Method[] methods = clazz.getMethods(); 
  112.                 for (Method method : methods) { 
  113.                     if (!method.isAnnotationPresent(GPRequestMapping.class)) { 
  114.                         continue
  115.                     } 
  116.  
  117.                     GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class); 
  118.                     String regex = ("/" + baseUrl + requestMapping.value().replaceAll("\\*"".*")).replaceAll("/+""/"); 
  119.                     Pattern pattern = Pattern.compile(regex); 
  120.                     this.handlerMappings.add(new GPHandlerMapping(pattern, controller, method)); 
  121.                     log.info("Mapping: " + regex + " , " + method); 
  122.  
  123.                 } 
  124.  
  125.             } 
  126.         }catch (Exception e){ 
  127.             e.printStackTrace(); 
  128.         } 
  129.  
  130.     } 
  131.  
  132.     private void initHandlerAdapters(GPApplicationContext context) { 
  133.         //在初始化阶段,我们能做的就是,将这些参数的名字或者类型按一定的顺序保存下来 
  134.         //因为后面用反射调用的时候,传的形参是一个数组 
  135.         //可以通过记录这些参数的位置index,逐个从数组中取值,这样就和参数的顺序无关了 
  136.         for (GPHandlerMapping handlerMapping : this.handlerMappings){ 
  137.             //每个方法有一个参数列表,这里保存的是形参列表 
  138.             this.handlerAdapters.put(handlerMapping,new GPHandlerAdapter()); 
  139.         } 
  140.  
  141.     } 
  142.  
  143.     private void initViewResolvers(GPApplicationContext context) { 
  144.         //在页面中输入http://localhost/first.html 
  145.         //解决页面名字和模板文件关联的问题 
  146.         String templateRoot = context.getConfig().getProperty("templateRoot"); 
  147.         String templateRootPath = this.getClass().getClassLoader().getResource (templateRoot).getFile(); 
  148.  
  149.         File templateRootDir = new File(templateRootPath); 
  150.  
  151.         for (File template : templateRootDir.listFiles()) { 
  152.             this.viewResolvers.add(new GPViewResolver(templateRoot)); 
  153.         } 
  154.  
  155.     } 

在上面的代码中,我们只实现了九大组件中的三大核心组件的基本功能,分别是HandlerMapping、HandlerAdapter、ViewResolver,完成MVC最核心的调度功能。其中HandlerMapping就是策略模式的应用,用输入URL间接调用不同的Method已达到获取结果的目的。顾名思义,HandlerAdapter应用的是适配器模式,将Request的字符型参数自动适配为Method的Java实参,主要实现参数列表自动适配和类型转换功能。ViewResolver也算一种策略,根据不同的请求选择不同的模板引擎来进行页面的渲染。接下来看service()方法,它主要负责接收请求,得到Request和Response对象。在Servlet子类中service()方法被拆分成doGet()方法和doPost()方法。我们在doGet()方法中直接调用doPost()方法,在doPost()方法中调用doDispatch()方法,真正的调用逻辑由doDispatch()来执行。

  1. @Override 
  2.     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
  3.         this.doPost(req,resp); 
  4.     } 
  5.  
  6.     @Override 
  7.     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
  8.         try { 
  9.             doDispatch(req, resp); 
  10.         }catch (Exception e){ 
  11.             resp.getWriter().write("<font size='25' color='blue'>500 Exception</font><br/>Details: <br/>" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]",""
  12.                     .replaceAll("\\s","\r\n") +  "<font color='green'><i>Copyright@GupaoEDU </i></font>"); 
  13.             e.printStackTrace(); 
  14.         } 
  15.     } 
  16.  
  17.     private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{ 
  18.  
  19.         //根据用户请求的URL来获得一个Handler 
  20.         GPHandlerMapping handler = getHandler(req); 
  21.         if(handler == null){ 
  22.             processDispatchResult(req,resp,new GPModelAndView("404")); 
  23.             return
  24.         } 
  25.  
  26.         GPHandlerAdapter ha = getHandlerAdapter(handler); 
  27.  
  28.         //这一步只是调用方法,得到返回值 
  29.         GPModelAndView mv = ha.handle(req, resp, handler); 
  30.  
  31.         //这一步才是真的输出 
  32.         processDispatchResult(req,resp, mv); 
  33.  
  34.     } 
  35.  
  36.     private void processDispatchResult(HttpServletRequest request,HttpServletResponse response, GPModelAndView mv) throws Exception { 
  37.         //调用viewResolver的resolveViewName()方法 
  38.         if(null == mv){ return;} 
  39.  
  40.         if(this.viewResolvers.isEmpty()){ return;} 
  41.  
  42.         if (this.viewResolvers != null) { 
  43.             for (GPViewResolver viewResolver : this.viewResolvers) { 
  44.                 GPView view = viewResolver.resolveViewName(mv.getViewName(), null); 
  45.                 if (view != null) { 
  46.                     view.render(mv.getModel(),request,response); 
  47.                     return
  48.                 } 
  49.             } 
  50.         } 
  51.  
  52.     } 
  53.  
  54.     private GPHandlerAdapter getHandlerAdapter(GPHandlerMapping handler) { 
  55.         if(this.handlerAdapters.isEmpty()){return  null;} 
  56.         GPHandlerAdapter ha = this.handlerAdapters.get(handler); 
  57.         if (ha.supports(handler)) { 
  58.             return ha; 
  59.         } 
  60.         return null
  61.     } 
  62.  
  63.     private GPHandlerMapping getHandler(HttpServletRequest req) { 
  64.  
  65.         if(this.handlerMappings.isEmpty()){ return  null;} 
  66.  
  67.         String url = req.getRequestURI(); 
  68.         String contextPath = req.getContextPath(); 
  69.         url = url.replace(contextPath,"").replaceAll("/+","/"); 
  70.  
  71.         for (GPHandlerMapping handler : this.handlerMappings) { 
  72.             Matcher matcher = handler.getPattern().matcher(url); 
  73.             if(!matcher.matches()){ continue;} 
  74.             return handler; 
  75.         } 
  76.  
  77.         return null

 GPDisptcherServlet的完整代码请关注微信公众号回复“Spring”。下面补充实现上面的代码中缺失的依赖类。

1.2 GPHandlerMapping

我们已经知道HandlerMapping主要用来保存URL和Method的对应关系,这里其实使用的是策略模式。

  1. package com.tom.spring.formework.webmvc; 
  2.  
  3. import java.lang.reflect.Method; 
  4. import java.util.regex.Pattern; 
  5.  
  6. public class GPHandlerMapping { 
  7.     private Object controller; //目标方法所在的contrller对象 
  8.     private Method method; //URL对应的目标方法 
  9.     private Pattern pattern;  //URL的封装 
  10.  
  11.     public GPHandlerMapping(Pattern pattern,Object controller, Method method) { 
  12.         this.controller = controller; 
  13.         this.method = method; 
  14.         this.pattern = pattern; 
  15.     } 
  16.  
  17.     public Object getController() { 
  18.         return controller; 
  19.     } 
  20.  
  21.     public void setController(Object controller) { 
  22.         this.controller = controller; 
  23.     } 
  24.  
  25.     public Method getMethod() { 
  26.         return method; 
  27.     } 
  28.  
  29.     public void setMethod(Method method) { 
  30.         this.method = method; 
  31.     } 
  32.  
  33.     public Pattern getPattern() { 
  34.         return pattern; 
  35.     } 
  36.  
  37.     public void setPattern(Pattern pattern) { 
  38.         this.pattern = pattern; 
  39.     } 

1.3 GPHandlerAdapter

原生Spring的HandlerAdapter主要完成请求传递到服务端的参数列表与Method实参列表的对应关系,完成参数值的类型转换工作。核心方法是handle(),在handle()方法中用反射来调用被适配的目标方法,并将转换包装好的参数列表传递过去。

  1. package com.tom.spring.formework.webmvc; 
  2.  
  3. import com.tom.spring.formework.annotation.GPRequestParam; 
  4.  
  5. import javax.servlet.http.HttpServletRequest; 
  6. import javax.servlet.http.HttpServletResponse; 
  7. import java.lang.annotation.Annotation; 
  8. import java.util.Arrays; 
  9. import java.util.HashMap; 
  10. import java.util.Map; 
  11.  
  12. //专人干专事 
  13. public class GPHandlerAdapter { 
  14.  
  15.     public boolean supports(Object handler){ 
  16.         return (handler instanceof GPHandlerMapping); 
  17.     } 
  18.  
  19.     public GPModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception{ 
  20.         GPHandlerMapping handlerMapping = (GPHandlerMapping)handler; 
  21.  
  22.         //每个方法有一个参数列表,这里保存的是形参列表 
  23.         Map<String,Integer> paramMapping = new HashMap<String, Integer>(); 
  24.  
  25.         //这里只是给出命名参数 
  26.         Annotation[][] pa = handlerMapping.getMethod().getParameterAnnotations(); 
  27.         for (int i = 0; i < pa.length ; i ++) { 
  28.             for (Annotation a : pa[i]) { 
  29.                 if(a instanceof GPRequestParam){ 
  30.                     String paramName = ((GPRequestParam) a).value(); 
  31.                     if(!"".equals(paramName.trim())){ 
  32.                         paramMapping.put(paramName,i); 
  33.                     } 
  34.                 } 
  35.             } 
  36.         } 
  37.  
  38.         //根据用户请求的参数信息,跟Method中的参数信息进行动态匹配 
  39.         //resp 传进来的目的只有一个:将其赋值给方法参数,仅此而已 
  40.  
  41.         //只有当用户传过来的ModelAndView为空的时候,才会新建一个默认的 
  42.  
  43.         //1. 要准备好这个方法的形参列表 
  44.         //方法重载时形参的决定因素:参数的个数、参数的类型、参数顺序、方法的名字 
  45.         //只处理Request和Response 
  46.         Class<?>[] paramTypes = handlerMapping.getMethod().getParameterTypes(); 
  47.         for (int i = 0;i < paramTypes.length; i ++) { 
  48.             Class<?> type = paramTypes[i]; 
  49.             if(type == HttpServletRequest.class || 
  50.                     type == HttpServletResponse.class){ 
  51.                 paramMapping.put(type.getName(),i); 
  52.             } 
  53.         } 
  54.  
  55.  
  56.  
  57.         //2. 得到自定义命名参数所在的位置 
  58.         //用户通过URL传过来的参数列表 
  59.         Map<String,String[]> reqParameterMap = req.getParameterMap(); 
  60.  
  61.         //3. 构造实参列表 
  62.         Object [] paramValues = new Object[paramTypes.length]; 
  63.  
  64.         for (Map.Entry<String,String[]> param : reqParameterMap.entrySet()) { 
  65.             String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]",""). replaceAll("\\s",""); 
  66.  
  67.             if(!paramMapping.containsKey(param.getKey())){continue;} 
  68.  
  69.             int index = paramMapping.get(param.getKey()); 
  70.  
  71.             //因为页面传过来的值都是String类型的,而在方法中定义的类型是千变万化的 
  72.             //所以要针对我们传过来的参数进行类型转换 
  73.             paramValues[index] = caseStringValue(value,paramTypes[index]); 
  74.         } 
  75.  
  76.         if(paramMapping.containsKey(HttpServletRequest.class.getName())) { 
  77.             int reqIndex = paramMapping.get(HttpServletRequest.class.getName()); 
  78.             paramValues[reqIndex] = req; 
  79.         } 
  80.  
  81.         if(paramMapping.containsKey(HttpServletResponse.class.getName())) { 
  82.             int respIndex = paramMapping.get(HttpServletResponse.class.getName()); 
  83.             paramValues[respIndex] = resp; 
  84.         } 
  85.  
  86.         //4. 从handler中取出Controller、Method,然后利用反射机制进行调用 
  87.  
  88.         Object result = handlerMapping.getMethod().invoke(handlerMapping.getController(), paramValues); 
  89.  
  90.         if(result == null){ return  null; } 
  91.  
  92.         boolean isModelAndView = handlerMapping.getMethod().getReturnType() == GPModelAndView.class; 
  93.         if(isModelAndView){ 
  94.             return (GPModelAndView)result; 
  95.         }else
  96.             return null
  97.         } 
  98.     } 
  99.  
  100.     private Object caseStringValue(String value,Class<?> clazz){ 
  101.         if(clazz == String.class){ 
  102.             return value; 
  103.         }else if(clazz == Integer.class){ 
  104.             return  Integer.valueOf(value); 
  105.         }else if(clazz == int.class){ 
  106.             return Integer.valueOf(value).intValue(); 
  107.         }else { 
  108.             return null
  109.         } 
  110.     } 
  111.  

1.4 GPModelAndView

原生Spring中ModelAndView类主要用于封装页面模板和要往页面传送的参数的对应关系。

  1. package com.tom.spring.formework.webmvc; 
  2.  
  3. import java.util.Map; 
  4.  
  5. public class GPModelAndView { 
  6.  
  7.     private String viewName; //页面模板的名称 
  8.     private Map<String,?> model; //往页面传送的参数 
  9.  
  10.     public GPModelAndView(String viewName) { 
  11.         this(viewName,null); 
  12.     } 
  13.     public GPModelAndView(String viewName, Map<String, ?> model) { 
  14.         this.viewName = viewName; 
  15.         this.model = model; 
  16.     } 
  17.  
  18.     public String getViewName() { 
  19.         return viewName; 
  20.     } 
  21.  
  22.     public void setViewName(String viewName) { 
  23.         this.viewName = viewName; 
  24.     } 
  25.  
  26.     public Map<String, ?> getModel() { 
  27.         return model; 
  28.     } 
  29.  
  30.     public void setModel(Map<String, ?> model) { 
  31.         this.model = model; 
  32.     } 

1.5 GPViewResolver

原生Spring中的ViewResolver主要完成模板名称和模板解析引擎的匹配。通过在Serlvet中调用resolveViewName()方法来获得模板所对应的View。在这个Mini版本中简化了实现,只实现了一套默认的模板引擎,语法也是完全自定义的。

  1. package com.tom.spring.formework.webmvc; 
  2.  
  3. import java.io.File; 
  4. import java.util.Locale; 
  5.  
  6. //设计这个类的主要目的是: 
  7. //1. 将一个静态文件变为一个动态文件 
  8. //2. 根据用户传送不同的参数,产生不同的结果 
  9. //最终输出字符串,交给Response输出 
  10. public class GPViewResolver { 
  11.     private final String DEFAULT_TEMPLATE_SUFFIX = ".html"
  12.  
  13.     private File templateRootDir; 
  14.     private String viewName; 
  15.  
  16.     public GPViewResolver(String templateRoot){ 
  17.         String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot). getFile(); 
  18.         this.templateRootDir = new File(templateRootPath); 
  19.     } 
  20.  
  21.     public GPView resolveViewName(String viewName, Locale locale) throws Exception { 
  22.         this.viewName = viewName; 
  23.         if(null == viewName || "".equals(viewName.trim())){ return null;} 
  24.         viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX); 
  25.         File templateFile = new File((templateRootDir.getPath() + "/" + viewName).replaceAll ("/+""/")); 
  26.         return new GPView(templateFile); 
  27.     } 
  28.  
  29.     public String getViewName() { 
  30.         return viewName; 
  31.     } 

1.6 GPView

这里的GPView就是前面所说的自定义模板解析引擎,其核心方法是render()。在render()方法中完成对模板的渲染,最终返回浏览器能识别的字符串,通过Response输出。

  1. package com.tom.spring.formework.webmvc; 
  2.  
  3. import javax.servlet.http.HttpServletRequest; 
  4. import javax.servlet.http.HttpServletResponse; 
  5. import java.io.RandomAccessFile; 
  6. import java.util.Map; 
  7. import java.io.File; 
  8. import java.util.regex.Matcher; 
  9. import java.util.regex.Pattern; 
  10.  
  11. public class GPView { 
  12.  
  13.     public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=utf-8"
  14.  
  15.     private File viewFile; 
  16.  
  17.     public GPView(File viewFile){ 
  18.         this.viewFile = viewFile; 
  19.     } 
  20.  
  21.     public String getContentType(){ 
  22.         return DEFAULT_CONTENT_TYPE; 
  23.     } 
  24.  
  25.     public void render(Map<String, ?> model,HttpServletRequest request, HttpServletResponse response) throws Exception{ 
  26.         StringBuffer sb = new StringBuffer(); 
  27.         RandomAccessFile ra = new RandomAccessFile(this.viewFile,"r"); 
  28.  
  29.  
  30.         try { 
  31.             String line = null
  32.             while (null != (line = ra.readLine())) { 
  33.                 line = new String(line.getBytes("ISO-8859-1"),"utf-8"); 
  34.                 Pattern pattern = Pattern.compile("¥\\{[^\\}]+\\}",Pattern.CASE_INSENSITIVE); 
  35.                 Matcher matcher = pattern.matcher(line); 
  36.  
  37.                 while (matcher.find()) { 
  38.  
  39.                     String paramName = matcher.group(); 
  40.                     paramName = paramName.replaceAll("¥\\{|\\}",""); 
  41.                     Object paramValue = model.get(paramName); 
  42.                     if (null == paramValue) { continue; } 
  43.                     //要把¥{}中间的这个字符串取出来 
  44.                     line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString())); 
  45.                     matcher = pattern.matcher(line); 
  46.  
  47.                 } 
  48.  
  49.                 sb.append(line); 
  50.             } 
  51.         }finally { 
  52.             ra.close(); 
  53.         } 
  54.         response.setCharacterEncoding("utf-8"); 
  55.         //response.setContentType(DEFAULT_CONTENT_TYPE); 
  56.         response.getWriter().write(sb.toString()); 
  57.     } 
  58.  
  59.     //处理特殊字符 
  60.     public static String makeStringForRegExp(String str) { 
  61.          return str.replace("\\", "\\\\").replace("*", "\\*") 
  62.         .replace("+""\\+").replace("|""\\|"
  63.         .replace("{""\\{").replace("}""\\}"
  64.         .replace("(""\\(").replace(")""\\)"
  65.         .replace("^""\\^").replace("$""\\$"
  66.         .replace("[""\\[").replace("]""\\]"
  67.         .replace("?""\\?").replace(",""\\,"
  68.         .replace(".""\\.").replace("&""\\&"); 
  69.     } 
  70.  

从上面的代码可以看出,GPView是基于HTML文件来对页面进行渲染的。但是加入了一些自定义语法,例如在模板页面中扫描到¥{name}这样的表达式,就会从ModelAndView的Model中找到name所对应的值,并且用正则表达式将其替换(外国人喜欢用美元符号$,我们的模板引擎就用人民币符号¥)。

2 业务代码实现

2.1 IQueryService

定义一个负责查询业务的顶层接口IQueryService,提供一个query()方法:

  1. package com.tom.spring.demo.service; 
  2.  
  3. /** 
  4.  * 查询业务 
  5.  * 
  6.  */ 
  7. public interface IQueryService  { 
  8.  
  9.    /** 
  10.     * 查询 
  11.     */ 
  12.    public String query(String name); 
  13.  

2.2 QueryService

查询业务的实现QueryService也非常简单,就是打印一下调用时间和传入的参数,并封装为JSON格式返回:

  1. package com.tom.spring.demo.service.impl; 
  2.  
  3. import java.text.SimpleDateFormat; 
  4. import java.util.Date
  5.  
  6. import com.tom.spring.demo.service.IQueryService; 
  7. import com.tom.spring.formework.annotation.GPService; 
  8. import lombok.extern.slf4j.Slf4j; 
  9.  
  10. /** 
  11.  * 查询业务 
  12.  * 
  13.  */ 
  14. @GPService 
  15. @Slf4j 
  16. public class QueryService implements IQueryService { 
  17.  
  18.    /** 
  19.     * 查询 
  20.     */ 
  21.    public String query(String name) { 
  22.       SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
  23.       String time = sdf.format(new Date()); 
  24.       String json = "{name:\"" + name + "\",time:\"" + time + "\"}"
  25.       log.info("这是在业务方法中打印的:" + json); 
  26.       return json; 
  27.    } 
  28.  

2.3 IModifyService

定义一个增、删、改业务的顶层接口IModifyService:

  1. package com.tom.spring.demo.service; 
  2. /** 
  3.  * 增、删、改业务 
  4.  */ 
  5. public interface IModifyService { 
  6.    /** 
  7.     * 增加 
  8.     */ 
  9.    public String add(String name, String addr) ; 
  10.    /** 
  11.     * 修改 
  12.     */ 
  13.    public String edit(Integer id, String name); 
  14.    /** 
  15.     * 删除 
  16.     */ 
  17.    public String remove(Integer id); 
  18.  

2.4 ModifyService

增、删、改业务的实现ModifyService也非常简单,主要是打印传过来的参数:

  1. package com.tom.spring.demo.service.impl; 
  2. import com.tom.spring.demo.service.IModifyService; 
  3. import com.tom.spring.formework.annotation.GPService; 
  4.  
  5. /** 
  6.  * 增、删、改业务 
  7.  */ 
  8. @GPService 
  9. public class ModifyService implements IModifyService { 
  10.    /** 
  11.     * 增加 
  12.     */ 
  13.    public String add(String name,String addr) { 
  14.       return "modifyService add,name=" + name + ",addr=" + addr; 
  15.    } 
  16.    /** 
  17.     * 修改 
  18.     */ 
  19.    public String edit(Integer id,String name) { 
  20.       return "modifyService edit,id=" + id + ",name=" + name
  21.    } 
  22.    /** 
  23.     * 删除 
  24.     */ 
  25.    public String remove(Integer id) { 
  26.       return "modifyService id=" + id; 
  27.    } 

2.5 MyAction

Controller的主要功能是负责调度,不做业务实现。业务实现方法全部在Service层,一般我们会将Service实例注入Controller。MyAction中主要实现对IQueryService和IModifyService的调度,统一返回结果:

  1. package com.tom.spring.demo.action
  2.  
  3. import java.io.IOException; 
  4. import javax.servlet.http.HttpServletRequest; 
  5. import javax.servlet.http.HttpServletResponse; 
  6. import com.tom.spring.demo.service.IModifyService; 
  7. import com.tom.spring.demo.service.IQueryService; 
  8. import com.tom.spring.formework.annotation.GPAutowired; 
  9. import com.tom.spring.formework.annotation.GPController; 
  10. import com.tom.spring.formework.annotation.GPRequestMapping; 
  11. import com.tom.spring.formework.annotation.GPRequestParam; 
  12. import com.tom.spring.formework.webmvc.GPModelAndView; 
  13.  
  14. /** 
  15.  * 公布接口URL 
  16.  */ 
  17. @GPController 
  18. @GPRequestMapping("/web"
  19. public class MyAction { 
  20.  
  21.    @GPAutowired IQueryService queryService; 
  22.    @GPAutowired IModifyService modifyService; 
  23.  
  24.    @GPRequestMapping("/query.json"
  25.    public GPModelAndView query(HttpServletRequest request, HttpServletResponse response, 
  26.                         @GPRequestParam("name") String name){ 
  27.       String result = queryService.query(name); 
  28.       return out(response,result); 
  29.    } 
  30.    @GPRequestMapping("/add*.json"
  31.    public GPModelAndView add(HttpServletRequest request,HttpServletResponse response, 
  32.             @GPRequestParam("name") String name,@GPRequestParam("addr") String addr){ 
  33.       String result = modifyService.add(name,addr); 
  34.       return out(response,result); 
  35.    } 
  36.    @GPRequestMapping("/remove.json"
  37.    public GPModelAndView remove(HttpServletRequest request,HttpServletResponse response, 
  38.          @GPRequestParam("id"Integer id){ 
  39.       String result = modifyService.remove(id); 
  40.       return out(response,result); 
  41.    } 
  42.    @GPRequestMapping("/edit.json"
  43.    public GPModelAndView edit(HttpServletRequest request,HttpServletResponse response, 
  44.          @GPRequestParam("id"Integer id, 
  45.          @GPRequestParam("name") String name){ 
  46.       String result = modifyService.edit(id,name); 
  47.       return out(response,result); 
  48.    } 
  49.  
  50.    private GPModelAndView out(HttpServletResponse resp,String str){ 
  51.       try { 
  52.          resp.getWriter().write(str); 
  53.       } catch (IOException e) { 
  54.          e.printStackTrace(); 
  55.       } 
  56.       return null
  57.    } 

2.6 PageAction

专门设计PageAction是为了演示Mini版Spring对模板引擎的支持,实现从Controller层到View层的传参,以及对模板的渲染进行最终输出:

  1. package com.tom.spring.demo.action
  2.  
  3. import java.util.HashMap; 
  4. import java.util.Map; 
  5. import com.tom.spring.demo.service.IQueryService; 
  6. import com.tom.spring.formework.annotation.GPAutowired; 
  7. import com.tom.spring.formework.annotation.GPController; 
  8. import com.tom.spring.formework.annotation.GPRequestMapping; 
  9. import com.tom.spring.formework.annotation.GPRequestParam; 
  10. import com.tom.spring.formework.webmvc.GPModelAndView; 
  11.  
  12. /** 
  13.  * 公布接口URL 
  14.  */ 
  15. @GPController 
  16. @GPRequestMapping("/"
  17. public class PageAction { 
  18.  
  19.    @GPAutowired IQueryService queryService; 
  20.  
  21.    @GPRequestMapping("/first.html"
  22.    public GPModelAndView query(@GPRequestParam("teacher") String teacher){ 
  23.       String result = queryService.query(teacher); 
  24.       Map<String,Object> model = new HashMap<String,Object>(); 
  25.       model.put("teacher", teacher); 
  26.       model.put("data", result); 
  27.       model.put("token""123456"); 
  28.       return new GPModelAndView("first.html",model); 
  29.    } 
  30.  

3 定制模板页面

为了更全面地演示页面渲染效果,分别定义了first.html对应PageAction中的first.html请求、404.html默认页和500.html异常默认页。

3.1 first.html

first.html定义如下:

  1. <!DOCTYPE html> 
  2. <html lang="zh-cn"
  3. <head> 
  4.    <meta charset="utf-8"
  5.    <title>SpringMVC模板引擎演示</title> 
  6. </head> 
  7. <center> 
  8.    <h1>大家好,我是¥{teacher}老师<br/>欢迎大家一起来探索Spring的世界</h1> 
  9.    <h3>Hello,My name is ¥{teacher}</h3> 
  10.    <div>¥{data}</div> 
  11.    Token值:¥{token} 
  12. </center> 
  13. </html> 

3.2 404.html

404.html定义如下:

  1. <!DOCTYPE html> 
  2. <html lang="zh-cn"
  3. <head> 
  4.     <meta charset="utf-8"
  5.     <title>页面去火星了</title> 
  6. </head> 
  7. <body> 
  8.     <font size='25' color='red'>404 Not Found</font><br/><font color='green'><i>Copyright @GupaoEDU</i></font> 
  9. </body> 
  10. </html> 

3.3 500.html

500.html定义如下:

  1. <!DOCTYPE html> 
  2. <html lang="zh-cn"
  3. <head> 
  4.     <meta charset="utf-8"
  5.     <title>服务器好像累了</title> 
  6. </head> 
  7. <body> 
  8.     <font size='25' color='blue'>500 服务器好像有点累了,需要休息一下</font><br/> 
  9.     <b>Message:¥{detail}</b><br/> 
  10.     <b>StackTrace:¥{stackTrace}</b><br/> 
  11.     <font color='green'><i>Copyright@GupaoEDU</i></font> 
  12. </body> 
  13. </html> 

4 运行效果演示

在浏览器中输入 http://localhost/web/query.json?name=Tom ,就会映射到MyAction中的@GPRequestMapping(“query.json”)对应的query()方法,得到如下图所示结果。

在浏览器中输入 http://localhost/web/addTom.json?name=tom&addr=HunanChangsha ,就会映射到MyAction中的@GPRequestMapping(“add*.json”)对应的add()方法,得到如下图所示结果。

在浏览器中输入 http://localhost/web/remove.json?id=66 ,就会映射到MyAction中的@GPRequestMapping(“remove.json”)对应的remove()方法,并将id自动转换为int类型,得到如下图所示结果。

在浏览器中输入 http://localhost/web/edit.json?id=666&name=Tom ,就会映射到MyAction中的@GPRequestMapping(“edit.json”)对应的edit()方法,并将id自动转换为int类型,得到如下图所示结果。

在浏览器中输入 http://localhost/first.html?teacher=Tom ,就会映射到PageAction中的@GPRequestMapping(“first.html”)对应的query()方法,得到如下图所示结果。

到这里,已经实现了Spring从IoC、ID到MVC的完整功能。虽然忽略了一些细节,但是我们已经了解到,Spring的核心设计思想其实并没有我们想象得那么神秘。我们已经巧妙地用到了工厂模式、静态代理模式、适配器模式、模板模式、策略模式、委派模式等,使得代码变得非常优雅。

 

责任编辑:姜华 来源: Tom弹架构
相关推荐

2021-12-15 09:17:12

Spring依赖注入面试题

2021-12-14 07:43:52

Spring核心原理环境准备

2022-01-05 08:53:13

Spring原理分析MVC

2023-02-23 08:15:33

Spring异常处理机制

2020-11-02 09:35:04

ReactHook

2020-12-03 08:14:45

Axios核心Promise

2021-10-31 19:39:11

注解Spring 核心类

2018-08-15 10:51:01

JavaSpring MVC框架

2011-03-18 09:37:40

spring

2020-05-21 13:25:43

Spring组件架构

2021-08-10 18:36:02

Express原理面试

2020-11-24 07:48:32

React

2020-10-20 09:12:57

axios核心原理

2022-08-27 13:49:36

ES7promiseresolve

2020-03-06 18:43:58

Spring MVC控制器Java

2022-01-26 15:20:00

配置微服务架构

2020-03-24 09:54:57

SpringMVCWebFlux

2009-08-06 18:23:51

XML映射XmlSerializ.asmx处理程序

2011-07-11 18:02:50

java

2011-07-11 18:10:28

java
点赞
收藏

51CTO技术栈公众号