mvc架构图用什么画(mvc框架图书馆管理系统jdbc)

2022-07-18,,,,

作为经典mvc思想的spring实现,它能够帮我们开发灵活的javaweb应用。今天我们就来对它动刀,看看它的内部是怎么实现的,我们能不能仿写一份呢?

首先我们通过一张时序图来看一下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.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">
<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

我们定义两个注解

@mycontroller
package 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 "";
}
@myrequestmapping
package 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中的字符串,找到两者相同的,以此完成匹配。反射在此过程中发挥了巨大作用,不论是找到类头上的注解,还是找到注解中的值等诸多动作都需要反射。

具体流程

init

service 注:在此处handler = controller + method

代码(分步)

创建一个dispatcherservlet继承httpservlet 并重写两个方法

public class mydispatcherservlet extends httpservlet {

@override
public void init(servletconfig config) throws servletexception {
}

@override
protected void service(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception {

}
}

接下来就是填充两个方法了,首先是init()方法

它大概可以分为4步

  1. 加载配置文件
  2. 扫描controller包
  3. 初始化controller
  4. 初始化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()中调用该方法
@override
public 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()中调用

@override
public 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包,存储所有能够响应的controller
scanpackage(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()中调用
@override
public void init(servletconfig config) throws servletexception {
//1. 加载配置文件
string initparameter = config.getinitparameter("contextconfiglocation");
try {
loadconfigfile(initparameter);
} catch (ioexception e) {
e.printstacktrace();
}
//2. 扫描controller包,存储所有能够响应的controller
scanpackage(properties.getproperty("package"));
//3. 初始化controller
try {
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()中调用

@override
public void init(servletconfig config) throws servletexception {
//1. 加载配置文件
string initparameter = config.getinitparameter("contextconfiglocation");
try {
loadconfigfile(initparameter);
} catch (ioexception e) {
e.printstacktrace();
}
//2. 扫描controller包,存储所有能够响应的controller
scanpackage(properties.getproperty("package"));
//3. 初始化controller
try {
initcontroller();
} catch (illegalaccessexception e) {
e.printstacktrace();
} catch (instantiationexception e) {
e.printstacktrace();
}
//4. 初始化handler映射器
inithandlermapping();
}

好了至此,我们的领导mydispatcherservlet 的初始化部分就写完了,现在他已经对自己的团队成员:众多业务员们(controller)已经了如指掌了(有同学可能会问:陈老师,你还没定义controller呢!这个先不急)下面,我们就重写他的service()方法,让他能够到外面接活

@override
protected void service(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception {
if (methodmap.isempty()){
return;
}
string uri = req.getrequesturi();
string contextpath = req.getcontextpath();
//获取有效url
string url = uri.replace(contextpath,"");
//如果没有对应的url,返回404
if (!methodmap.containskey(url)){
resp.getwriter().println("404");
}else {
//有的话,通过invoke方法执行对应controller的method
method 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.为本项目配置tomcat

2.运行

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
*/
@override
public void init(servletconfig config) throws servletexception {
//1. 加载配置文件
string initparameter = config.getinitparameter("contextconfiglocation");
try {
loadconfigfile(initparameter);
} catch (ioexception e) {
e.printstacktrace();
}
//2. 扫描controller包,存储所有能够响应的controller
scanpackage(properties.getproperty("package"));
//3. 初始化controller
try {
initcontroller();
} catch (illegalaccessexception e) {
e.printstacktrace();
} catch (instantiationexception e) {
e.printstacktrace();
}
//4. 初始化controller映射器
inithandlermapping();
}

@override
protected 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());
//至此,初始化完成,后端整装待发
}
}
}
}
}

《mvc架构图用什么画(mvc框架图书馆管理系统jdbc).doc》

下载本文的Word格式文档,以方便收藏与打印。