Spring框架进阶(五)Spring V2.0 MVC

20 篇文章 1 订阅
11 篇文章 0 订阅

目录

1、MVC九大组件

2、实现思路

3、代码实现


MVC是负责渲染页面和匹配URL与Controller函数的组件

1、MVC九大组件

MVC共有九大组件

  1. MulitipartResolver:多文件上传的组件
  2. LocaleResolver:本地语言环境
  3. TemeResolver:主题模板处理器
  4. HandlerMapping:保存URL映射关系
  5. HandlerAdapter:动态参数适配器
  6. HandlerExceptionResolver:异常拦截
  7. RequestToViewNameTranslator:视图提取器
  8. ViewResolvers:视图转换器,模板引擎
  9. FlashMapManager:参数缓存器

2、实现思路

MVC有九个组件,但并非所有的组件都非常重要,在这里只用三个组件完成基础的匹配渲染。

需要了解的,就是HandlerMapping,HandlerAdapter,ViewResolvers三个组件,实现步骤可分为四步

  1. 根据URL拿到对应的HandlerMapping
  2. 根据HandlerMapping拿到HandlerAdapter
  3. 根据HandlerAdapter拿到对应的ModelAndView
  4. 根据ViewResolver找到对应View对象,通过View对象渲染页面

3、代码实现

优化HandlerMapping的结构,Map的底层就是数组,依旧要遍历,所以将HandlerMapping抽取出来,保存所在的类,方法,以及对应URL的正则规则

public class MyHandlerMapping {

    private Object controller;
    private Method method;
    private Pattern pattern;

    public MyHandlerMapping(Method method, Object controller, Pattern pattern) {
        this.method = method;
        this.controller = controller;
        this.pattern = pattern;
    }

    public Method getMethod() {
        return method;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public Object getController() {
        return controller;
    }
}

 抽取HandlerAdapter

public class MyHandlerAdapter {
    
}

 抽取ViewResolver

public class MyViewResolver {

}

 设置容器

private List<MyHandlerMapping> handlerMappings = new ArrayList<MyHandlerMapping>();
private Map<MyHandlerMapping, MyHandlerAdapter> handlerAdpaters = new HashMap<MyHandlerMapping, MyHandlerAdapter>();
private List<MyViewResolver> viewResolvers = new ArrayList<MyViewResolver>();

 将Spring源码的初始化策略模仿写入init方法中

@Override
public void init(ServletConfig config) throws ServletException {
    myApplicationContext = new MyApplicationContext(config.getInitParameter("contextConfigLocation"));
    initStrategies(myApplicationContext);
}

