Spring 源码分析(四)—— MVC功能实现

建议从下文开始阅读。
Spring源码 v1.0 —— 简陋版
Spring源码 v2.0 —— applicationContext
Spring源码 v3.0 —— 三级缓存及循环依赖

Spring MVC 九大组件

序号 组件名 解释
1 MultipartResolver 多文件上传的组件
2 LocaleResolver 初始化本地语言环境
3 ThemeResolver 初始化模板处理器
4 HandlerMappings handlerMapping
5 HandlerAdapters 初始化参数适配器
6 HandlerExceptionResolvers 初始化异常拦截器
7 RequestToViewNameTranslator 初始化视图预处理器
8 ViewResolvers 初始化视图转换器
9 FlashMapManager FlashMap管理器

下文只处理HandlerMappings,HandlerAdapters以及ViewResolvers三个组件。

核心组件执行流程

Spring 源码分析(四)—— MVC功能实现_第1张图片

功能实现

所有代码基于前文的代码实现。

首先在类中添加接下来需要使用到的集合。

//保存controller 中 URL和Method的对应关系
private List<DemoHandlerMapping> handlerMappings = new ArrayList<>();

private Map<DemoHandlerMapping, DemoHandlerAdapter> handlerAdapters = new HashMap<>();

//视图解析
private List<DemoViewResolver> viewResolvers = new ArrayList<>();

初始化配置

配置文件 application

scanPackage=com.spring.test.demo

templateRoot=layouts

相关界面模板

resource 下 创建 layouts 文件夹。

  • 404.html

    DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <title>页面去火星了title>
    head>
    <body>
        <font size='25' color='red'>404 Not Foundfont><br/><font color='green'><i>Copyright@cci>font>
    body>
    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/>
    body>
    html>
    
  • first.html

    DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <title>SpringMVC模板引擎演示title>
    head>
    <center>
        <h3>Hello,My name is ¥{teacher}h3>
        <div>¥{data}div>
        Token值:¥{token}
    center>
    html>
    

接口类

其他service业务逻辑直接从上一个项目复制即可。

@DemoController
@DemoRequestMapping("/")
public class PageAction {
     

    @DemoAutowired
    IQueryService queryService;

    @DemoRequestMapping("/first.html")
    public DemoModelAndView query(@DemoRequestParam("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 DemoModelAndView("first.html", model);
    }

}
@DemoController
@DemoRequestMapping("/web")
public class MyAction {
     

    @DemoAutowired
    IQueryService queryService;
    @DemoAutowired
    IModifyService modifyService;

    @DemoRequestMapping("/query.json")
    public DemoModelAndView query(HttpServletRequest request, HttpServletResponse response,
                                  @DemoRequestParam("name") String name) {
     
        String result = queryService.query(name);
        return out(response, result);
    }

    @DemoRequestMapping("/add*.json")
    public DemoModelAndView add(HttpServletRequest request, HttpServletResponse response,
                                @DemoRequestParam("name") String name, @DemoRequestParam("addr") String addr) {
     
        String result = modifyService.add(name, addr);
        return out(response, result);
    }

    @DemoRequestMapping("/remove.json")
    public DemoModelAndView remove(HttpServletRequest request, HttpServletResponse response,
                                   @DemoRequestParam("id") Integer id) {
     
        String result = modifyService.remove(id);
        return out(response, result);
    }

    @DemoRequestMapping("/edit.json")
    public DemoModelAndView edit(HttpServletRequest request, HttpServletResponse response,
                                 @DemoRequestParam("id") Integer id,
                                 @DemoRequestParam("name") String name) {
     
        String result = modifyService.edit(id, name);
        return out(response, result);
    }


    private DemoModelAndView out(HttpServletResponse resp, String str) {
     
        try {
     
            resp.getWriter().write(str);
        } catch (IOException e) {
     
            e.printStackTrace();
        }
        return null;
    }

}
public interface IModifyService {
     

   /**
    * 增加
    */
   public String add(String name, String addr);
   
   /**
    * 修改
    */
   public String edit(Integer id, String name);
   
