阅读SpringMVC源码前,不妨看下简易版本SpringMVC框架的搭建

开发环境:windows10、idea、jdk1.8、apache-tomcat-9.0.0.M3

 

SpringMVC框架是基于Servlet设计的,所以如果你知道SpringMVC,但是没听过道Servlet,那你就应该先去学习下Servlet的知识点了。以下我所描述的SpringMVC框架都是简易版本SpringMVC框架。该框架主要是以请求为驱动,其核心是DispatcherServlet类,它实际上就是一个Servlet,底层实现的也是Servlet。在SpringMVC框架中DispatcherServlet主要做两件事,

第一 :处理请求路径到各个Handler之间的映射,这里的Handler就类似我们平时使用的Controller,也就是struts里边的action,这时候需要使用一个HandlerMapping的东西,也就是一个map来保存映射关系,并且需要在初始化的时候就保存,这就使用到了Servlet生命周期中初始化阶段所调用的init()方法;

第二 :将用户请求分发到各个具体的Handler,并且将请求交给Handler中的具体方法去处理,然后就接收返回回来的ModelAndView,查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图,视图负责将结果显示到客户端。

首先我们先新建一个Java Enterprise项目,也就是一个java web项目

项目建立好之后,在web.xml中我们先做简单的配置,将客户端所有的请求交给DispatcherServlet这个类去处理。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>com.zhainan.springmvc.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

接下来就是最为重要的DispatcherServlet实现:

public class DispatcherServlet extends HttpServlet {

    private Map<String, HandlerBean> handlerBeanMap;

