第一篇 - 手写SpringMvc框架

在这里插入图片描述
Github源码下载地址:https://github.com/chenxingxing6/springmvc
CSDN源码下载地址:https://download.csdn.net/download/m0_37499059/11783232

在这里插入图片描述


一、前言

SpringMVC是Spring框架的一个模块,是基于mvc的webframework模块。mvc是一种设计模式,即model-view-controller,mvc在b/s系统下的应用如下图所示。
在这里插入图片描述

SpringMvc原理图:

在这里插入图片描述


二、手写SpringMvc

代码下载Github:https://github.com/chenxingxing6/springmvc

我们所有的注解都自己定义,并对注解进行解析处理。通过写这个SpringMvc框架,我们可以大致掌握SpringMvc的实现思路,用户请求怎么进来的,怎么通过Mapping映射到具体需要执行的方法上,并如何对结果进行处理(Json数据格式,视图)。

需要依赖的包:

 <dependency>
     <groupId>javax.servlet</groupId>
     <artifactId>javax.servlet-api</artifactId>
     <version>3.1.0</version>
     <optional>true</optional>
     <scope>provided</scope>  #应用服务器,比如tomcat都有这个jar包
</dependency>

在这里插入图片描述


GitHub里面对进行了更新,完善了更多的功能,具体看Github.
在这里插入图片描述

2.1项目结构

在这里插入图片描述


2.2登陆测试Demo

访问地址:http://localhost:8080/test/view?path=login

在这里插入图片描述
在这里插入图片描述


@RequestMapping("/login")
    public MyModeAndView login(@RequestParam("name") String name, @RequestParam("pwd") String pwd){
        MyModeAndView modeAndView = new MyModeAndView();
        modeAndView.setViewName("index");
        modeAndView.addObject("name", name);
        return modeAndView;
    }

<html>
<head>
    <title>登陆</title>
</head>
<body>
<div>
    <h1>欢迎登陆</h1>
    <form action="/test/login" method="get">
   <div>
       <label>用户名:</label>
       <input name="name" type="text"/>
   </div>
    <div>
        <label>用户名:</label>
        <input name="pwd" type="password"/>
    </div>
    <div>
        <label></label>
        <input type="submit" value="提交"/>
    </div>
    </form>
</div>
</body>
</html>


<html>
<head>
    <title>Title</title>
</head>
<body>
<div>
    <h1>手写SpringMvc</h1>
    <h3 style="color: blue;">欢迎 ${name} 你使用本系统....</h3>
</div>
</body>
</html>

2.3核心代码
package org.springframework.servlet;

import com.alibaba.fastjson.JSON;
import org.springframework.annotation.*;
import org.springframework.core.*;

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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * @Author: cxx
 * @Date: 2019/8/27 22:45
 */
public class MyDispatcherServlet extends HttpServlet{
    // 存放配置信息
    Properties props = null;

    // 所有的类名
    private List<String> classNames = null;

    // 实例化对象
    Map<String, Object> ioc = null;

    // Handler
    List<Handler> handlers;

    // 默认适配器
    IHandlerAdapter defaultHandlerAdapter = null;

    public MyDispatcherServlet(){
        props = new Properties();
        classNames = new ArrayList<>();
        ioc = new ConcurrentHashMap<>();
        handlers = new ArrayList<>();
        defaultHandlerAdapter = new DefaultHandlerAdapter();
    }

    // 初始化
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("------ My mvc is init start...... ------");
        // 1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        System.out.println("------ 1.加载配置文件成功-doLoadConfig() ------");

        // 2.根据配置文件扫描所有相关类
        doScanner(props.getProperty("scanPackage"));
        System.out.println("------ 2.扫描所有相关类-ddoScanner() ------");

        // 3.初始化所有相关类的实例,并将放入IOC容器中
        doInstance();
        System.out.println("------ 3.实例化成功-doInstance() ------");

        // 4.实现DI
        doAutowried();
        System.out.println("------ 4.依赖注入成功-doAutowried() ------");

        // 5.初始化HandlerMapping
        initHandlerMapping();
        System.out.println("------ 5.HandlerMapping初始化成功-initHandlerMapping() ------");

