深圳幻海软件技术有限公司 欢迎您!

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

2023-02-28

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

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

1 MVC顶层设计

1.1 GPDispatcherServlet

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

package com.tom.spring.formework.webmvc.servlet; 
 
import com.tom.spring.formework.annotation.GPController; 
import com.tom.spring.formework.annotation.GPRequestMapping; 
import com.tom.spring.formework.context.GPApplicationContext; 
import com.tom.spring.formework.webmvc.*; 
import lombok.extern.slf4j.Slf4j; 
 
import javax.servlet.ServletConfig; 
import javax.servlet.ServletException; 
import javax.servlet.http.HttpServlet; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import java.io.File; 
import java.io.IOException; 
import java.lang.reflect.Method; 
import java.util.*; 
import java.util.regex.Matcher; 
import java.util.regex.Pattern; 
 
//Servlet只是作为一个MVC的启动入口 
@Slf4j 
public class GPDispatcherServlet extends HttpServlet { 
 
    private  final String LOCATION = "contextConfigLocation"
 
    //读者可以思考一下这样设计的经典之处 
    //GPHandlerMapping最核心的设计,也是最经典的 
    //它直接干掉了Struts、Webwork等MVC框架 
    private List<GPHandlerMapping> handlerMappings = new ArrayList<GPHandlerMapping>(); 
 
    private Map<GPHandlerMapping,GPHandlerAdapter> handlerAdapters = new HashMap<GPHandlerMapping, GPHandlerAdapter>(); 
 
    private List<GPViewResolver> viewResolvers = new ArrayList<GPViewResolver>(); 
 
