今天我们就来对它动刀,看看它的内部是怎么实现的,我们能不能仿写一份呢?首先我们通过一张时序图来看一下springMVC的运行流程。
springMVC运行流程从上面的时序图,我们可看到,一个叫dispatcherServlet的家伙十分繁忙,几乎每一步都有它的参与,他怎么这么忙啊,这就和它的名字有关dispatcher /dɪs’pætʃə/ n. 发报机,调度员它就相当于在M-V-C三者之间的邮差,或者说是领导,负责调用各个组件。
我们来假设一下这个场景:DispatcherServlet是MVC场景里的老大,而且亲力亲为,什么事都要他过目审批,这天他收到了一份用户请求,叫他给出一个网页页面。
他马上给他的副手HandlerMapping,说:“小刘,你看看这个活,谁来干合适?”小刘HandlerMapping一看员工花名册有一个叫小张的Controller能够胜任,小刘就对领导说:“Controller小张能干”。
这时候,领导DispatcherServlet不能直接找到小张,因为小张只负责实现具体业务,而用户的要求太抽象,小张看不懂,需要有个人帮他理一理,第一步该做什么,第二步该做什么。
这时候项目经理HandlerAdaper就上线了,领导找到项目经理说:“帮小张理一理,这个活具体该咋做”。
项目经理三下五除二给整完了,之后,领导拿着处理好的任务,将任务交给里小张,我们的小张也很争气呀,也给干完了,而且,他干的工程不仅有业务(Model)还有漂亮的组件(View),不过小张同学的审美不太好,没办法把它们组合到一块。
于是,领导DispatcherServlet就吭哧吭哧跑到学美术的viewRsolver身边,让她给渲染一下。
viewRsolver画技高超,寥寥几笔渲染出来了一份既有业务资料,也很好看的页面出来。
至此一个项目完成了,DispatcherServlet就拿着成果(JSP等前端页面)展示给用户看,用户心满意足,大方的付了钱,于是,大家都有钱拿…看完了Rod Johnson的springMVC的MVC 流程,里面组件分工明确,各司其职,能够完成很多复杂的业务,但是我们刚开始上手,肯定不能上来就整这么多,因此今天我们搭一个简单版的,只有领导(DispatcherServlet)和各类业务员等。
业务员,还是只负责具体业务,其他的活全让领导干。
我们的流程:在我们的流程中 DispatcherServlet领导 = 前端控制器 + 映射处理器好了明确了我们要搭的任务,现在建哥来手把手教学,开搞!详细步骤1.新建webApp骨架的maven工程2.在pox.xml中引入依赖<!– 引入servlet jar –><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!– 引入反射jar包–><dependency><groupId>org.reflections</groupId><artifactId>reflections</artifactId><version>0.9.11</version></dependency>3.新建包如图所示4.编写配置文件在resource目录下编写配置文件:applicationContext.properties,内容为:指定扫描路径package,我们在这里指定controller所在的包package=com.cloudwise.controller5.更新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"><display-name>Archetype Created Web Application</display-name><!-- 配置我们自己的前端控制器,MyDispatcherServlet就是一个servlet,拦截前端发送的请求--><servlet><servlet-name>xxx</servlet-name><servlet-class>com.cloudwise.servlet.MyDispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>applicationContext.properties</param-value></init-param></servlet><servlet-mapping><servlet-name>xxx</servlet-name><!-- 拦截所有请求--><url-pattern>/</url-pattern></servlet-mapping></web-app>6.自定义注解注解在这里的作用就相当于给类/方法加上一个小尾巴,我们通过不同的尾巴辨识不同的Controller和Method我们定义两个注解@MyControllerpackage com.cloudwise.annotition;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** @author Teacher 陈* @creat 2021-02-22-13:04* @describ 我的Controller注解,用于模仿spring中的@Controller* 能够作用于类上,标识该类是一个Controller*/@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface MyController {/*** 没有用,但为了模仿spring中的@Controller,我们还是把它加上* 我们的简单版采用默认的id:首字母小写的类名*/String value() default "";}@MyRequestMappingpackage com.cloudwise.annotition;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** @author Teacher 陈* @creat 2021-02-22-13:11* @describ 用于模仿spring中的@RequestMapping* 能够作用于类和方法上,用于通过url指定对应的Controller和 Method*/@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface MyRequestMapping {/*** 简单版,域名只能有一段,只能是/controllerName/methodName*/String value() default "";}好了上面的就是一些准备性的工作,如果说把仿写springMVC看成是组成一个团队的话,上面的工作相当于给团队找工作场地,下面就是对人物的刻画了,首先有请我们的领导MyDispatcherServlet编写前端控制器编写前端控制器(一个Servlet),并重写init和service方法MyDispatcherServlet总览整个过程围绕两个重写的方法而展开,其中init()是重点。
MyDispatcherServlet要做的事,用一句话来说:看前端的访问地址,然后调用匹配的处理器(Controller)的对应方法(method)要完成这些,我们需要通过注解,为Controller和method绑定上一定的字符串,然后通过分析前端传过来的Url中的字符串,找到两者相同的,以此完成匹配。
反射在此过程中发挥了巨大作用,不论是找到类头上的注解,还是找到注解中的值等诸多动作都需要反射。
具体流程InitService 注:在此处Handler = controller + method代码(分步)创建一个dispatcherServlet继承httpservlet 并重写两个方法public class MyDispatcherServlet extends HttpServlet {@Overridepublic void init(ServletConfig config) throws ServletException {}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {}}接下来就是填充两个方法了,首先是init()方法它大概可以分为4步加载配置文件扫描controller包初始化controller初始化Handler映射器(Handler = controller + method)那我们开始吧,写加载配置文件的代码1.加载配置文件首先,我们在这里选用properties文件的形式进行配置,因此,需要有一个properties对象/*** 我们将需要扫描的包放在一个.properties文件中* 需要在初始化的时候读取它*/private Properties properties = new Properties();再写一个工具性的方法/*** 加载配置文件* @param fileName*/private void loadConfigfile(String fileName) throws IOException {//以流的方式获取资源InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(fileName);properties.load(resourceAsStream);resourceAsStream.close();}之后,我们在init()中调用该方法@Overridepublic void init(ServletConfig config) throws ServletException {//1. 加载配置文件//这里我们在web.xml中配置的初始化参数contextConfigLocation就起到效果了String initParameter = config.getInitParameter("contextConfigLocation");try {loadConfigfile(initParameter);} catch (IOException e) {e.printStackTrace();}}那么至此,我们的第一步加载配置文件部分的代码就写完啦另外三步采用同样的思路2.扫描controller包定义所需属性/*** 我们需要一个Set,将所有能够响应的Controller存起来*/private Set<Class<?>> classSet = new HashSet<>();写工具性方法/*** 扫描包,获取所有带MyController的类* @param packageName*/private void scanPackage(String packageName){Reflections reflections = new Reflections(packageName);classSet = reflections.getTypesAnnotatedWith(MyController.class);}在init()中调用@Overridepublic void init(ServletConfig config) throws ServletException {//1. 加载配置文件//这里我们在web.xml中配置的初始化参数contextConfigLocation就起到效果了String initParameter = config.getInitParameter("contextConfigLocation");try {loadConfigfile(initParameter);} catch (IOException e) {e.printStackTrace();}//2. 扫描controller包,存储所有能够响应的ControllerscanPackage(properties.getProperty("package"));}3.初始化controller定义所需属性/*** 类spring-mvc容器,存储Controller对象*/private Map<String,Object> mySpringMVCContext = new HashMap<>();写工具性方法/*** 初始化Controller*/private void initController() throws IllegalAccessException, InstantiationException {if(classSet.isEmpty()){return;}for (Class<?> controller : classSet) {mySpringMVCContext.put(firstWordToLowCase(controller.getSimpleName()),controller.newInstance());}}/*** 首字母转小写* @param string* @return 首字母为小写的String*/private String firstWordToLowCase(String string){char[] chars = string.toCharArray();//将大写转成小写chars[0]+=32;return String.valueOf(chars);}在init()中调用@Overridepublic void init(ServletConfig config) throws ServletException {//1. 加载配置文件String initParameter = config.getInitParameter("contextConfigLocation");try {loadConfigfile(initParameter);} catch (IOException e) {e.printStackTrace();}//2. 扫描controller包,存储所有能够响应的ControllerscanPackage(properties.getProperty("package"));//3. 初始化controllertry {initController();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}4.初始化Handler映射器(Handler = controller + method)定义所需属性/*** 存储所有方法的Map<url:method>*/private Map<String,Method> methodMap = new HashMap<>();/*** 存储所有Controller的Map*/private Map<String,Object> controllerMap = new HashMap<>();具体实现方法private void initHandlerMapping() {if (mySpringMVCContext.isEmpty()){return;}for (Map.Entry<String, Object> entry : mySpringMVCContext.entrySet()) {Class<?> entryClass = entry.getValue().getClass();if (!entryClass.isAnnotationPresent(MyController.class)){continue;}//Controller类上的requestMapping值,如果有则获取String baseUrl = "";if (entryClass.isAnnotationPresent(MyRequestMapping.class)){MyRequestMapping annotation = entryClass.getAnnotation(MyRequestMapping.class);baseUrl = annotation.value();}//获取所有方法Method[] methods = entryClass.getMethods();for (Method method : methods) {if (method.isAnnotationPresent(MyRequestMapping.class)){MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);String url = annotation.value();url = baseUrl + url;//将该方法放入方法集methodMap.put(url,method);//将该controller方法处理器集controllerMap.put(url,entry.getValue());//至此,初始化完成,后端整装待发}}}}在init()中调用@Overridepublic void init(ServletConfig config) throws ServletException {//1. 加载配置文件String initParameter = config.getInitParameter("contextConfigLocation");try {loadConfigfile(initParameter);} catch (IOException e) {e.printStackTrace();}//2. 扫描controller包,存储所有能够响应的ControllerscanPackage(properties.getProperty("package"));//3. 初始化controllertry {initController();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}//4. 初始化Handler映射器initHandlerMapping();}好了至此,我们的领导MyDispatcherServlet 的初始化部分就写完了,现在他已经对自己的团队成员:众多业务员们(Controller)已经了如指掌了(有同学可能会问:陈老师,你还没定义Controller呢!这个先不急)下面,我们就重写他的service()方法,让他能够到外面接活@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {if (methodMap.isEmpty()){return;}String uri = req.getRequestURI();String contextPath = req.getContextPath();//获取有效urlString url = uri.replace(contextPath,"");//如果没有对应的url,返回404if (!methodMap.containsKey(url)){resp.getWriter().println("404");}else {//有的话,通过invoke方法执行对应controller的methodMethod method = methodMap.get(url);Object controller = controllerMap.get(url);try {method.invoke(controller);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}至此,MyDispatcherServlet的所有代码都已经完成了,他也能够成为一个合格的领导了。
上面部分代码写的较为分散,文末放上MyDispatcherServlet的完整代码供同学们参考最后,编写一个Controller进行测试package com.cloudwise.controller;/*** @author Teacher 陈* @creat 2021-02-22-14:57* @describ*/import com.cloudwise.annotition.MyController;import com.cloudwise.annotition.MyRequestMapping;/*** @author :Teacher 陈* @date :Created in 2021/2/22 14:57* @description:我的实验性Controller* @modified By:* @version:*/@MyController@MyRequestMapping(value = "/test")public class MyFirstController {@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被调用了");}}测试1.为本项目配置tomcat2.运行3.1浏览器地址栏输入对应网址控制台成功打印日志信息3.2浏览器地址栏输入无效网址,页面返回404至此,今天的手写springMVC就全部完成了。
当然本项目还有很多待提升的地方,诸如不能返回json数据,controller不能有参数,等等。
但是我们不可能一朝一夕建成罗马,应该一步一个脚印,通过这个项目掌握springMVC的运行流程,为以后更难的项目打下点基础。
代码(总览)package com.cloudwise.servlet;/*** @author Teacher 陈* @creat 2021-02-22-13:44* @describ*/import com.cloudwise.annotition.MyController;import com.cloudwise.annotition.MyRequestMapping;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.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.*;/*** @author :Teacher 陈* @date :Created in 2021/2/22 13:44* @description:我的前端控制器dispather* @modified By:* @version:*/public class MyDispatcherServlet extends HttpServlet {/*** 我们将需要扫描的包放在一个.properties文件中* 需要在初始化的时候读取它*/private Properties properties = new Properties();/*** 我们需要一个Set,将所有能够响应的Controller存起来*/private Set<Class<?>> classSet = new HashSet<>();/*** 类spring-mvc容器,存储Controller对象*/private Map<String,Object> mySpringMVCContext = new HashMap<>();/*** 存储所有方法的Map<url:method>*/private Map<String,Method> methodMap = new HashMap<>();/*** 存储所有Controller的Map*/private Map<String,Object> controllerMap = new HashMap<>();/*** @description: 初始化,要做什么事呢?* 0. 接收到请求之后,首先将后端环境初始化好* 1. 加载配置文件* 2. 扫描controller包* 3. 初始化controller* 4. 初始化Controller映射器* @create by: Teacher 陈* @create time: 2021/2/22 13:47* @param config* @return void*/@Overridepublic void init(ServletConfig config) throws ServletException {//1. 加载配置文件String initParameter = config.getInitParameter("contextConfigLocation");try {loadConfigfile(initParameter);} catch (IOException e) {e.printStackTrace();}//2. 扫描controller包,存储所有能够响应的ControllerscanPackage(properties.getProperty("package"));//3. 初始化controllertry {initController();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}//4. 初始化Controller映射器initHandlerMapping();}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {if (methodMap.isEmpty()){return;}String uri = req.getRequestURI();String contextPath = req.getContextPath();String url = uri.replace(contextPath,"");if (!methodMap.containsKey(url)){resp.getWriter().println("404");}else {Method method = methodMap.get(url);Object controller = controllerMap.get(url);try {method.invoke(controller);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}/*** 以下为工具性函数*//*** 加载配置文件* @param fileName*/private void loadConfigfile(String fileName) throws IOException {//以流的方式获取资源InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(fileName);properties.load(resourceAsStream);resourceAsStream.close();}/*** 扫描包,获取所有带MyController的类* @param packageName*/private void scanPackage(String packageName){Reflections reflections = new Reflections(packageName);classSet = reflections.getTypesAnnotatedWith(MyController.class);}/*** 初始化Controller*/private void initController() throws IllegalAccessException, InstantiationException {if(classSet.isEmpty()){return;}for (Class<?> controller : classSet) {mySpringMVCContext.put(firstWordToLowCase(controller.getSimpleName()),controller.newInstance());}}/*** 首字母转小写* @param string* @return 首字母为小写的String*/private String firstWordToLowCase(String string){char[] chars = string.toCharArray();//将大写转成小写chars[0]+=32;return String.valueOf(chars);}private void initHandlerMapping() {if (mySpringMVCContext.isEmpty()){return;}for (Map.Entry<String, Object> entry : mySpringMVCContext.entrySet()) {Class<?> entryClass = entry.getValue().getClass();if (!entryClass.isAnnotationPresent(MyController.class)){continue;}//Controller类上的requestMapping值,如果有则获取String baseUrl = "";if (entryClass.isAnnotationPresent(MyRequestMapping.class)){MyRequestMapping annotation = entryClass.getAnnotation(MyRequestMapping.class);baseUrl = annotation.value();}//获取所有方法Method[] methods = entryClass.getMethods();for (Method method : methods) {if (method.isAnnotationPresent(MyRequestMapping.class)){MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);String url = annotation.value();url = baseUrl + url;//将该方法放入方法集methodMap.put(url,method);//将该controller方法处理器集controllerMap.put(url,entry.getValue());//至此,初始化完成,后端整装待发}}}}}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)