mvc框架圖書館管理系統jdbc「mvc架構圖用什麼畫」

作為經典MVC思想的spring實現,它能夠幫我們開發靈活的JavaWeb應用。今天我們就來對它動刀,看看它的內部是怎麼實現的,我們能不能仿寫一份呢?

首先我們通過一張時序圖來看一下springMVC的運行流程。建哥手把手系列——手寫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)和各類業務員等。業務員,還是只負責具體業務,其他的活全讓領導干。

我們的流程:

建哥手把手系列——手寫springMVC框架

在我們的流程中 DispatcherServlet領導 = 前端控制器 + 映射處理器

好了明確了我們要搭的任務,現在建哥來手把手教學,開搞!

詳細步驟

1.新建webApp骨架的maven工程

建哥手把手系列——手寫springMVC框架

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

建哥手把手系列——手寫springMVC框架

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

建哥手把手系列——手寫springMVC框架

Service 注:在此處Handler = controller + method

建哥手把手系列——手寫springMVC框架

代碼(分步)

創建一個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

建哥手把手系列——手寫springMVC框架

2.運行

3.1瀏覽器地址欄輸入對應網址

建哥手把手系列——手寫springMVC框架

控制台成功打印日誌信息

建哥手把手系列——手寫springMVC框架

3.2瀏覽器地址欄輸入無效網址,頁面返回404

建哥手把手系列——手寫springMVC框架

至此,今天的手寫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());
//至此,初始化完成,後端整裝待發
}
}
}
}
}

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/253033.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-14 02:24
下一篇 2024-12-14 02:24

相關推薦

發表回復

登錄後才能評論