本文节选自《Spring 5核心原理》

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

在上面的代码中,我们只实现了九大组件中的三大核心组件的基本功能,分别是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. @Override
  6. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  7. try {
  8. doDispatch(req, resp);
  9. }catch (Exception e){
  10. resp.getWriter().write("<font size='25' color='blue'>500 Exception</font><br/>Details: <br/>" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]","")
  11. .replaceAll("\\s","\r\n") + "<font color='green'><i>Copyright@GupaoEDU </i></font>");
  12. e.printStackTrace();
  13. }
  14. }
  15. private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
  16. //根据用户请求的URL来获得一个Handler
  17. GPHandlerMapping handler = getHandler(req);
  18. if(handler == null){
  19. processDispatchResult(req,resp,new GPModelAndView("404"));
  20. return;
  21. }
  22. GPHandlerAdapter ha = getHandlerAdapter(handler);
  23. //这一步只是调用方法,得到返回值
  24. GPModelAndView mv = ha.handle(req, resp, handler);
  25. //这一步才是真的输出
  26. processDispatchResult(req,resp, mv);
  27. }
  28. private void processDispatchResult(HttpServletRequest request,HttpServletResponse response, GPModelAndView mv) throws Exception {
  29. //调用viewResolver的resolveViewName()方法
  30. if(null == mv){ return;}
  31. if(this.viewResolvers.isEmpty()){ return;}
  32. if (this.viewResolvers != null) {
  33. for (GPViewResolver viewResolver : this.viewResolvers) {
  34. GPView view = viewResolver.resolveViewName(mv.getViewName(), null);
  35. if (view != null) {
  36. view.render(mv.getModel(),request,response);
  37. return;
  38. }
  39. }
  40. }
  41. }
  42. private GPHandlerAdapter getHandlerAdapter(GPHandlerMapping handler) {
  43. if(this.handlerAdapters.isEmpty()){return null;}
  44. GPHandlerAdapter ha = this.handlerAdapters.get(handler);
  45. if (ha.supports(handler)) {
  46. return ha;
  47. }
  48. return null;
  49. }
  50. private GPHandlerMapping getHandler(HttpServletRequest req) {
  51. if(this.handlerMappings.isEmpty()){ return null;}
  52. String url = req.getRequestURI();
  53. String contextPath = req.getContextPath();
  54. url = url.replace(contextPath,"").replaceAll("/+","/");
  55. for (GPHandlerMapping handler : this.handlerMappings) {
  56. Matcher matcher = handler.getPattern().matcher(url);
  57. if(!matcher.matches()){ continue;}
  58. return handler;
  59. }
  60. return null;
  61. }

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

1.2 GPHandlerMapping

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


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

1.3 GPHandlerAdapter

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


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

1.4 GPModelAndView

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


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

1.5 GPViewResolver

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


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

1.6 GPView

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


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

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

2 业务代码实现

2.1 IQueryService

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


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

2.2 QueryService

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


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

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. @GPService
  8. public class ModifyService implements IModifyService {
  9. /**
  10. * 增加
  11. */
  12. public String add(String name,String addr) {
  13. return "modifyService add,name=" + name + ",addr=" + addr;
  14. }
  15. /**
  16. * 修改
  17. */
  18. public String edit(Integer id,String name) {
  19. return "modifyService edit,id=" + id + ",name=" + name;
  20. }
  21. /**
  22. * 删除
  23. */
  24. public String remove(Integer id) {
  25. return "modifyService id=" + id;
  26. }
  27. }

2.5 MyAction

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

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

2.6 PageAction

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


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

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弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!

如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!