   /**
    * 删除
    */
   public String remove(Integer id);
   
}
@DemoService
public class ModifyService implements IModifyService {
     

    @DemoAutowired
    private IQueryService queryService;

    @DemoAutowired
    private IAddService addService;

    /**
     * 增加
     */
    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;
    }

}

init方法

在之前的代码中我们在init方法初始化ApplicationContext对象来实现IOC及DI功能。将部分代码从DispatchServlet中抽离出来;现在实现MVC功能,我们需要重写相关代码。

首先根据Spring源码中的代码,

在FrameworkServlet.java类中的initWebApplicationContext方法里面,它调用了onRefresh方法,然后对MVC的九大组件进行了初始化。
Spring 源码分析(四)—— MVC功能实现_第2张图片
所以,我们在根据以上方法直接在init方法中初始化MVC的各大组件。共九大组件,在此只实现最重要的三个组件。

@Override
public void init(ServletConfig config) throws ServletException {
     

    try {
     
        applicationContext = new DemoApplicationContext(config.getInitParameter("contextConfigLocation"));
    } catch (Exception e) {
     
        e.printStackTrace();
    }

    //============MVC 9大组件功能===============
    initStrategies(applicationContext);

    System.out.println("Spring framework is init.");

}

protected void initStrategies(DemoApplicationContext context) {
     
    //handlerMapping
    initHandlerMappings(context);

    //初始化参数适配器
    initHandlerAdapters(context);

    //初始化视图转换器
    initViewResolvers(context);

}

handlerMapping

该方法和我们刚开始处理url和method映射关系的方法相差不大,差别在于这边为了优化代码和处理正则表达式,修改了部分代码。

private void initHandlerMappings(DemoApplicationContext context) {
     

    // 判断实例数量
    if (applicationContext.getBeanDefinitionCount() == 0) {
     
        return;
    }

    // 遍历IOC容器中的实例名
    for (String beanName : this.applicationContext.getBeanDefinitionNames()) {
     
        // 根据实例名从IOC容器中获取实例
        Object instance = applicationContext.getBean(beanName);
        Class<?> clazz = instance.getClass();

        if (!clazz.isAnnotationPresent(DemoController.class)) {
     
            continue;
        }

        //处理controller类添加地址注解
        String baseUrl = "";
        if (clazz.isAnnotationPresent(DemoRequestMapping.class)) {
     
            DemoRequestMapping requestMapping = clazz.getAnnotation(DemoRequestMapping.class);
            baseUrl = requestMapping.value();
        }

        //只迭代public方法
        for (Method method : clazz.getMethods()) {
     
            if (!method.isAnnotationPresent(DemoRequestMapping.class)) {
     
                continue;
            }

            DemoRequestMapping requestMapping = method.getAnnotation(DemoRequestMapping.class);
            // /demo/query 使用正则表达处理 多个斜杠问题
            String regex = ("/" + baseUrl + "/" + requestMapping.value())
                    .replaceAll("\\*", ".*")
                    .replaceAll("/+", "/");
            Pattern pattern = Pattern.compile(regex);

            handlerMappings.add(new DemoHandlerMapping(pattern, instance, method));
            System.out.println("Mapped : " + pattern + "--->" + method);

        }
    }
}

初始化参数适配器

private void initHandlerAdapters(DemoApplicationContext context) {
     
    for (DemoHandlerMapping handlerMapping : handlerMappings) {
     
        this.handlerAdapters.put(handlerMapping, new DemoHandlerAdapter());
    }
}

处理适配器类

它的作用用一句话概括就是调用具体的方法对用户发来的请求来进行处理;就是我们原始代码中根据url调用相关的方法。

public class DemoHandlerAdapter {
     
