栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

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

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

核心组件执行流程

功能实现

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

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

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

private Map handlerAdapters = new HashMap<>();

//视图解析
private List viewResolvers = new ArrayList<>();
初始化配置 配置文件 application
scanPackage=com.spring.test.demo

templateRoot=layouts
相关界面模板

resource 下 创建 layouts 文件夹。

  • 404.html

    
    
    
        
        页面去火星了
    
    
        404 Not Found
    Copyright@cc
  • 500.html

    
    
    
        
        服务器好像累了
    
    
        500 服务器好像有点累了,需要休息一下
    Message:¥{detail}
    StackTrace:¥{stackTrace}
  • first.html

    
    
    
        
        SpringMVC模板引擎演示
    
    

    Hello,My name is ¥{teacher}

    ¥{data}
    Token值:¥{token}
接口类

其他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 model = new HashMap();
        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的九大组件进行了初始化。

所以,我们在根据以上方法直接在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 paramIndexMapping = new HashMap();
        // 多个参数, 每个参数后面有多个值
        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 params = req.getParameterMap();
        for (Map.Entry 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 model;

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

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

    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

    public Map getModel() {
        return model;
    }

    public void setModel(Map 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 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

访问一个不存在的地址

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/337792.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号