    private GPApplicationContext context; 
 

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

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

@Override 
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
        this.doPost(req,resp); 
    } 
 
    @Override 
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
        try { 
            doDispatch(req, resp); 
        }catch (Exception e){ 
            resp.getWriter().write("<font size='25' color='blue'>500 Exception</font><br/>Details: <br/>" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]",""
                    .replaceAll("\\s","\r\n") +  "<font color='green'><i>Copyright@GupaoEDU </i></font>"); 
            e.printStackTrace(); 
        } 
    } 
 
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{ 
 
        //根据用户请求的URL来获得一个Handler 
        GPHandlerMapping handler = getHandler(req); 
        if(handler == null){ 
            processDispatchResult(req,resp,new GPModelAndView("404")); 
            return
        } 
 
        GPHandlerAdapter ha = getHandlerAdapter(handler); 
 
        //这一步只是调用方法,得到返回值 
        GPModelAndView mv = ha.handle(req, resp, handler); 
 
        //这一步才是真的输出 
        processDispatchResult(req,resp, mv); 
 
    } 
 
    private void processDispatchResult(HttpServletRequest request,HttpServletResponse response, GPModelAndView mv) throws Exception { 
        //调用viewResolver的resolveViewName()方法 
        if(null == mv){ return;} 
 
        if(this.viewResolvers.isEmpty()){ return;} 
 
        if (this.viewResolvers != null) { 
            for (GPViewResolver viewResolver : this.viewResolvers) { 
                GPView view = viewResolver.resolveViewName(mv.getViewName(), null); 
                if (view != null) { 
                    view.render(mv.getModel(),request,response); 
                    return
                } 
            } 
        } 
 
    } 
 
    private GPHandlerAdapter getHandlerAdapter(GPHandlerMapping handler) { 
        if(this.handlerAdapters.isEmpty()){return  null;} 
        GPHandlerAdapter ha = this.handlerAdapters.get(handler); 
        if (ha.supports(handler)) { 
            return ha; 
        } 
        return null
    } 
 
    private GPHandlerMapping getHandler(HttpServletRequest req) { 
 
        if(this.handlerMappings.isEmpty()){ return  null;} 
 
        String url = req.getRequestURI(); 
        String contextPath = req.getContextPath(); 
        url = url.replace(contextPath,"").replaceAll("/+","/"); 
 
        for (GPHandlerMapping handler : this.handlerMappings) { 
            Matcher matcher = handler.getPattern().matcher(url); 
            if(!matcher.matches()){ continue;} 
            return handler; 
        } 
 
        return null

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.

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

1.2 GPHandlerMapping

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

package com.tom.spring.formework.webmvc; 
 
import java.lang.reflect.Method; 
import java.util.regex.Pattern; 
 
public class GPHandlerMapping { 
    private Object controller; //目标方法所在的contrller对象 
    private Method method; //URL对应的目标方法 
    private Pattern pattern;  //URL的封装 
 
    public GPHandlerMapping(Pattern pattern,Object controller, Method method) { 
        this.controller = controller; 
        this.method = method; 
        this.pattern = pattern; 
    } 
 
    public Object getController() { 
        return controller; 
    } 
 
    public void setController(Object controller) { 
        this.controller = controller; 
    } 
 
    public Method getMethod() { 
        return method; 
    } 
 
    public void setMethod(Method method) { 
        this.method = method; 
    } 
 
    public Pattern getPattern() { 
        return pattern; 
    } 
 
    public void setPattern(Pattern pattern) { 
        this.pattern = pattern; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

1.3 GPHandlerAdapter

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

package com.tom.spring.formework.webmvc; 
 
import com.tom.spring.formework.annotation.GPRequestParam; 
 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import java.lang.annotation.Annotation; 
import java.util.Arrays; 
import java.util.HashMap; 
import java.util.Map; 
 
//专人干专事 
public class GPHandlerAdapter { 
 
    public boolean supports(Object handler){ 
        return (handler instanceof GPHandlerMapping); 
    } 
 
    public GPModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception{ 
        GPHandlerMapping handlerMapping = (GPHandlerMapping)handler; 
 
        //每个方法有一个参数列表,这里保存的是形参列表 
        Map<String,Integer> paramMapping = new HashMap<String, Integer>(); 
 
        //这里只是给出命名参数 
        Annotation[][] pa = handlerMapping.getMethod().getParameterAnnotations(); 
        for (int i = 0; i < pa.length ; i ++) { 
            for (Annotation a : pa[i]) { 
                if(a instanceof GPRequestParam){ 
                    String paramName = ((GPRequestParam) a).value(); 
                    if(!"".equals(paramName.trim())){ 
                        paramMapping.put(paramName,i); 
                    } 
                } 
            } 
        } 
 
        //根据用户请求的参数信息,跟Method中的参数信息进行动态匹配 
        //resp 传进来的目的只有一个:将其赋值给方法参数,仅此而已 
 
        //只有当用户传过来的ModelAndView为空的时候,才会新建一个默认的 
 
        //1. 要准备好这个方法的形参列表 
        //方法重载时形参的决定因素:参数的个数、参数的类型、参数顺序、方法的名字 
        //只处理Request和Response 
        Class<?>[] paramTypes = handlerMapping.getMethod().getParameterTypes(); 
        for (int i = 0;i < paramTypes.length; i ++) { 
            Class<?> type = paramTypes[i]; 
            if(type == HttpServletRequest.class || 
                    type == HttpServletResponse.class){ 
                paramMapping.put(type.getName(),i); 
            } 
        } 
 
 
 
        //2. 得到自定义命名参数所在的位置 
        //用户通过URL传过来的参数列表 
        Map<String,String[]> reqParameterMap = req.getParameterMap(); 
 
        //3. 构造实参列表 
        Object [] paramValues = new Object[paramTypes.length]; 
 
        for (Map.Entry<String,String[]> param : reqParameterMap.entrySet()) { 
            String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]",""). replaceAll("\\s",""); 
 
            if(!paramMapping.containsKey(param.getKey())){continue;} 
 
            int index = paramMapping.get(param.getKey()); 
 
            //因为页面传过来的值都是String类型的,而在方法中定义的类型是千变万化的 
            //所以要针对我们传过来的参数进行类型转换 
            paramValues[index] = caseStringValue(value,paramTypes[index]); 
        } 
 
        if(paramMapping.containsKey(HttpServletRequest.class.getName())) { 
            int reqIndex = paramMapping.get(HttpServletRequest.class.getName()); 
            paramValues[reqIndex] = req; 
        } 
 
        if(paramMapping.containsKey(HttpServletResponse.class.getName())) { 
            int respIndex = paramMapping.get(HttpServletResponse.class.getName()); 
            paramValues[respIndex] = resp; 
        } 
 
        //4. 从handler中取出Controller、Method,然后利用反射机制进行调用 
 
        Object result = handlerMapping.getMethod().invoke(handlerMapping.getController(), paramValues); 
 
        if(result == null){ return  null; } 
 
        boolean isModelAndView = handlerMapping.getMethod().getReturnType() == GPModelAndView.class; 
        if(isModelAndView){ 
            return (GPModelAndView)result; 
        }else
            return null
        } 
    } 
 
    private Object caseStringValue(String value,Class<?> clazz){ 
        if(clazz == String.class){ 
            return value; 
        }else if(clazz == Integer.class){ 
            return  Integer.valueOf(value); 
        }else if(clazz == int.class){ 
            return Integer.valueOf(value).intValue(); 
        }else { 
            return null
        } 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.

1.4 GPModelAndView

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

package com.tom.spring.formework.webmvc; 
 
import java.util.Map; 
 
public class GPModelAndView { 
 
    private String viewName; //页面模板的名称 
    private Map<String,?> model; //往页面传送的参数 
 
    public GPModelAndView(String viewName) { 
        this(viewName,null); 
    } 
    public GPModelAndView(String viewName, Map<String, ?> model) { 
        this.viewName = viewName; 
        this.model = model; 
    } 
 
    public String getViewName() { 
        return viewName; 
    } 
 
    public void setViewName(String viewName) { 
        this.viewName = viewName; 
    } 
 
    public Map<String, ?> getModel() { 
        return model; 
    } 
 
    public void setModel(Map<String, ?> model) { 
        this.model = model; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

1.5 GPViewResolver

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

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

1.6 GPView

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

package com.tom.spring.formework.webmvc; 
 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import java.io.RandomAccessFile; 
import java.util.Map; 
import java.io.File; 
import java.util.regex.Matcher; 
import java.util.regex.Pattern; 
 
public class GPView { 
 
    public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=utf-8"
 
    private File viewFile; 
 
    public GPView(File viewFile){ 
        this.viewFile = viewFile; 
    } 
 
    public String getContentType(){ 
        return DEFAULT_CONTENT_TYPE; 
    } 
 
    public void render(Map<String, ?> model,HttpServletRequest request, HttpServletResponse response) throws Exception{ 
        StringBuffer sb = new StringBuffer(); 
        RandomAccessFile ra = new RandomAccessFile(this.viewFile,"r"); 
 
 
        try { 
            String line = null
            while (null != (line = ra.readLine())) { 
                line = new String(line.getBytes("ISO-8859-1"),"utf-8"); 
                Pattern pattern = Pattern.compile("¥\\{[^\\}]+\\}",Pattern.CASE_INSENSITIVE); 
                Matcher matcher = pattern.matcher(line); 
 
                while (matcher.find()) { 
 
                    String paramName = matcher.group(); 
                    paramName = paramName.replaceAll("¥\\{|\\}",""); 
                    Object paramValue = model.get(paramName); 
                    if (null == paramValue) { continue; } 
                    //要把¥{}中间的这个字符串取出来 
                    line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString())); 
                    matcher = pattern.matcher(line); 
 
                } 
 
                sb.append(line); 
            } 
        }finally { 
            ra.close(); 
        } 
        response.setCharacterEncoding("utf-8"); 
        //response.setContentType(DEFAULT_CONTENT_TYPE); 
        response.getWriter().write(sb.toString()); 
    } 
 
    //处理特殊字符 
    public static String makeStringForRegExp(String str) { 
         return str.replace("\\", "\\\\").replace("*", "\\*") 
        .replace("+""\\+").replace("|""\\|"
        .replace("{""\\{").replace("}""\\}"
        .replace("(""\\(").replace(")""\\)"
        .replace("^""\\^").replace("$""\\$"
        .replace("[""\\[").replace("]""\\]"
        .replace("?""\\?").replace(",""\\,"
        .replace(".""\\.").replace("&""\\&"); 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.

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

2 业务代码实现

2.1 IQueryService

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

package com.tom.spring.demo.service; 
 
/** 
 * 查询业务 
 * 
 */ 
public interface IQueryService  { 
 
   /** 
    * 查询 
    */ 
   public String query(String name); 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

2.2 QueryService

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

package com.tom.spring.demo.service.impl; 
 
import java.text.SimpleDateFormat; 
import java.util.Date
 
import com.tom.spring.demo.service.IQueryService; 
import com.tom.spring.formework.annotation.GPService; 
import lombok.extern.slf4j.Slf4j; 
 
/** 
 * 查询业务 
 * 
 */ 
@GPService 
@Slf4j 
public class QueryService implements IQueryService { 
 
   /** 
    * 查询 
    */ 
   public String query(String name) { 
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
      String time = sdf.format(new Date()); 
      String json = "{name:\"" + name + "\",time:\"" + time + "\"}"
      log.info("这是在业务方法中打印的:" + json); 
      return json; 
   } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

2.3 IModifyService

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

package com.tom.spring.demo.service; 
/** 
 * 增、删、改业务 
 */ 
public interface IModifyService { 
   /** 
    * 增加 
    */ 
   public String add(String name, String addr) ; 
   /** 
    * 修改 
    */ 
   public String edit(Integer id, String name); 
   /** 
    * 删除 
    */ 
   public String remove(Integer id); 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

2.4 ModifyService

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

package com.tom.spring.demo.service.impl; 
import com.tom.spring.demo.service.IModifyService; 
import com.tom.spring.formework.annotation.GPService; 
 
/** 
 * 增、删、改业务 
 */ 
@GPService 
public class ModifyService implements IModifyService { 
   /** 
    * 增加 
    */ 
   public String add(String name,String addr) { 
      return "modifyService add,name=" + name + ",addr=" + addr; 
   } 
   /** 
    * 修改 
    */ 
   public String edit(Integer id,String name) { 
      return "modifyService edit,id=" + id + ",name=" + name
   } 
   /** 
    * 删除 
    */ 
   public String remove(Integer id) { 
      return "modifyService id=" + id; 
   } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

2.5 MyAction

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

package com.tom.spring.demo.action
 
import java.io.IOException; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import com.tom.spring.demo.service.IModifyService; 
import com.tom.spring.demo.service.IQueryService; 
import com.tom.spring.formework.annotation.GPAutowired; 
import com.tom.spring.formework.annotation.GPController; 
import com.tom.spring.formework.annotation.GPRequestMapping; 
import com.tom.spring.formework.annotation.GPRequestParam; 
import com.tom.spring.formework.webmvc.GPModelAndView; 
 
/** 
 * 公布接口URL 
 */ 
@GPController 
@GPRequestMapping("/web"
public class MyAction { 
 
   @GPAutowired IQueryService queryService; 
   @GPAutowired IModifyService modifyService; 
 
   @GPRequestMapping("/query.json"
   public GPModelAndView query(HttpServletRequest request, HttpServletResponse response, 
                        @GPRequestParam("name") String name){ 
      String result = queryService.query(name); 
      return out(response,result); 
   } 
   @GPRequestMapping("/add*.json"
   public GPModelAndView add(HttpServletRequest request,HttpServletResponse response, 
            @GPRequestParam("name") String name,@GPRequestParam("addr") String addr){ 
      String result = modifyService.add(name,addr); 
      return out(response,result); 
   } 
   @GPRequestMapping("/remove.json"
   public GPModelAndView remove(HttpServletRequest request,HttpServletResponse response, 
         @GPRequestParam("id"Integer id){ 
      String result = modifyService.remove(id); 
      return out(response,result); 
   } 
   @GPRequestMapping("/edit.json"
   public GPModelAndView edit(HttpServletRequest request,HttpServletResponse response, 
         @GPRequestParam("id"Integer id, 
         @GPRequestParam("name") String name){ 
      String result = modifyService.edit(id,name); 
      return out(response,result); 
   } 
 
   private GPModelAndView out(HttpServletResponse resp,String str){ 
      try { 
         resp.getWriter().write(str); 
      } catch (IOException e) { 
         e.printStackTrace(); 
      } 
      return null
   } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.

2.6 PageAction

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

package com.tom.spring.demo.action
 
import java.util.HashMap; 
import java.util.Map; 
import com.tom.spring.demo.service.IQueryService; 
import com.tom.spring.formework.annotation.GPAutowired; 
import com.tom.spring.formework.annotation.GPController; 
import com.tom.spring.formework.annotation.GPRequestMapping; 
import com.tom.spring.formework.annotation.GPRequestParam; 
import com.tom.spring.formework.webmvc.GPModelAndView; 
 
/** 
 * 公布接口URL 
 */ 
@GPController 
@GPRequestMapping("/"
public class PageAction { 
 
   @GPAutowired IQueryService queryService; 
 
   @GPRequestMapping("/first.html"
   public GPModelAndView query(@GPRequestParam("teacher") String teacher){ 
      String result = queryService.query(teacher); 
      Map<String,Object> model = new HashMap<String,Object>(); 
      model.put("teacher", teacher); 
      model.put("data", result); 
      model.put("token""123456"); 
      return new GPModelAndView("first.html",model); 
   } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

3 定制模板页面

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

3.1 first.html

first.html定义如下:

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

3.2 404.html

404.html定义如下:

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

3.3 500.html

500.html定义如下:

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

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的核心设计思想其实并没有我们想象得那么神秘。我们已经巧妙地用到了工厂模式、静态代理模式、适配器模式、模板模式、策略模式、委派模式等,使得代码变得非常优雅。