    public DemoModelAndView handle(HttpServletRequest req, HttpServletResponse resp, DemoHandlerMapping handlerMapping) throws InvocationTargetException, IllegalAccessException {
     

        Method method = handlerMapping.getMethod();
        // 1.先把形参的位置和参数名字建立映射关系,缓存下来
        Map<String, Integer> paramIndexMapping = new HashMap<String, Integer>();
        // 多个参数, 每个参数后面有多个值
        Annotation[][] pa = method.getParameterAnnotations();
        for (int i = 0; i < pa.length; i++) {
     
            for (Annotation annotation : pa[i]) {
     
                if (annotation instanceof DemoRequestParam) {
     
                    String paramName = ((DemoRequestParam) annotation).value();
                    if (!"".equals(paramName.trim())) {
     
                        paramIndexMapping.put(paramName, i);
                    }
                }
            }
        }

        Class<?>[] paramTypes = method.getParameterTypes();
        for (int i = 0; i < paramTypes.length; i++) {
     
            Class<?> type = paramTypes[i];
            if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
     
                paramIndexMapping.put(type.getName(), i);
            }
        }

        // 2.根据参数位置匹配参数名字,从url中取到参数名字对应的值
        Object[] paramValues = new Object[paramTypes.length];

        //http://localhost/demo/query?name=cc
        Map<String, String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> param : params.entrySet()) {
     
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", "");

            if (!paramIndexMapping.containsKey(param.getKey())) {
     
                continue;
            }

            int index = paramIndexMapping.get(param.getKey());

            //设计到类型强制转换
            paramValues[index] = castStringValue(value, paramTypes[index]);
        }

        if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
     
            int index = paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = req;
        }

        if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
     
            int index = paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }

        Object result = method.invoke(handlerMapping.getController(), paramValues);
        if (result == null || result instanceof Void) {
     
            return null;
        }
        // 如果有返回结果,则将其放入ModelAndView中
        boolean isModelAndView = handlerMapping.getMethod().getReturnType() == DemoModelAndView.class;
        if (isModelAndView) {
     
            return (DemoModelAndView) result;
        }
        return null;
    }

    //强制转换数据类型
    private Object castStringValue(String value, Class<?> paramType) {
     
        if (String.class == paramType) {
     
            return value;
        }

        if (Integer.class == paramType) {
     
            return Integer.valueOf(value);
        } else if (Double.class == paramType) {
     
            return Double.valueOf(value);
        } else {
     
            if (value != null) {
     
                return value;
            }
            return null;
        }
    }
}
public class DemoModelAndView {
     

    private String viewName;

    private Map<String, ?> model;

    public DemoModelAndView(String viewName) {
     
        this.viewName = viewName;
    }

    public DemoModelAndView(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;
    }
}

初始化视图转换器

private void initViewResolvers(DemoApplicationContext context) {
     
    //获取模板文件
    String templateRoot = context.getConfig().getProperty("templateRoot");
    String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();

    //由于我这里只有一套模板文件,所以直接传的文件路径
    File templateRootDir = new File(templateRootPath);
    for (File file : templateRootDir.listFiles()) {
     
        this.viewResolvers.add(new DemoViewResolver(templateRoot));
    }
}

需要在ApplicationContext中添加getConfig方法

public Properties getConfig() {
     
    return this.reader.getConfig();
}
DemoBeanDefinitionReader.class

public Properties getConfig() {
     
    return this.contextConfig;
}

视图解析器类

把逻辑视图名称解析为对象的View对象

public class DemoViewResolver {
     

    // 文件名后缀 .ftl .jsp .vm
    private final String DEFAULT_TEMPLATE_SUFFIX = ".html";

    private File templateRootDir;

    // 获取文件路径
    public DemoViewResolver(String templateRoot) {
     
        String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
        templateRootDir = new File(templateRootPath);
    }

    // 返回视图对象
    public DemoView resolveViewName(String 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 DemoView(templateFile);
    }
}

视图对象

public class DemoView {
     

    private File viewFile;

    public DemoView(File templateFile) {
     
        this.viewFile = templateFile;
    }