        System.out.println("------ My mvc is init end...... ------");
    }

    public void doLoadConfig(String location){
        String configName = location.split(":")[1];
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(configName);
        try {
            props.load(is);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (is != null){
                try {
                    is.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

    public void doScanner(String packageName){
        // 进行递归扫描
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replace(".", "/"));
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()) {
            if (file.isDirectory()){
                doScanner(packageName + "." + file.getName());
            }else {
                String className = packageName + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    /**
     * IOC容器规则 key-value
     * 1.key默认用类名小写字段,否则优先使用用户自定义名字
     * 2.如果是接口,用接口的类型作为key
     */
    public void doInstance(){
        if (classNames.isEmpty()){
            return;
        }
        // 利用反射,将扫描的className进行初始化
        try {
            for (String className : classNames) {
                Class clazz = Class.forName(className);
                // 进行Bean实例化,初始化IOC
                if (clazz.isAnnotationPresent(Controller.class)){
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, clazz.newInstance());
                }else if (clazz.isAnnotationPresent(Service.class)){
                    Service service = (Service) clazz.getAnnotation(Service.class);
                    String beanName = service.value();
                    if ("".equals(beanName.trim())){
                        beanName = lowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);

                    // 接口也需要注入,接口类型作为key
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces) {
                        ioc.put(i.getName(), instance);
                    }
                }else {
                    continue;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void doAutowried(){
        if (ioc.isEmpty()){
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 获取到所有字段,不管什么类型,都强制注入
            Field[] field = entry.getValue().getClass().getDeclaredFields();
            for (Field f : field) {
                if (f.isAnnotationPresent(Autowired.class)){
                    Autowired autowired = f.getAnnotation(Autowired.class);
                    String beanName = autowired.value().trim();
                    if ("".equals(beanName)){
                        // com.demo.service.ITestService
                        beanName = f.getType().getName();
                    }
                    // 不管愿不愿意,都需要强吻
                    f.setAccessible(true);
                    try {
                        // 例如:TestController -> TestService
                        f.set(entry.getValue(), ioc.get(beanName));
                    }catch (Exception e) {
                        e.printStackTrace();
                        continue;
                    }
                }
            }
        }
    }


    public void initHandlerMapping(){
        if (ioc.isEmpty()){
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(Controller.class)){
                continue;
            }
            String baseUrl = "";
            if (clazz.isAnnotationPresent(RequestMapping.class)){
                RequestMapping requestMapping = (RequestMapping)clazz.getAnnotation(RequestMapping.class);
                baseUrl = requestMapping.value();
            }

            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(RequestMapping.class)){
                    continue;
                }
                RequestMapping requestMapping = (RequestMapping)method.getAnnotation(RequestMapping.class);
                String regex = ("/" + baseUrl + requestMapping.value()).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                handlers.add(new Handler(pattern, entry.getValue(), method));
                System.out.println("------   Mapping: " + regex + ", method:" + method);
            }
        }
    }

    // 6.运行阶段,等待请求
    @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);
            String url = req.getRequestURI();
            String contextpath= req.getContextPath();
            url = url.replace(contextpath, "").replaceAll("/+", "/");
            System.out.println("进行请求....url:" + url);
        }catch (FileNotFoundException e){
            resp.getWriter().write("404 Not Found");
            return;
        }catch (Exception e){
            resp.getWriter().write("500 error \r\n\n" + Arrays.toString(e.getStackTrace()));
            return;
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        boolean jsonResult = false;
        // 获取适配器
        IHandlerAdapter handlerAdapter = defaultHandlerAdapter;
        Handler handler = handlerAdapter.getHandler(req, handlers);
        if (handler == null){
            throw new FileNotFoundException();
        }
        Object[] paramValues = handlerAdapter.hand(req, resp, handlers);
        Method method = handler.method;
        Object controller = handler.controller;
        String beanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());

        //如果controller或这个方法有UVResponseBody修饰,返回json
        if (controller.getClass().isAnnotationPresent(ResponseBody.class) || method.isAnnotationPresent(ResponseBody.class)){
            jsonResult = true;
        }
        Object object = method.invoke(ioc.get(beanName), paramValues);
        if (jsonResult && object !=null){
            resp.getWriter().write(JSON.toJSONString(object));
        }else {
            // 返回视图
            doResolveView(object, req, resp);
        }
    }

    public void doResolveView(Object object, HttpServletRequest req, HttpServletResponse resp) throws Exception{
        // 视图前缀
        String prefix = props.getProperty("view.prefix");
        // 视图后缀
        String suffix = props.getProperty("view.suffix");

        MyModeAndView modeAndView = null;
        if (object instanceof MyModeAndView){
            modeAndView = (MyModeAndView) object;
        }else {
            modeAndView = new MyModeAndView(object.toString());
        }
        DefaultViewResolver viewResolver = new DefaultViewResolver(prefix, suffix);
        viewResolver.resolve(modeAndView, req, resp);
    }

    /**
     * 首字母小写
     * @param old
     * @return
     */
    private static String lowerFirstCase(String old){
        char [] chars = old.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}

三、总结

服务启动会去读取web.xml配置文件,然后执行MyDispatcherServlet的init()方法,初始化一些配置,IOC,依赖注入,RequestMapping等,然后对发送进来的请求进行解析,通过反射方式调用到具体Controller的某个方法,然后对执行的结果进行处理。

 <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <display-name>DispatcherServlet</display-name>
        <servlet-class>org.springframework.servlet.MyDispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:application.properties</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

添加过滤器对编码进行设置:
<!-- 过滤器 -->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

package org.springframework.web.filter;

import javax.servlet.*;
import java.io.IOException;

/**
 * @Author: cxx
 * @Date: 2019/9/15 23:56
 */
public class CharacterEncodingFilter implements Filter{
    // 编码格式
    private String encoding;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("------ filter init .......");
        encoding = filterConfig.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("------ 过滤器处理编码问题......" + encoding);
        servletRequest.setCharacterEncoding(encoding);
        servletResponse.setCharacterEncoding(encoding);
        servletResponse.setContentType("text/html");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值