作为经典mvc思想的spring实现,它能够帮我们开发灵活的javaweb应用。今天我们就来对它动刀,看看它的内部是怎么实现的,我们能不能仿写一份呢?
首先我们通过一张时序图来看一下springmvc的运行流程。
springmvc运行流程
从上面的时序图,我们可看到,一个叫dispatcherrvlet的家伙十分繁忙,几乎每一步都有它的参与,他怎么这么忙啊,这就和它的名字有关
dispatcher /dɪs’pætʃə/ n. 发报机,调度员
它就相当于在m-v-c三者之间的邮差,或者说是领导,负责调用各个组件。
我们来假设一下这个场景:
dispatcherrvlet是mvc场景里的老大,而且亲力亲为,什么事都要他过目审批,这天他收到了一份用户请求,叫他给出一个网页页面。
他马上给他的副手handlermapping,说:“小刘,你看看这个活,谁来干合适?”小刘handlermapping一看员工花名册有一个叫小张的controller能够胜任,小刘就对领导说:“controller小张能干”。
这时候,领导dispatcherrvlet不能直接找到小张,因为小张只负责实现具体业务,而用户的要求太抽象,小张看不懂,需要有个人帮他理一理,第一步该做什么,第二步该做什么。这时候项目经理handleradaper就上线了,领导找到项目经理说:“帮小张理一理,这个活具体该咋做”。
项目经理三下五除二给整完了,之后,领导拿着处理好的任务,将任务交给里小张,我们的小张也很争气呀,也给干完了,而且,他干的工程不仅有业务(model)还有漂亮的组件(view),不过小张同学的审美不太好,没办法把它们组合到一块。于是,领导dispatcherrvlet就吭哧吭哧跑到学美术的viewrsolver身边,让她给渲染一下。viewrsolver画技高超,寥寥几笔渲染出来了一份既有业务资料,也很好看的页面出来。
至此一个项目完成了,dispatcherrvlet就拿着成果(jsp等前端页面)展示给用户看,用户心满意足,大方的付了钱,于是,大家都有钱拿…
看完了rod johnson的springmvc的mvc 流程,里面组件分工明确,各司其职,能够完成很多复杂的业务,但是我们刚开始上手,肯定不能上来就整这么多,因此今天我们搭一个简单版的,只有领导(dispatcherrvlet)和各类业务员等。业务员,还是只负责具体业务,其他的活全让领导干。
我们的流程:
在我们的流程中 dispatcherrvlet领导 = 前端控制器 + 映射处理器
好了明确了我们要搭的任务,现在建哥来手把手教学,开搞!
<!– 引入rvlet jar –>
<dependency>
<groupid>javax.rvlet</groupid>
<artifactid>javax.rvlet-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>
在resource目录下编写配置文件:
applicationcontext.properties,内容为:指定扫描路径package,我们在这里指定controller所在的包
package=com.cloudwi.controller
骨架用的还是2.0版本,我们在这里更新为4.0的。
并且注册我们的领导mydispatcherrvlet并为其指定配置文件所在位置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><!-- 配置我们自己的前端控制器,mydispatcherrvlet就是一个rvlet,拦截前端发送的请求--><rvlet><rvlet-name>xxx</rvlet-name><rvlet-class>com.cloudwi.rvlet.mydispatcherrvlet</rvlet-class><init-param><param-name>contextconfiglocation</param-name><param-value>applicationcontext.properties</param-value></init-param></rvlet><rvlet-mapping><rvlet-name>xxx</rvlet-name><!-- 拦截所有请求--><url-pattern>/</url-pattern></rvlet-mapping></web-app>
注解在这里的作用就相当于给类/方法加上一个小尾巴,我们通过不同的尾巴辨识不同的controller和method
我们定义两个注解
@mycontrollerpackage com.cloudwi.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.cloudwi.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指定对应的controlle夜宿山寺r和 method*/@target({elementtype.type,elementtype.method})@retention(retentionpolicy.runtime)public @interface myrequestmapping {/*** 简单版,域名只能有一段,只能是/controllername/methodname*/string value() default "";}
好了上面的就是一些准备性的工作,如果说把仿写springmvc看成是组成一个团队的话,上面的工作相当于给团队找工作场地,下面就是对人物的刻画了,首先有请我们的领导mydispatcherrvlet
编写前端控制器(一个rvlet),并重写init和rvice方法
mydispatcherrvlet
整个过程围绕两个重写的方法而展开,其中init()是重点。
mydispatcherrvlet要做的事,用一句话来说:看前端的访问地址,然后调用匹配的处理器(controller)的对应方法(method)
要完成这些,我们需要通过注解,为controller和method绑定上一定的字符串,然后通过分析前端传过来的url中的字符串,找到两者相同的,以此完成匹配。反射在此过程中发挥了巨大作用,不论是找到类头上的注解,还是找到注解中的值等诸多动作都需要反射。
init
rvice 注:在此处handler = controller + method
创建一个dispatcherrvlet继承httprvlet 并重写两个方法
public class mydispatcherrvlet extends httprvlet {@overridepublic vo考研流程图id init(rvletconfig config) throws rvletexception {}@overrideprotected void rvice(httprvletrequest req, httprvletrespon resp) throws rvletexception, ioexception {}}
接下来就是填充两个方法了,首先是init()方法
它大概可以分为4步
加载配置文件扫描三个牛叫什么controller包初始化controller初始化handler映射器(handler = controller + method)那我们开始吧,写加载配置文件的代码
首先,我们在这里选用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.clo();}之后,我们在init()中调用该方法@overridepublic void init(rvletconfig config) throws rvletexception {//1. 加载配置文件//这里我们在web.xml中配置的初始化参数contextconfiglocation就起到效果了string initparameter = config.getinitparameter("contextconfiglocation");try {loadconfigfile(initparameter);} catch (ioexception e) {e.printstacktrace();}}
那么至此,我们的第一步加载配置文件部分的代码就写完啦
另外三步采用同样的思路
定义所需属性
/*** 我们需要一个t,将所有能够响应的controller存起来*/private t<class<?>> classt = new hasht<>();写工具性方法/*** 扫描包,获取所有带mycontroller的类* @param packagename*/private void scanpackage(string packagename){reflections reflections = new reflections(packagename);classt = reflections.gettypesannotatedwith(mycontroller.class);}
在init()中调用
@overridepublic void init(rvletconfig config) throws rvletexception {//1. 加载配置文件//这里我们在web.xml中配置的初始化参数contextconfiglocation就起到效果了string initparameter = config.getinitparameter("contextconfiglocation");try {loadconfigfile(initparameter);} catch (ioexception e) {e.printstacktrace();}//2. 扫描controller包,存储所有能够响应的controllerscanpackage(properties.getproperty("package"));}
定义所需属性
/*** 类spring-mvc容器,存储controller对象*/private map<string,object> myspringmvccontext = new hashmap<>();写工具性方法/*** 初始化controller*/private void initcontroller() throws illegalaccesxception, instantiationexception {if(classt.impty()){return;}for (class<?> controller : classt) {myspringmvccontext.put(firstwordtolowca(controller.getsimplename()),controller.newinstance());}}/*** 首字母转小写* @param string* @return 首字母为小写的string*/private string firstwordtolowca(string string){char[] chars = string.tochararray();//将大写转成小写chars[0]+=32;return string.valueof(chars);}在init()中调用@overridepublic void init(rvletconfig config) throws rvletexception {//1. 加载配置文件string initparameter = config.getinitparameter("contextconfiglocation");try {loadconfigfile(initparameter);} catch (ioexception e) {e.printstacktrace();}//2. 扫描controller包,存储所有能够响应的controllerscanpackage(properties.getproperty("package"));//3. 初始化controllertry {initcontroller();} catch (illegalaccesxception e) {e.printstacktrace();} catch (instantiationexception e) {e.printstacktrace();}}
(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.impty()){return;}for (map.entry<string, object> entry : myspringmvccontext.entryt()) {class<?> entryclass = entry.getvalue().getclass();if (!entryclass.isannotationprent(mycontroller.class)){continue;}//controller类上的requestmapping值,如果有则获取string baurl = "";if (entryclass.isannotationprent(myrequestmapping.class)){myrequestmapping annotation = entryclass.getannotation(myrequestmapping.class);baurl = annotation.value();}//获取所有方法method[] methods = entryclass.getmethods();for (method method : methods) {if (method.isannotationprent(myrequestmapping.class)){myrequestmapping annotation = method.getannotation(myrequestmapping.class);string url = annotation.value();url = baurl + url;//将该方法放入方法集methodmap.put(url,method);//将该controller方法处理器集controllermap.put(url,entry.getvalue());//至此,初始化完成,后端整装待发}}}}
在init()中调用
@overridepublic void init(rvletconfig config) throws rvletexception {//1. 加载配置文件string initparameter = config.getinitparameter("contextconfiglocation");try {loadconfigfile(initparameter);} catch (ioexception e) {e.printstacktrace();}//2. 扫描controller包,存储所有能够响应的controllerscanpackage(properties.getproperty("package"));//3. 初始化controllertry {initcontroller();} catch (illegalaccesxception e) {e.printstacktrace();} catch (instantiationexception e) {e.printstacktrace();}//4. 初始化handler映射器inithandlermapping();}
好了至此,我们的领导mydispatcherrvlet 的初始化部分就写完了,现在他已经对自己的团队成员:众多业务员们(controller)已经了如指掌了(有同学可能会问:陈老师,你还没定义controller呢!这个先不急)下面,我们就重写他的rvice()方法,让他能够到外面接活
@overrideprotected void rvice(httprvletrequest req, httprvletrespon resp) throws rvletexception, ioexception {if (methodmap.impty()){return;}string uri = req.getrequesturi();string contextpath = req.getcontextpath();//获取有效urlstring url = uri.replace(contextpath,"");//如果没有对应的url,返回404if (!methodmap.containskey(url)){resp.getwriter().println("404");}el {//有的话,通过invoke方法执行对应controller的methodmethod method = methodmap.get(url);object controller = controllermap.get(url);try {method.invoke(controller);} catch (illegalaccesxception e) {e.printstacktrace();} catch (invocationtargetexception e) {e.printstacktrace();}}}
至此,mydispatcherrvlet的所有代码都已经完成了,他也能够成为一个合格的领导了。
上面部分代码写的较为分散,文末放上mydispatcherrvlet的完整代码供同学们参考
package com.cloudwi.controller;/*** @author teacher 陈* @creat 2021-02-22-14:57* @describ*/import com.cloudwi.annotition.mycontroller;import com.cloudwi.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.为本项目配置tomcat
2.运行
3.1浏览器地址栏输入对应网址
控制台成功打印日志信息
3.2浏览器地址栏输入无效网址,页面返回404
至此,今天的手写springmvc就全部完成了。
当然本项目还有很多待提升的地方,诸如不能返回json数据,controller不能有参数,等等。但是我们不可能一朝一夕建成罗马,应该一步一个脚印,通过这个项目掌握springmvc的运行流程,为以后更难的项目打下点基础。
package com.cloudwi.rvlet;/*** @author teacher 陈* @creat 2021-02-22-13:44* @describ*/import com.cloudwi.annotition.mycontroller;import com.cloudwi.annotition.myrequestmapping;import org.reflections.reflections;import javax.rvlet.rvletconfig;import javax.rvlet.rvletexception;import javax.rvlet.http.httprvlet;import javax.rvlet.http.httprvletrequest;import javax.rvlet.http.httprvletrespon;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 mydispatcherrvlet extends httprvlet {/*** 我们将需要扫描的包放在一个.properties文件中* 需要在初始化的时候读取它*/private properties properties = new properties(有关战争的成语故事);/*** 我们需要一个t,将所有能够响应的controller存起来*/private t<class<?>> classt = new hasht<>();/*** 类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(rvletconfig config) throws rvletexception {//1. 加载配置文件string initparameter = config.getinitparameter("contextconfiglocation");try {loadconfigfile(initparameter);} catch (ioexception e) {e.printstacktrace();}//2. 扫描controller包,存储所有能够响应的controllerscanpackage(properties.getproperty("package"));//3. 初始化controllertry {initcontroller();} catch (illegalaccesxception e) {e.printstacktrace();} catch (instantiationexception e) {e.printstacktrace();}//4. 初始化controller映射器inithandlermapping();}@overrideprotected void rvice(httprvletrequest req, httprvletrespon resp) throws rvletexception, ioexception {if (methodmap.impty()){return;}string uri = req.getrequesturi();string contextpath = req.getcontextpath();string url = uri.replace(contextpath,"");if (!methodmap.containskey(url)){resp.getwriter().println("404");}el {method method = methodmap.get(url);object controller = controllermap.get(url);try {method.invoke(controller);} catch (illegalaccesxception 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.clo();}/*** 扫描包,获取所有带mycontroller的类* @param packagename*/private void scanpackage(string packagename){reflections reflections = new reflections(packagename);classt = reflections.gettypesannotate四级报名费dwith(mycontroller.class);}/*** 初始化controller*/private void initcontroller() throws illegalaccesxception, instantiationexception {if(classt.impty()){return;}for (class<?> controller : classt) {myspringmvccontext.put(firstwordtolowca(controller.getsimplename()),controller.newinstance());}}/*** 首字母转小写* @param string* @return 首字母为小写的string*/private string firstwordtolowca(string string){char[] chars = string.tochararray();//将大写转成小写chars[0]+=32;return string.valueof(chars);}private void inithandlermapping() {if (myspringmvccontext.impty()){return;}for (map.entry<string, object> entry : myspringmvccontext.entryt()) {class<?> entryclass = entry.getvalue().getclass();if (!entryclass.isannotationprent(mycontroller.class)){continue;}//controller类上的requestmapping值,如果有则获取string baurl = "";if (entryclass.isannotationprent(myrequestmapping.class)){myrequestmapping annotation = entryclass.getannotation(myrequestmapping.class);baurl = annotation.value();}//获取所有方法method[] methods = entryclass.getmethods();for (method method : methods) {if (method.isannotationprent(myrequestmapping.class)){myrequestmapping annotation = method.getannotation(myrequestmapping.class);string url = annotation.value();url = baurl + url;//将该方法放入方法集methodmap.put(url,method);//将该controller方法处理器集controllermap.put(url,entry.getvalue());//至此,初始化完成,后端整装待发}}}}}
本文发布于:2023-04-05 09:45:07,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/cdb9ef331b4918c5bccfca8db48a736b.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:mvc架构图用什么画(mvc框架图书馆管理系统jdbc).doc
本文 PDF 下载地址:mvc架构图用什么画(mvc框架图书馆管理系统jdbc).pdf
留言与评论(共有 0 条评论) |