    // 根据模板渲染界面
    public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) throws Exception {
     

        //读取模板文件
        StringBuffer sb = new StringBuffer();
        //既可以读取文件内容,也可以向文件输出数据,还可以设置相应权限
        RandomAccessFile ra = new RandomAccessFile(this.viewFile, "r");

        //逐行读取数据
        String line = null;
        while (null != (line = ra.readLine())) {
     
            line = new String(line.getBytes("iso-8859-1"), "utf-8");

            //el正则表达式
            Pattern pattern = Pattern.compile("¥\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(line);
            //如果有符合的数据
            while (matcher.find()) {
     
                //¥{name}
                String paramName = matcher.group();

                //去除 无效的符号 变为 name
                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);
        }

        //输出到界面
        resp.setCharacterEncoding("UTF-8");
        resp.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("&", "\\&");
    }
}

doDispatcher调度方法

我们可以根据MVC的核心流程先搭建好初步的框架。

  • 根据URL拿到对应的handle
  • 根据handleMapping拿到handlerAdapter
  • 根据HandlerAdapter拿到对应的ModelAndView
  • 根据ViewResolver找到对应的View对象
    通过View对象渲染页面并返回

根据以上步骤我们重新编写DemoDispatchServlet中的doDispatcher调度方法。

private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    //1.根据URL拿到对应的handle
    DemoHandlerMapping handlerMapping = getHandle(req);

    //404异常
    if (handlerMapping == null) {
        processDispatchResult(req, resp, new DemoModelAndView("404"));
        return;
    }

    //2.根据handleMapping拿到handlerAdapter
    DemoHandlerAdapter ha = getHandleAdapter(handlerMapping);

    //3.根据HandlerAdapter拿到对应的ModelAndView
    DemoModelAndView mv = ha.handle(req, resp, handlerMapping);

    //4.根据ViewResolver找到对应的View对象
    //通过View对象渲染页面并返回
    processDispatchResult(req, resp, mv);
}

获取HandleMapping

private DemoHandlerMapping getHandle(HttpServletRequest req) {
     
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        // 将url整理成我们需要的requestMapping地址
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

        //路径正则表达式
        for (DemoHandlerMapping handlerMapping : this.handlerMappings) {
     
            // 判断该路径是否在程序中存在
            Matcher matcher = handlerMapping.getPattern().matcher(url);
            if (!matcher.matches()) {
     
                continue;
            }
            return handlerMapping;
        }
        return null;
}

DemoHandlerMapping类用来存储方法及对应的路径正则表达式。

public class DemoHandlerMapping {
     

    private Object controller;

    protected Method method;

    // 路径正则匹配
    protected Pattern pattern;

    public DemoHandlerMapping(Pattern pattern, Object controller, Method method) {
     
        this.method = method;
        this.pattern = pattern;
        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;
    }

    public Object getController() {
     
        return controller;
    }

    public void setController(Object controller) {
     
        this.controller = controller;
    }
}

获取handlerAdapter

这个方法相对简单,直接根据handleMapping从handlerAdapters中获取信息并返回。

private DemoHandlerAdapter getHandleAdapter(DemoHandlerMapping handlerMapping) {
     
    if (this.handlerAdapters.isEmpty()) {
     
        return null;
    }
    DemoHandlerAdapter ha = this.handlerAdapters.get(handlerMapping);
    return ha;
}

获取ModelAndView

对应HandlerAdapter中的handle方法。

DemoModelAndView mv = ha.handle(req, resp, handlerMapping);

渲染界面

private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, DemoModelAndView mv) {
     
        if (mv == null) {
     
            return;
        }
        if (this.viewResolvers.isEmpty()) {
     
            return;
        }

        //视图解析 我这里只有一套模板,所以循环没啥意义
        for (DemoViewResolver viewResolver : viewResolvers) {
     
            DemoView view = viewResolver.resolveViewName(mv.getViewName());
            try {
     
                view.render(mv.getModel(), req, resp);
            } catch (Exception e) {
     
                e.printStackTrace();
            }
            return;
        }
}

测试

访问http://localhost:8081/first.html?teacher=abc
Spring 源码分析(四)—— MVC功能实现_第3张图片

访问一个不存在的地址
Spring 源码分析(四)—— MVC功能实现_第4张图片

你可能感兴趣的:(源码分析,spring,mvc,java)