 MVC初始化策略,初始化映射,初始化参数适配,初始化视图转换

//MVC初始化
private void initStrategies(MyApplicationContext context) {
    //初始化方法映射
    initHandlerMappings(context);
    //初始化参数适配器
    initHandlerAdapters(context);
    //初始化视图转换器
    initViewResolvers(context);
}

HandlerMapping的初始化方式与原本的初始化方式没有什么区别,正常写入即可,但是在放入MyHandlerMapping时,需要注意,将所属的Controller,Method,URL的Pattern

private void initHandlerMappings(MyApplicationContext context) {
    if (myApplicationContext.getBeanDefinitionCount() == 0) {
        return;
    }
    try {
        for (String beanName : myApplicationContext.getBeanDefinitionNames()) {
            Object instance = myApplicationContext.getBean(beanName);
            Class aClass = instance.getClass();
            if (!aClass.isAnnotationPresent(MyController.class)) {
                continue;
            }
            String baseUrl = "";
            if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping myRequestMapping = (MyRequestMapping) aClass.getAnnotation(MyRequestMapping.class);
                baseUrl = "/" + myRequestMapping.value();
            }
            for (Method method : aClass.getMethods()) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                MyRequestMapping myRequestMapping = (MyRequestMapping) method.getAnnotation(MyRequestMapping.class);
                String url = (baseUrl + "/" + myRequestMapping.value())
                        .replaceAll("\\*", ".*")
                        .replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(url);
                handlerMappings.add(new MyHandlerMapping(method, instance, pattern));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 以handlerMapping作为Key,以HandlerAdapter作为Value,完成初始化操作

private void initHandlerAdapters(MyApplicationContext context) {
    for (MyHandlerMapping handlerMapping : handlerMappings) {
        handlerAdapters.put(handlerMapping, new MyHandlerAdapter());
    }
}

初始化视图解析器,采用类似BeanDefinitionReader的方式对

private void initViewResolvers(MyApplicationContext context) {
    String templateRoot = context.getConfig().getProperty("templateRoot");
    URL templateRootPath = this.getClass().getClassLoader().getResource(templateRoot);
    File file = new File(templateRootPath.getFile());
    this.viewResolvers.add(new MyViewResolver(file));
}

MVC初始化应该放在ApplicationContext后面

@Override
public void init(ServletConfig config) throws ServletException {
    myApplicationContext = new MyApplicationContext(config.getInitParameter("contextConfigLocation"));
    initStrategies(myApplicationContext);
}

 原来的doDispatcher方法就可以改为:

private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws InvocationTargetException, IllegalAccessException, IOException {
    //1、根据URL拿到对应的Handler
    MyHandlerMapping handlerMapping = getHandlerMapping(req);
    if (null == handlerMapping) {
        processDispatchResult(req, resp, new MyModelAndView("404"));
        return;
    }
    //2、根据HandlerMapping拿到HandlerAdapter
    MyHandlerAdapter handlerAdapter = getHandlerAapter(handlerMapping);
    //3、根据HandlerAdapter拿到对应的ModelAndView
    MyModelAndView modelAndView = handlerAdapter.handle(req, resp, handlerMapping);
    //4、根据ViewResolver找到对应View对象,通过View对象渲染页面
    processDispatchResult(req, resp, modelAndView);
}

实现起来也是非常的简单

首先第一个方法getHandlerMapping可以从request中获取到URL,然后轮询HandlerMapping的容器,获取匹配,得到对应的HandlerMapping。

private MyHandlerMapping getHandlerMapping(HttpServletRequest req) {
    String uri = req.getRequestURI();
    String contextPath = req.getContextPath();
    uri = uri.replaceAll(contextPath, "").replaceAll("/+", "/");
    for (MyHandlerMapping handlerMapping : handlerMappings) {
        Matcher matcher = handlerMapping.getPattern().matcher(uri);
        if (!matcher.matches()) {
            continue;
        }
        return handlerMapping;
    }
    return null;
}

获取到对应的参数匹配,可以从HandlerMapping和HandlerAdapter的匹配获取到

private MyHandlerAdapter getHandlerAapter(MyHandlerMapping handlerMapping) {
    if (this.handlerAdpaters.isEmpty()) {
        return null;
    }
    MyHandlerAdapter handlerAdapter = this.handlerAdpaters.get(handlerMapping);
    return handlerAdapter;
}

 从参数匹配器中获取ModelAndView

MyModelAndView modelAndView = handlerAdapter.handle(req, resp, handlerMapping);

 先创建ModelAndView,保存视图名称和映射

public class MyModelAndView {

    private String ViewName;
    private Map<String,?> model;

    public MyModelAndView(String viewName) {
        ViewName = viewName;
    }

    public MyModelAndView(String viewName, Map<String, ?> model) {
        ViewName = viewName;
        this.model = model;
    }

    public String getViewName() {
        return ViewName;
    }

    public Map<String, ?> getModel() {
        return model;
    }
}

将URL映射关系设置出来

public class MyHandlerAdapter {
    public MyModelAndView handle(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping handlerMapping) throws InvocationTargetException, IllegalAccessException {

        Method method = handlerMapping.getMethod();
        Map<String, Integer> paramMapping = new HashMap<String, Integer>();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                if (annotation instanceof MyRequestParam) {
                    String value = ((MyRequestParam) annotation).value();
                    if (!"".equals(value)) {
                        paramMapping.put(value, i);
                    }
                }
            }
        }
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            if (HttpServletRequest.class == parameterType || HttpServletResponse.class == parameterType) {
                paramMapping.put(parameterType.getName(), i);
            }
        }
        Object[] paramValues = new Object[paramMapping.size()];
        Map<String, String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> param : params.entrySet()) {
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", "");
            if (!paramMapping.containsKey(param.getKey())) {
                continue;
            }
            paramValues[paramMapping.get(param.getKey())] = value;
        }

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

        if (paramMapping.containsKey(HttpServletResponse.class.getName())) {
            int index = paramMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }
        Object result = method.invoke(handlerMapping.getController(), paramValues);
        if (null == result || result instanceof Void) {
            return null;
        }
        if (result.getClass() == MyModelAndView.class) {
            return (MyModelAndView) result;
        }
        return null;
    }
}
根据ViewResolver找到对应View对象,通过View对象渲染页面
private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, MyModelAndView modelAndView) throws IOException {
    if (modelAndView == null) {
        return;
    }
    if (viewResolvers.isEmpty()) {
        return;
    }
    for (MyViewResolver viewResolver : viewResolvers) {
        MyView view = viewResolver.resolverViewName(modelAndView.getViewName());
        view.render(modelAndView.getModel(),req,resp);
    }
}

锁定要渲染的页面

public class MyViewResolver {

    private final String DEFAULT_TEMPLATE_SUFFIX = ".html";
    private File templateRootDir;

    public MyViewResolver(File file) {
        templateRootDir = file;
    }

    public MyView resolverViewName(String viewName) {
        viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);
        for (File file : templateRootDir.listFiles()) {
            if (file.getName() == viewName || file.getName().equals(viewName)) {
                return new MyView(file);
            }
        }
        return null;
    }
}

渲染页面的法则,读取对应读数

public class MyView {

    private File viewFile;

    public MyView(File file) {
        viewFile = file;
    }

    public void render(Map<String, ?> modelAndView, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        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");

            Pattern pattern = Pattern.compile("#\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(line);
            while (matcher.find()) {
                String paramName = matcher.group();

                paramName = paramName.replaceAll("#\\{|\\}", "");
                Object paramValue = modelAndView.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("&", "\\&");
    }
}

 这样组件就完成了

可以通过http://localhost:8080/user/teacher.html?teacher=x访问

源码地址:springwrite: 手写框架

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值