SpringMVC底層機制簡單實作-01
主要完成:核心分發控制器+Controller和Service注入容器+物件自動裝配+控制器方法獲取引數+視圖決議+回傳JSON格式資料
1.搭建開發環境
-
創建 Maven 專案,File-New-Project-Maven
-
將 pom.xml 檔案中的編譯版本改為1.8
-
在 src 目錄下創建以下目錄:
java 代碼放在 java 目錄下,相關的資源檔案放在 resource 目錄下,對 maven 的 web 專案而言,resource 就是類路徑,前端頁面放在 webapp 下,該目錄對應之前的 web 目錄,test/java 目錄用于存放測驗檔案,測驗需要的資源檔案放在 test/resource 目錄下,
-
在 pom.xml 中引入基本的 jar 包
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--引入原生servlet依賴的jar包--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <!-- 1.scope表示引入jar包的作用范圍, 2.provided表示專案在打包放到生產環境時,不需要打上servlet-api.jar 3.因為 tomcat本身就有該jar包,使用tomcat的即可,防止版本沖突 --> <scope>provided</scope> </dependency> <!--引入dom4j,用于決議xml--> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <!--引入常用的工具類jar包,該jar含有很多常用的類--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.5</version> </dependency> </dependencies>
2.任務1-開發MyDispatcherServlet
說明:撰寫 MyDispatcherServlet,充當原生的 DispatcherServlet(即核心控制器)
2.1分析
2.2代碼實作
-
創建 src/main/java/com/li/myspringmvc/servlet/MyDispatcherServlet.java,充當原生的前端控制器,
package com.li.myspringmvc.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author 李 * @version 1.0 * 1.MyDispatcherServlet 充當原生的 DispatcherServlet,它的本質就是一個Servlet * 因此繼承 HttpServlet */ public class MyDispatcherServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyDispatcherServlet doGet() 被呼叫.."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyDispatcherServlet doPost() 被呼叫.."); } } -
創建 src/main/resources/myspringmvc.xml,充當原生的 applicationContext-mvc.xml(即 spring 容器組態檔)
-
配置 src/main/webapp/WEB-INF/web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>MyDispatcherServlet</servlet-name> <servlet-class>com.li.myspringmvc.servlet.MyDispatcherServlet</servlet-class> <!--給前端控制器指定配置引數,指定要操作的spring容器檔案--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:myspringmvc.xml</param-value> </init-param> <!--要求該物件在tomcat啟動時就自動加載--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MyDispatcherServlet</servlet-name> <!--作為前端控制器,攔截所有請求--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> -
配置 Tomcat,進行測驗
-
瀏覽器訪問
http://localhost:8080/li_springmvc/aaa
3.任務2-實作客戶端/瀏覽器可以請求控制層
3.1分析
任務2的總目標是:
實作自己的 @Controller 注解和 @RequestMapping 注解,當瀏覽器訪問指定的 URL 時,由前端控制器,找到 Controller 的某個方法,然后通過 tomcat 將資料回傳給瀏覽器,
3.2代碼實作
步驟一:兩個注解和測驗Controller
(1)Controller 注解
package com.li.myspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author 李
* @version 1.0
* 該注解用于標識一個控制器組件
* 1.@Target(ElementType.TYPE) 指定自定義注解可修飾的型別
* 2.@Retention(RetentionPolicy.RUNTIME) 作用范圍,RUNTIME使得可以通過反射獲取自定義注解
* 3.@Documented 在生成檔案時,可以看到自定義注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
(2)RequestMapping 注解
package com.li.myspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author 李
* @version 1.0
* RequestMapping 注解用于指定控制器-方法的映射路徑
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
(3)用于測驗的控制器 MonsterController.java
package com.li.controller;
import com.li.myspringmvc.annotation.Controller;
import com.li.myspringmvc.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author 李
* @version 1.0
*/
@Controller
public class MonsterController {
//撰寫方法,可以列出妖怪串列
//springmvc支持原生的servlet api,為了看到底層機制,這里直接放入兩個引數
@RequestMapping(value = "https://www.cnblogs.com/monster/list")
public void listMonster(HttpServletRequest request, HttpServletResponse response) {
//設定編碼
response.setContentType("text/html;charset=utf-8");
//獲取writer,回傳提示資訊
try {
PrintWriter printWriter = response.getWriter();
printWriter.print("<h1>妖怪串列資訊</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
步驟二:配置容器檔案 springmvc.xml,指定掃描的包
指定掃描的包是為了之后使用注解獲取需要反射的類
如果需要添加新的掃描包,在base-package添加包路徑,用逗號表示間隔,
<?xml version="1.0" encoding="utf-8" ?>
<beans>
<!--指定要掃描的包及其子包的java類-->
<component-scan base-package="com.li.controller,com.li.service"/>
</beans>
步驟三:撰寫 XMLParse 工具類,用于決議 springmvc.xml,得到要掃描的包
package com.li.myspringmvc.xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/**
* @author 李
* @version 1.0
* XMLParse用于決議spring組態檔
*/
public class XMLParse {
public static String getBasePackage(String xmlFile) {
SAXReader saxReader = new SAXReader();
//maven的類路徑是在target/li-springmvc/WEB-INF/classes/目錄下
//通過類的加載路徑-->獲取到spring組態檔[對應的資源流]
InputStream inputStream =
XMLParse.class.getClassLoader().getResourceAsStream(xmlFile);
try {
//得到組態檔的檔案
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
Element componentScanElement = rootElement.element("component-scan");
Attribute attribute = componentScanElement.attribute("base-package");
String basePackage = attribute.getText();
return basePackage;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
步驟四:開發MyWebApplicationContext,充當原生Spring容器,得到掃描類的全路徑串列
即把指定目錄包括子目錄下的 java 類的全路徑掃描到 ArrayList 集合中,以便之后反射,是否需要反射,還要取決于類中是否添加了@Controller注解
(1)MyWebApplicationContext.java 實作自定義的 spring 容器,目前先完成掃描作業
package com.li.myspringmvc.context;
import com.li.myspringmvc.xml.XMLParse;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* @author 李
* @version 1.0
* MyWebApplicationContext 是我們自定義的spring容器
*/
public class MyWebApplicationContext {
//屬性classFullPathList用于保存掃描包/子包的類的全路徑
private List<String> classFullPathList = new ArrayList<>();
//該方法完成對自己的 spring容器的初始化
public void init() {
//回傳的是我們在容器檔案中配置的base-package的value
String basePackage = XMLParse.getBasePackage("myspringmvc.xml");
//這時你的 basePackage是像 com.li.controller,com.li.service 這樣子的
//通過逗號進行分割包
String[] basePackages = basePackage.split(",");
if (basePackages.length > 0) {
//遍歷這些包
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("掃描后的路徑classFullPathList=" + classFullPathList);
}
/**
* 該方法完成對包的掃描
* @param pack 表示要掃描的包,如 "com.li.controller"
*/
public void scanPackage(String pack) {
//得到包所在的作業路徑[絕對路徑]
// (1)通過類的加載器,得到指定包的作業路徑[絕對路徑]
// (2)然后用斜杠代替點=>如 com.li.controller=>com/li/controller
URL url =
this.getClass().getClassLoader()
.getResource("/" + pack.replaceAll("\\.", "/"));
// url=file:/D:/IDEA-workspace/li-springmvc/target/li-springmvc
// /WEB-INF/classes/com/li/controller/
//System.out.println("url=" + url);
//根據得到的路徑,對其進行掃描,把類的全路徑保存到 classFullPathList屬性中
String path = url.getFile();
System.out.println("path=" + path);
//在io中,把目錄也視為一個檔案
File dir = new File(path);
//遍歷 dir目錄,因為可能會有[多個檔案/子目錄]
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
//如果是目錄,需要遞回掃描
//pack加上下一級的目錄名繼續下一層的掃描
scanPackage(pack + "." + file.getName());
} else {
//這時得到的檔案可能是.class檔案,也可能是其他檔案
//就算是class檔案,還需要考慮是否要注入到容器的問題
//目前先把所有檔案的全路徑都保存到集合中,后面注入物件到spring容器時再考慮過濾
String classFullPath =
pack + "." + file.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
}
(2)通過 MyDispatcherServlet 前端控制器來呼叫并初始化 spring 容器
//添加init方法,用于初始化spring容器
@Override
public void init() throws ServletException {
MyWebApplicationContext myWebApplicationContext = new MyWebApplicationContext();
myWebApplicationContext.init();
}
(3)啟動tomcat,后臺成功獲取到了路徑,測驗成功,
tomcat啟動--加載了MyDispatcherServlet--通過該Servlet的init()生命周期方法初始化自定義的 spring 容器,同時呼叫自定義 spring 容器的 init 方法去掃描包
步驟五:完善MyWebApplicationContext(自定義 spring 容器),實體化物件到容器中
將掃描到的類,在滿足添加了注解的情況下,通過反射注入到 ioc 容器
(1)部分代碼:在MyWebApplicationContext中添加新屬性 ioc 和新方法 executeInstance,
//定義屬性ioc,用于存放反射生成的 bean物件(單例的)
public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();
/**
* 該方法完成對自己的 spring容器的初始化
*/
public void init() {
//回傳的是我們在容器檔案中配置的base-package的value
String basePackage = XMLParse.getBasePackage("myspringmvc.xml");
//這時你的 basePackage是像 com.li.controller,com.li.service 這樣子的
//通過逗號進行分割包
String[] basePackages = basePackage.split(",");
if (basePackages.length > 0) {
//遍歷這些包
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("掃描后的路徑classFullPathList=" + classFullPathList);
//將掃描到的類反射到ioc容器
executeInstance();
System.out.println("掃描后的ioc容器=" + ioc);
}
//...
/**
* 該方法將掃描到的類,在滿足條件的情況下進行反射,并放入到ioc容器中
*/
public void executeInstance() {
//是否掃描到了類
if (classFullPathList.size() == 0) {//沒有掃描到類
return;
}
//遍歷 classFullPathList,進行反射
try {
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
//判斷是否要進行反射(即是否添加了注解)
if (clazz.isAnnotationPresent(Controller.class)) {
Object instance = clazz.newInstance();
//獲取該物件的id,默認情況下為類名(首字母小寫)
String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase()
+ clazz.getSimpleName().substring(1);
String value = https://www.cnblogs.com/liyuelian/archive/2023/02/09/clazz.getAnnotation(Controller.class).value();
if (!"".equals(value)) {//如果注解的value指定了id
beanName = value;
}
ioc.put(beanName, instance);
}//如果有其他注解,可以進行擴展
}
} catch (Exception e) {
e.printStackTrace();
}
}
(2)啟動tomcat,反射成功,
步驟六:完成請求URL和控制器方法的映射關系
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/543404.html
標籤:其他