    @Override
    public void init() throws ServletException {
        handlerBeanMap = new HashMap<String, HandlerBean>();
        for (String name : ClassUtils.getAllClassesFromPackage("com.zhainan.springmvc.handler")) {
            try {
                Class clz = Class.forName(name);
                for (Method method : clz.getDeclaredMethods()) {
                    RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                    if (requestMapping != null) {
                        String path = requestMapping.value();
                        handlerBeanMap.put(path, new HandlerBean(name, method.getName()));
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException
    {
        String path = req.getPathInfo();
        HandlerBean handlerBean = handlerBeanMap.get(path);
        if (handlerBean == null)
            handlerBean = new HandlerBean("com.zhainan.springmvc.handler.DefaultHandler", "doService");
        try {
            Class clz = Class.forName(handlerBean.getBeanName());
            for (Method method : clz.getDeclaredMethods()) {
                if (handlerBean.getMethodName().equals(method.getName())) {
                    Parameter[] parameters = method.getParameters();
                    Object[] args = new Object[parameters.length];
                    for (int i = 0; i < parameters.length; i++) {
                        RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
                        if (requestParam != null) {
                            String parameterName = requestParam.value();
                            args[i] = req.getParameter(parameterName);
                        }
                        if (parameters[i].getType() == HttpServletResponse.class) {
                            args[i] = resp;
                        }
                    }
                    ModelAndView view = (ModelAndView) method.invoke(clz.newInstance(), args);
                    new ViewResolver().render(view, req, resp);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

可以看到简易SpringMVC框架中的DispatcherServlet直接继承自HttpServlet,重写了它的init()方法和doGet()方法,init()方法先保存请求路径到各个Handler之间的映射,当然这里边就涉及了类ClassUtils中的一个重要方法和一个类HandlerBean,以及一些关键注解

ClassUtils类:根据包名定位到文件夹,然后根据读取该包下所有class文件,最后根据包名和文件名,构造返回的完整类名.

public class ClassUtils {

    public static List<String> getAllClassesFromPackage(String packageName){

        List<String> classNames = new ArrayList<String>();

        String pkg = "com.zhainan.springmvc.handler";
        String relPath = pkg.replace('.', '/');

        URL resource = Thread.currentThread().getContextClassLoader().getResource(relPath);
        if (resource == null) {
            throw new RuntimeException("Unexpected problem: No resource for "
                    + relPath);
        }

        File f = new File(resource.getPath());

        String[] files = f.list();

        for (int i = 0; i < files.length; i++) {

            String fileName = files[i];
            String className = null;
            String fileNm = null;

            if (fileName.endsWith(".class")) {

                fileNm = fileName.substring(0, fileName.length() - 6);
                className = pkg + '.' + fileNm;
            }
            if (className != null) {
                classNames.add(className);
            }
        }

        return classNames;
    }
}

HandlerBean类:具体Handler和其中处理方法的对应关系

public class HandlerBean {

    private String beanName;

    private String methodName;

    public HandlerBean(String beanName, String methodName) {
        this.beanName = beanName;
        this.methodName = methodName;
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }
}

注解RequestMapping:和Controller里边的RequestMapping是一样的道理,将请求路径映射到某个具体方法

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {

    String value() default "";

}

注解RequestParam:和Controller里边的RequestParam一样,请求路径中的请求参数接收

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {

    String value() default "";

}

注解PathVariable:和Controller里边的PathVariable一样,请求路径中的路径变量接收

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {

    String value() default "";

}

doGet()方法直接调用类里边的doDispatch()方法用于将用户请求分发到各个具体的Handler去处理。接收返回回来的ModelAndView,查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图,视图负责将结果显示到客户端。

 

首先来看看我们的Handler实现,在Demo中我定义了两个Handler

DefaultHandler:用于处理默认请求

public class DefaultHandler {

    public void doService(HttpServletRequest request,
                          HttpServletResponse response) throws IOException {
        response.getWriter().write("this is index page !");

    }

}

DemoHandler:用于处理指定路径的请求,可以在这编写相关的API

public class DemoHandler {

    @RequestMapping(value = "/test")
    public void test(HttpServletRequest request,
                     HttpServletResponse response) throws IOException {

        response.getWriter().write("<h1>test</h1>");
    }

    @RequestMapping(value = "/demo")
    public ModelAndView demo(@RequestParam(value = "name") String name,
                         @RequestParam(value = "age") String age) {

        Map<String, String> model = new HashMap<String, String>();
        model.put("name", name);
        model.put("age", age);

        return new ModelAndView(model, "my.view");
    }

}

除此之外,还可以去编写其他的Handler,如同Controller一样去处理各类请求。

 

接下来就是关于视图的处理了,先来看下ModelAndView

ModelAndView:ModelAndView中包含了模型(Model)和视图(View),Handler处理完具体的业务逻辑后可以返回对应的模型和需要返回的视图

public class ModelAndView {
    private Map<String, String> model;
    private String viewName;

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

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

    public void setModel(Map<String, String> model) {
        this.model = model;
    }

    public String getViewName() {
        return viewName;
    }

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

然后再编写一个小型的试图解析功能

先定义一个接口View

public interface View {

    public void render(ModelAndView view,
                       HttpServletRequest request,
                       HttpServletResponse response) throws IOException;
}

然后定义一个泪ViewResolver去实现View的render()方法

public class ViewResolver implements View {

    @Override
    public void render(ModelAndView view, HttpServletRequest request, HttpServletResponse response) throws IOException {
        PrintWriter writer = response.getWriter();
        String viewName = view.getViewName();
        String content = loadTemplate(request.getServletContext().getResourceAsStream("/WEB-INF/" + viewName));
        content = parseTemplate(view.getModel(), content);
        writer.write(content);
    }

    private String loadTemplate(InputStream is) throws IOException {

        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder content = new StringBuilder();
        String line = null;
        while ((line = reader.readLine()) != null) {
            content.append(line);
        }
        return content.toString();
    }

    private String parseTemplate(Map<String, String> model, String template) {

        for (Map.Entry<String, String> entry : model.entrySet()) {
            String key = entry.getKey();
            String matchedKey = String.format("\\$\\{%s\\}", key);
            template = template.replaceAll(matchedKey, entry.getValue());
        }
        return template;

    }

}

最后就是视图文件了,demo中定义了一个简单的视图文件my.view

<html>
 <head>
  <title>test</title>
 </head>
 <body>
  <h3>hello,my name is ${name},${age} years old.</h3>
 </body>
</html>

最终还是要展示下效果

 

 

 

 附上项目基本结构图和别人家的SpringMVC实现原理图帮助理解

 

项目基本结构图:

别人家的SpringMVC实现原理图:

 

 

项目Git地址:https://gitee.com/lvchang/springmvc_demo.git

 

--END--

 

个人微信公众号“IT集装箱”目前正在吸粉阶段,欢迎搜索加关注,获取更多精彩IT技术知识内容分享!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值