30个类手写Spring核心原理之MVC映射功能(4)的更多相关文章

  1. 30个类手写Spring核心原理之依赖注入功能(3)

    本文节选自<Spring 5核心原理> 在之前的源码分析中我们已经了解到,依赖注入(DI)的入口是getBean()方法,前面的IoC手写部分基本流程已通.先在GPApplicationC ...

  2. 30个类手写Spring核心原理之动态数据源切换(8)

    本文节选自<Spring 5核心原理> 阅读本文之前,请先阅读以下内容: 30个类手写Spring核心原理之自定义ORM(上)(6) 30个类手写Spring核心原理之自定义ORM(下)( ...

  3. 30个类手写Spring核心原理之环境准备(1)

    本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...

  4. 30个类手写Spring核心原理之AOP代码织入(5)

    本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...

  5. 30个类手写Spring核心原理之自定义ORM(上)(6)

    本文节选自<Spring 5核心原理> 1 实现思路概述 1.1 从ResultSet说起 说到ResultSet,有Java开发经验的"小伙伴"自然最熟悉不过了,不过 ...

  6. 30个类手写Spring核心原理之Ioc顶层架构设计(2)

    本文节选自<Spring 5核心原理> 1 Annotation(自定义配置)模块 Annotation的代码实现我们还是沿用Mini版本的,保持不变,复制过来便可. 1.1 @GPSer ...

  7. 手写webpack核心原理,再也不怕面试官问我webpack原理

    手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...

  8. 手写Spring MVC

    闲及无聊 又打开了CSDN开始看一看有什么先进的可以学习的相关帖子,这时看到了一位大神写的简历装X必备,手写Spring MVC. 我想这个东西还是有一点意思的 就拜读了一下大佬的博客 通读了一遍相关 ...

  9. Spring事务原理分析--手写Spring事务

    一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...

随机推荐

  1. 25.A Famous Music Composer

    描述 Mr. B is a famous music composer. One of his most famous work was his set of preludes. These 24 p ...

  2. Python基础(普通函数及参数)

    # def my_abs(x): # if not isinstance(x,(int,float)):#参数类型做检查,只允许整数和浮点数类型的参数.数据类型检查可以用内置函数isinstance( ...

  3. 【JAVA】笔记(8)--- java.lang.String 精讲

    String 特性: 1.String 表示字符串类型,属于引用数据类型,所以其储存的是地址: 2.java 中规定,双引号括起来的字符串是不可变的,也就说" name "永远也只 ...

  4. [spojSUBLEX]Lexicographical Substring Search

    建立后缀自动机,对于同一个节点,出现次数是相同的(right的大小),同时满足单调性(长度越长出现次数越少),所以只需要考虑最长的串即可. PS:似乎也并不需要求依次后缀的max,不知道为什么-- 1 ...

  5. [bzoj3351]Regions

    这道题有一种较为暴力的做法,对于每个点枚举所有与r2为该属性的询问并加以修改,最坏时间复杂度为o(nq),然而是可过的(97s) 发现只有当r2相同的询问数特别多时才会达到最坏时间复杂度,因此如果删除 ...

  6. [bzoj5294]二进制

    首先可以发现$2^k$模3意义下有循环节,也就是1,-1,1,-1--考虑对于x个1,y个0,判断是否存在3的倍数1.x为偶数时一定可以,选择等量的1和-1即可2.x为奇数,要满足$x\ge 3$且$ ...

  7. 青龙+Nvjdc短信登陆对接Xdd-plus推送+Ninja CK登陆教程(11.23更新)

    一.准备工作 1.shh工具(powshell.gitbash等等) 2.购买一台云服务器(阿里云.腾讯云都可以) 3.安装宝塔面板 宝塔Linux面板安装教程 - 2021年8月18日更新 - 7. ...

  8. JavaMail发送邮件(超详细)

    一:邮件发送的基本概念 本文我将阐述使用JavaMail方式发送和接收Email的详细说明,本博客本着以后遇到类似的邮件发送需求可以直接把代码粘过去直接使用,快捷方便省时间,对于刚接触的JavaMai ...

  9. NOI 2008 志愿者招募

    NOI 2008 志愿者招募 考虑用 $ p_i $ 表示第 $ i $ 天实际招收的人数,我们假设我们有三种志愿者,分别是 $ 1\to 2,1 \to 3 , 2\to 3 $ ,我们招手的人数分 ...

  10. MySQL:事务常用语句

    Mysql(版本是8)的事务隔离级别 默认是RR:REPEATABLE-READ:可重复读 查看 当前隔离级别 全局隔离级别  修改 -- 当前修改 -- 设置成可重复读 SET transactio ...