手写springmvc框架

1.Spring

MVC的运行流程如下。

 

我们来假设一下这个场景:

DispatcherServlet是MVC场景里的老大,而且亲力亲为,什么事都要他过目审批,这天他收到了一份用户请求,叫他给出一个网页页面。

他马上给他的副手HandlerMapping,说:“小刘,你看看这个活,谁来干合适?”小刘HandlerMapping一看员工花名册有一个叫小张的Controller能够胜任,小刘就对领导说:“Controller小张能干”。

这时候,领导DispatcherServlet不能直接找到小张,因为小张只负责实现具体业务,而用户的要求太抽象,小张看不懂,需要有个人帮他理一理,第一步该做什么,第二步该做什么。这时候项目经理HandlerAdaper就上线了,领导找到项目经理说:“帮小张理一理,这个活具体该咋做”。

项目经理三下五除二给整完了,之后,领导拿着处理好的任务,将任务交给里小张,我们的小张也很争气呀,也给干完了,而且,他干的工程不仅有业务(Model)还有漂亮的组件(View),不过小张同学的审美不太好,没办法把它们组合到一块。于是,领导DispatcherServlet就吭哧吭哧跑到学美术的viewRsolver身边,让她给渲染一下。viewRsolver画技高超,寥寥几笔渲染出来了一份既有业务资料,也很好看的页面出来。

至此一个项目完成了,DispatcherServlet就拿着成果(JSP等前端页面)展示给用户看,用户心满意足,大方的付了钱,于是,大家都有钱拿...

看完了Rod Johnson的springMVC的MVC 流程,里面组件分工明确,各司其职,能够完成很多复杂的业务,但是我们刚开始上手,肯定不能上来就整这么多,因此今天我们搭一个简单版的,只有领导(DispatcherServlet)和各类业务员等。业务员,还是只负责具体业务,其他的活全让领导干。

2.我们写的框架的流程

在我们的流程中 DispatcherServlet领导 = 前端控制器 + 映射处理器 

 3.开始搭建

1. 新建maven工程

2.在pom.xml中导入依赖

 

 <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.reflections</groupId>
      <artifactId>reflections</artifactId>
      <version>0.9.11</version>
    </dependency>

3.项目架构

 

4.编写配置文件

在resource目录下编写配置文件:
applicationContext.properties,内容为:指定扫描路径package,我们在这里指定controller所在的包

package=com.yun.controller

5.更新web.xml文件

骨架用的还是2.0版本,我们在这里更新为4.0的。

并且注册我们的领导MyDispatcherServlet并为其指定配置文件所在位置contextConfigLocation,我们的领导凡事亲力亲为,在这里让他拦截所有请求。

<?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_4_0.xsd"
         version="4.0">

    <!-- 配置我们自己的前端控制器,MyDispatcherServlet就是一个servlet,拦截前端发送的请求-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>com.yun.servlet.MyDispatcherServlet</servlet-class>
<!--        配置扫描包-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>application.properties</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!-- 拦截所有请求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

6.自定义注解

注解在这里的作用就相当于给类/方法加上一个小尾巴,我们通过不同的尾巴辨识不同的Controller和Method

我们定义两个注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ fileName:MyController
 * @ description:模仿spring中的@Controller,作用于类上,标识该类是一个controller
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {

    /**
     * 没有用,但为了模仿spring中的@Controller,我们还是把它加上
     * 我们的简单版采用默认的id:首字母小写的类名
     */
    String value() default "";
}



import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ fileName:MyRequestMapping
 * @ description:模仿@RequestMapping,作用于类上和方法上,用于通过url指定对应的Controller和 Method
 * @ createTime:2021/12/17 10:31
 * @ version:1.0.0
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    /**
     * @Author: zyk
     * @Description: 域名只能有一段,只能是/controllerName/methodName
     * @Date: 2021/12/17 15:15
     */
    String value() default "";
}

上面的就是一些准备性的工作,如果说把仿写springMVC看成是组成一个团队的话,上面的工作相当于给团队找工作场地,下面就是对人物的刻画了,首先有请我们的领导MyDispatcherServlet.

编写前端控制器

编写前端控制器(一个Servlet),并重写init和service方法

MyDispatcherServlet

总览

整个过程围绕两个重写的方法而展开,其中init()是重点。

MyDispatcherServlet要做的事,用一句话来说:看前端的访问地址,然后调用匹配的处理器(Controller)的对应方法(method)

要完成这些,我们需要通过注解,为Controller和method绑定上一定的字符串,然后通过分析前端传过来的Url中的字符串,找到两者相同的,以此完成匹配。反射在此过程中发挥了巨大作用,不论是找到类头上的注解,还是找到注解中的值等诸多动作都需要反射。

具体流程

代码

创建一个dispatcherServlet继承httpservlet 并重写两个方法。(init()和service())

