当前位置:首页 > 知识库 > 正文
龙哥云资源网

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

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

首先我们通过一张时序图来看一下springMVC的运行流程。mvc架构图用什么画(mvc框架图书馆管理系统jdbc)  第1张

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)和各类业务员等。业务员,还是只负责具体业务,其他的活全让领导干。

我们的流程:

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

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

好了明确了我们要搭的任务,现在建哥来手把手教学,开搞!

详细步骤

1.新建webApp骨架的maven工程

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

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.新建包如图所示

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

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

我们定义两个注解

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

具体流程

Init

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

Service 注:在此处Handler = controller + method

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

代码(分步)

创建一个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.为本项目配置tomcat

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

2.运行

3.1浏览器地址栏输入对应网址

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

控制台成功打印日志信息

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

3.2浏览器地址栏输入无效网址,页面返回404

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

至此,今天的手写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());//至此,初始化完成,后端整装待发}}}}}

发表评论