import com.yun.annotation.MyController;
import com.yun.annotation.MyRequestMapping;
import javafx.scene.effect.Reflection;
import org.reflections.Reflections;

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.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @ fileName:MyDispatcherServlet
 * @ description:前端控制器,相当经理,处理分发请求
 * 看前端的访问地址,然后调用匹配的处理器(Controller)的对应方法(method)
 * @ author:zyk
 * @ createTime:2021/12/17 10:43
 * @ version:1.0.0
 */
public class MyDispatcherServlet extends HttpServlet {
    /**
     * 1.配置扫描的包,放到一个.properties文件中,在初始化的时候读取
     */
    private Properties properties = new Properties();
    /**
     * 2.需要一个set,把所有能够响应controller的存起来
     */
    private Set<Class<?>> classSet = new HashSet<>();
    /**
     * 3.springMVC容器
     */
    private Map<String, Object> springMVCContext = new HashMap<>();
    /**
     * 4.映射处理器,存储所有的方法
     */
    private Map<String, Method> handlerMapping = new HashMap<>();
    /**
     * 4.后端处理器的映射关系,存储所有controller
     */
    private Map<String, Object> controllerMap = new HashMap<>();

    /**
     * @Author: zyk
     * 1.加载配置文件
     * 2.扫描controller包
     * 3.初始化controller
     * 4.初始化Handler映射器(Handler = controller + method)
     * @Date: 2021/12/17 15:20
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件,在web.xml中配置的初始化参数contextConfigLocation
        String initParameter = config.getInitParameter("contextConfigLocation");
        try {
            loadConfigFile(initParameter);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //2.扫描controller包
        scanPackage(properties.getProperty("package"));
        //3.初始化controller
        initController();
        //4. 初始化处理器映射器
        initHandlerMapping();
    }

    /**
     * @Author: zyk
     * @Description: 执行业务的方法
     * @Date: 2021/12/17 15:46
     * @Param: [req, resp]
     * @return: void
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理请求
        if (handlerMapping.isEmpty()) {
            return;
        }
        //获取url
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        String url = uri.replace(contextPath, "");
        if (!handlerMapping.containsKey(url)) {
            resp.getWriter().println("404");
        } else {
            Method method = handlerMapping.get(url);
            Object controller = controllerMap.get(url);
            try {
                method.invoke(controller);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @ author: zyk
     * @ description:加载配置文件
     * @ date: 2021/12/17 10:51
     * 工具类
     */
    private void loadConfigFile(String fileName) {
        //以流的方式获取资源
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileName);
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @ author: zyk
     * @ description:扫描所有带有MyController注解的类,并封装到set集合中
     * @ date: 2021/12/17 10:52
     * 工具类
     */
    private void scanPackage(String packageName) {
        Reflections retentions = new Reflections(packageName);
        classSet = retentions.getTypesAnnotatedWith(MyController.class);
    }

    /**
     * @ author: zyk
     * @ description:初始化controller
     * @ date: 2021/12/17 10:53
     * 工具类
     */
    private void initController() {
        if (classSet.isEmpty()) {
            return;
        }
        for (Class<?> controller : classSet) {
            try {
                springMVCContext.put(lowerFirstWord(controller.getSimpleName()), controller.newInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @ author: zyk
     * @ description:首字母转小写
     * @ date: 2021/12/17
     * 工具类
     */
    private String lowerFirstWord(String simpleName) {
        char[] array = simpleName.toCharArray();
        array[0] += 32;
        return String.valueOf(array);
    }

    /**
     * @ author: zyk
     * @ description:初始化映射处理器,(Handler = controller + method)
     * @ date: 2021/12/17 10:58
     */
    private void initHandlerMapping() {
        if (springMVCContext.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : springMVCContext.entrySet()) {
            //获取class对象
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(MyController.class)) {
                continue;
            } else {
                String baseUrl = "";
                if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                    //如果类包含注解MyRequestMapping,获取注解值
                    MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class);
                    baseUrl = annotation.value();
                }
                //获取所有的方法
                Method[] methods = aClass.getMethods();
                for (Method method : methods) {
                    // 判断方法上含有MyRequestMapping注解
                    if (method.isAnnotationPresent(MyRequestMapping.class)) {
                        MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                        String url = annotation.value();
                        url = baseUrl + url;
                        //将该方法放入方法集
                        handlerMapping.put(url, method);
                        try {
                            //放入controllerMap中
                            controllerMap.put(url, aClass.newInstance());
                        } catch (InstantiationException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

 最后,编写一个Controller进行测试


@MyRequestMapping(value = "/test")
@MyController
public class TestController {
    @MyRequestMapping(value = "/test1")
    public void test1() {
        System.out.println("test1被调用了");
    }

    @MyRequestMapping(value = "/test2")
    public void test2() {
        System.out.println("test2被调用了");
    }

    @MyRequestMapping(value = "/test3")
    public void test3() {
        System.out.println("test3被调用了");
    }
}

测试截图:

输入错误地址:

 输入正确地址:

 

以上就是全部重写SpringMvc框架。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值