SpringMVC學習總結 + 【手寫SpringMVC底層機制核心】

来源:https://www.cnblogs.com/zydevelop/p/18122469/zy_springmvc
-Advertisement-
Play Games

SpringMVC筆記 SpringMVC介紹 基本介紹 SpringMVC 是WEB 層框架, 接管了Web 層組件, 支持MVC 的開發模式/開發架構 SpringMVC 通過註解,讓POJO 成為控制器,不需要繼承類或者實現介面 SpringMVC 採用低耦合的組件設計方式,具有更好擴展和靈活 ...


SpringMVC筆記

SpringMVC介紹

基本介紹

  1. SpringMVC 是WEB 層框架, 接管了Web 層組件, 支持MVC 的開發模式/開發架構
  2. SpringMVC 通過註解,讓POJO 成為控制器,不需要繼承類或者實現介面
  3. SpringMVC 採用低耦合的組件設計方式,具有更好擴展和靈活性.
  4. 支持REST 格式的URL 請求.

Spring MVC和Spring之間的關係

  1. Spring MVC 只是Spring 處理WEB 層請求的一個模塊/組件, Spring MVC 的基石是Servlet 【Java WEB】
  2. Spring Boot 是為了簡化開發者的使用, 推出的封神框架(約定優於配置,簡化了Spring的配置流程), SpringBoot 包含很多組件/框架,Spring 就是最核心的內容之一,也包含SpringMVC
  3. Spring Boot > Spring > Spring MVC

@RequestMapping

基本介紹

@RequestMapping 註解可以指定控制器/處理器的某個方法的請求的url

 @RequestMapping(value = "/buy")
    public String buy(){
        System.out.println("購買商品");
        return "success";
    }

其它使用方式

  1. @RequestMapping 可以修飾方法和類

    • @RequestMapping 註解可以修飾方法,還可以修飾類當同時修飾類和方法時,
      請求的url 就是組合/類請求值/方法請求值
    @RequestMapping(value = "/user" )
    @Controller 
    public class UserHandler {
        
        @RequestMapping(value = "/buy")
        public String buy(){
            System.out.println("購買商品");
            return "success";
        }
    }
    

    此時url = /user/buy

  2. @RequestMapping 可以指定請求方式

    • @RequestMapping 還可以指定請求的方式(post/get/put/delete..), 請求的方式需
      要和指定的一樣,否則報錯
@RequestMapping(value = "/find", method = RequestMethod.GET)
    public String search(String bookId) {
        System.out.println("查詢書籍bookId= " + bookId);
        return "success";
    }
  • SpringMVC 控制器預設支持GET 和POST 兩種方式, 也就是你不指定method , 可以接收
    GET 和POST 請求
 @RequestMapping(value = "/buy")//預設支持GET 和POST 兩種方式
    public String buy(){
        System.out.println("購買商品");
        return "success";
    }

3.@RequestMapping 可指定params 和headers 支持簡單表達式

  1. param1: 表示請求必須包含名為param1 的請求參數

  2. !=param1: 表示請求不能包含名為param1 的請求參數

  3. param1 != value1: 表示請求包含名為param1 的請求參數,但其值不能為value1

  4. {"param1=value1", "param2"}: 請求必須包含名為param1 和param2 的兩個請求參數,
    且param1 參數的值必須為value1

@RequestMapping(value = "/find", params = "bookId", method = RequestMethod.GET)
public String search(String bookId) {
System.out.println("查詢書籍bookId= " + bookId);
return "success";
}

4.@RequestMapping 支持Ant 風格資源地址

  1. ?:匹配文件名中的一個字元
  2. *:匹配文件名中的任意字元
  3. **:匹配多層路徑
    • Ant 風格的url 地址舉例
      /user/*/createUser: 匹配/user/aaa/createUser、/user/bbb/createUser 等URL
      /user/**/createUser: 匹配/user/createUser、/user/aaa/bbb/createUser 等URL
      /user/createUser??: 匹配/user/createUseraa、/user/createUserbb 等URL

5.@RequestMapping 可配合@PathVariable 映射URL 綁定的占位符

  • 不需要在url 地址上帶參數名了,更加的簡潔明瞭
// 我們希望目標方法獲取到username 和userid, value="/xx/{username}" -@PathVariable("username")
    @RequestMapping(value = "/reg/{username}/{userId}")
    public String register(@PathVariable("username") String username,
                             @PathVariable("userId") String userId){
        System.out.println("接收到參數--" + "username= " + username + "--" + "usreid= " + userId);
        return "success";
    }
<a href="user/reg/kristina/300">占位符的演示</a>

6.請求的簡寫形式

​ @GetMapping @PostMapping @PutMapping @DeleteMapping

Rest請求風格

基本介紹

  1. REST:即Representational State Transfer。(資源)表現層狀態轉化。是目前流行的請求方式。它結構清晰, 很多網站採用。
  2. HTTP 協議裡面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:
  3. GET 用來獲取資源,POST 用來新建資源,PUT 用來更新資源,DELETE 用來刪除資源。

傳統的請求方法

在url中:
getBook?id=1 GET
delete?id=1 GET
update POST
add POST

說明: 傳統的url 是通過參數來說明crud 的類型,rest 是通過get/post/put/delete 來說明crud 的類型

REST 的核心過濾器

  1. 當前的瀏覽器只支持post/get 請求,因此為了得到put/delete 的請求方式需要使用Spring
    提供的HiddenHttpMethodFilter 過濾器進行轉換.
  2. HiddenHttpMethodFilter:瀏覽器form 表單隻支持GET 與POST 請求,而DELETE、PUT
    等method 並不支持,Spring 添加了一個過濾器,可以將這些請求轉換為標準的http 方
    法,使得支持GET、POST、PUT 與DELETE 請求
  3. HiddenHttpMethodFilter 能對post 請求方式進行轉換
  4. 過濾器需要在web.xml 中配置

配置過濾器

<filter>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

當我們需要 用delete/put 的方式時,

name="_method" 名字需要寫成_method 因為後臺的HiddenHttpMethodFilter
就是按這個名字來獲取hidden 域的值,從而進行請求轉換的.

<form action="user/book/100" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="修改書籍~">
</form>
public String delBook(@PathVariable("id") String id) {
System.out.println("刪除書籍id= " + id);
//return "success"; [如果這樣返回會報錯JSPs only permit GET POST orHEAD]
return "redirect:/user/success"; //重定向到一個沒有指定method 的Handler 方法
}

註意事項

  1. HiddenHttpMethodFilter, 在將post 轉成delete / put 請求時,是按_method 參數名來讀取的
  2. 如果web 項目是運行在Tomcat 8 及以上,會發現被過濾成DELETE 和PUT 請求,到達控制器時能順利執行,但是返回時(forward)會報HTTP 405 的錯誤提示:消息JSP 只允許GET、POST 或HEAD。
  3. 因此,將請求轉發(forward)改為請求重定向(redirect):重定向到一個Handler,由Handler 轉發到頁面

SpringMVC映射請求數據

獲取參數值

/**
* @RequestParam(value="name", required=false)
* 1.@RequestParam : 表示說明一個接受到的參數
* 2.value="name" : 接收的參數名是name
* 3.required=false : 表示該參數可以有,也可以沒有,如果required=true,表示必須傳遞該參數.
* 預設是required=true
*/
@RequestMapping("vote01")
    public String test01(@RequestParam(value = "name",required = false) String username){
        System.out.println("得到的username= "+username);
        return "success";
    }
  1. @RequestParam 表示會接收提交的參數
  2. value = "name" 表示提交的參數名是name
  3. required = false 表示該參數可以沒有,預設是true 表示必須有這個參數
  4. 當我們使用@RequestParam(value = "name",required = false) 就表示 請求的參數名和目標方法的形參名 可以不一致

獲取Http請求消息頭

@RequestMapping(value = "/vote02")
    public String test02(@RequestHeader(value = "Accept-Encoding")String ae,
                         @RequestHeader(value = "Host")String host) {
        System.out.println("Accept-Encoding =" + ae);
        System.out.println("Host =" + host);
        //返回到一個結果
        return "success";
    }

獲取javabean 形式的數據

@RequestMapping(value = "/vote03")
    public String test03(Master master) {
        System.out.println("主人信息= " + master);
        //返回結果
        return "success";
    }
  1. 方法的形參 (Master master) 用對應的對象類型指定即可,SpringMVC會自動的進行封裝

  2. 如果自動的完成封裝,要求提交的數據參數名和對象的欄位名保持一致

  3. 支持級聯數據獲取: 如果對象的屬性任然是一個對象,就通過 欄位名.欄位名 來提交

    比如Master[Pet] ,可以通過 pet.id ,pet.name 來指定

  4. 如果參數名和欄位名不匹配,則對象的屬性就是null

ServletAPI

  • 開發中原生的ServletAPI仍然可以使用
 @RequestMapping(value = "/vote04")
    public String test04(HttpServletRequest request,HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        System.out.println("username= "+username+" ,password= "+password);
		//返回結果
        return "success";
    }
  • 除了HttpServletRequest, HttpServletResponse 還可以其它對象也可以這樣的形式獲取
  1. HttpSession、java.security.Principal,InputStream,OutputStream,Reader,Writer

模型數據

數據放入Request域

通過HttpServletRequest放入Request域

  • 通過HttpServletRequest放入Request域是預設機制
 @RequestMapping(value = "/vote05")
    public String test05(Master master) {
        return "vote_ok";
    }
  1. 當我們提交的數據和對象名保持一致,那麼SpringMVC回自動封裝成一個對象【在前面 獲取javabean 形式的數據 講過】
  2. SpringMVC還會有一個機制,會自動將封裝的這個對象【model模型】放入Request域
  3. 也可以手動將對象放入Request域
  4. 會以 k-v 的形式放入Request域,k 是類名首字母小寫,v 是對象
  • 如果我們需要向Request域中添加新的屬性/對象

     request.setAttribute("address","beijing");
    
  • 如果我們要修改預設機制自動放入的對象的屬性

master.setName("pp");

通過請求的方法參數Map<String,Object>放入Request域

 @RequestMapping(value = "/vote06")
    public String test06(Master master, Map<String,Object> map) {
        map.put("address","tianjing");
        map.put("master",null);
        //返回到一個結果
        return "vote_ok";
    }
  • SpringMVC會遍歷Map,然會將map中的 k-v 存放到Request域
  • 如果 map.put("master",null); 會覆蓋預設機制的master,為null

通過返回ModelAndView對象實現Request域數據

@RequestMapping(value = "/vote07")
    public ModelAndView test07(Master master) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("address","shanghai");
        modelAndView.addObject("master",null);
        //指定跳轉的視圖名稱
        modelAndView.setViewName("vote_ok");
        return modelAndView;
    }
  • SpringMVC會遍歷ModelAndView,然會將ModelAndView中的 k-v 存放到Request域
  • 如果 modelAndView.addObject("master",null); 會覆蓋預設機制的master,為null

註意事項:

  1. 從本質上看,請求響應的方法 return "xx",是返回了一個字元串,其實本質是返回一個ModelAndView對象,只是預設被封裝起來了
  2. ModelAndView對象既可以包含model數據,也可以包含視圖信息
  3. ModelAndView對象的addObject("","");方法 可以添加key -value數據,預設在Request域中
  4. ModelAndView對象setView方法是指定 視圖名稱

數據放入Session域

@RequestMapping(value = "/vote08")
    public String test08(Master master,HttpSession session){
        session.setAttribute("master",master);
        session.setAttribute("address","guangzhou");
        return "vote_ok";
    }

@ModelAttribute

 @ModelAttribute
    public void prepareModel(){
        System.out.println("prepareModel()-----完成準備工作-----");
    }
  1. 在某個方法上加上 @ModelAttribute 註解,那麼在調用該Handler的任何方法都會調用這個方法
  2. 類似Aop的前置通知

視圖和視圖解析器

基本介紹

  1. 在springMVC 中的目標方法最終返回都是一個視圖(有各種視圖).
  2. 返回的視圖都會由一個視圖解析器來處理(視圖解析器有很多種)

自定義視圖

  1. 在預設情況下,我們都是返回預設的視圖, 然後這個返回的視圖交由SpringMVC 的 InternalResourceViewResolver 視圖處理器來處理的

​ 首碼 value="/WEB-INF/pages/" 和 尾碼 value=".jsp" 之後會拼接 返回給視圖解析器的返回值

<!--    配置預設視圖解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

    <!--    配置屬性  prefix 首碼  和  suffix 尾碼-->
    <!--    這裡的首碼 value="/WEB-INF/pages/"  和 尾碼 value=".jsp" 之後會拼接 返回給視圖解析器的返回值
            例如 UserSerlet return "login ok";
            就會拼接成 /WEB-INF/pages/login_ok.jsp  從而進行跳轉-->
        <property name="prefix" value="/WEB-INF/pages/"/>

        <property name="suffix" value=".jsp"/>
    </bean>
2. 在實際開發中,我們有時需要自定義視圖,這樣可以滿足更多更複雜的需求.

需要在spring配置文件 , 增加自定義視圖解析器

<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
	<property name="order" value="99"></property>
</bean>
  • name="order" :表示給這個解析器設置優先順序,
  • 我們自己的視圖解析優先順序高,Order 值越小,優先順序越高

編寫自己的視圖

  • 繼承 AbstractView 就可以作為一個視圖使用
  • @Component(value = "myView")會作為id= myView 的一個組件 註入到容器中
@Component(value = "zyView")
public class MyView extends AbstractView {
    @Override
    protected void renderMergedOutputModel(Map<String, Object> map,
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {

        //該方法完成視圖渲染
        //並且可以確定我們要跳轉的頁面 /WEB-INF/pages/my_view.jsp
        System.out.println("進入到自己的視圖..");

        //請求轉發到 /WEB-INF/pages/my_view.jsp
        //第一個斜杠會解析成 工程路徑-> springmvc/
        httpServletRequest.getRequestDispatcher("/WEB-INF/pages/my_view.jsp")
                .forward(httpServletRequest,httpServletResponse);
    }
}

進行跳轉

@RequestMapping("/goods")
@Controller
public class GoodsHandler {
    @RequestMapping(value = "/buy")
    public String buy() {
        System.out.println("--buy()被調用--");
       return "zyView";
    }
}

自定義視圖小結

  1. 自定義視圖: 創建一個View 的bean, 該bean 需要繼承自AbstractView, 並實現 renderMergedOutputModel 方法.

  2. 並把自定義View 加入到IOC 容器中

  3. 自定義視圖的視圖處理器,使用BeanNameViewResolver, 這個視圖處理器也需要配置到ioc 容器

  4. BeanNameViewResolver 的調用優先順序需要設置一下,設置order 比Integer.MAX_VAL 小的值. 以確保其在InternalResourceViewResolver 之前被調用

自定義視圖-工作流程

  1. SpringMVC 調用目標方法, 返回自定義View 在IOC 容器中的id

  2. SpringMVC 調用BeanNameViewResolver 解析視圖: 從IOC 容器中獲取返回id 值對應的bean, 即自定義的View 的對象

  3. SpringMVC 調用自定義視圖的renderMergedOutputModel 方法渲染視圖

  4. 如果在SpringMVC 調用目標方法, 返回自定義View 在IOC 容器中的id不存在, 則仍然按照預設的視圖處理器機制處理

自定義解析器的執行流程-源碼

/**
 * 自定義解析器的執行流程
 *  1.
 *    @RequestMapping(value = "/buy")
 *     public String buy() {
 *         System.out.println("--buy()被調用--");
 *        return "zyView";  -->
 *     }
 *  2.
 *  ApplicationContext context = obtainApplicationContext();
 * 		if (!context.containsBean(viewName)) {//判斷viewName是否在容器中
 * 			// Allow for ViewResolver chaining...
 * 			return null;
 *                }
 * 		if (!context.isTypeMatch(viewName, View.class)) {//判斷是否繼承了 AbstractView 實際上是判斷是否實現了View介面 因為AbstractView實現了View介面
 * 			if (logger.isDebugEnabled()) {
 * 				logger.debug("Found bean named '" + viewName + "' but it does not implement View");
 *            }
 * 			// Since we're looking into the general ApplicationContext here,
 * 			// let's accept this as a non-match and allow for chaining as well...
 * 			return null;
 *        }
 * 		return context.getBean(viewName, View.class);    -->
 * 	}
 * 	3.
 * 	 protected void renderMergedOutputModel(Map<String, Object> map,
 * HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
 *
 *         //該方法完成視圖渲染
 *         //並且可以確定我們要跳轉的頁面 /WEB-INF/pages/my_view.jsp
 *         System.out.println("進入到自己的視圖..");
 *
 *         //請求轉發到 /WEB-INF/pages/my_view.jsp
 *         //第一個斜杠會解析成 工程路徑-> springmvc/
 *         httpServletRequest.getRequestDispatcher("/WEB-INF/pages/my_view.jsp")
 *                 .forward(httpServletRequest,httpServletResponse); -->
 *     }
 * }
 * 	4.
 * 	<h1>進入到my_view頁面</h1>
 * <p>是從自定義視圖來的...</p>
 *
 *
 */

預設解析器的執行流程-源碼

/**
 * 預設解析器的執行流程
 * 1.
 * public String buy() {
 *         System.out.println("--buy()被調用--");
 *        return "zyView";->
 *     }
 * 2.
 * public InternalResourceViewResolver(String prefix, String suffix) {
 * 		this();
 * 		setPrefix(prefix);//用於拼接
 * 		setSuffix(suffix);//用於拼接
 *  }
 * 3.
 * protected AbstractUrlBasedView buildView(String viewName) throws Exception {
 * 		InternalResourceView view = (InternalResourceView) super.buildView(viewName);//調用super.buildView(viewName)
 * 		if (this.alwaysInclude != null) {
 * 			view.setAlwaysInclude(this.alwaysInclude);
 *                }
 * 		view.setPreventDispatchLoop(true);
 * 		return view;
 * }
 * 4.
 * 找不到 報錯404
 */

找不到自定義解析器 會調用預設解析器

/**
 * 找不到自定義解析器 會調用預設解析器
 * 1.
 * public String buy() {
 *         System.out.println("--buy()被調用--");
 *        return "zyView"; -->
 *     }
 * 2.
 *  ApplicationContext context = obtainApplicationContext();
 * 		if (!context.containsBean(viewName)) {//找不到返回null
 * 			// Allow for ViewResolver chaining...
 * 			return null;-->
 *     }
 * 3.
 *  if (this.viewResolvers != null) {
 * 			for (ViewResolver viewResolver : this.viewResolvers) { //遍歷解析器
 * 				View view = viewResolver.resolveViewName(viewName, locale);//此時以及走預設解析器那一套了
 * 				if (view != null) {
 * 					return view;//進行拼接首碼和尾碼 但找不到
 *            }
 *    }
 *  4.
 *  拼接後找不到 報錯 404
 */

預設解析器一旦解析 不會去自定義解析器

  • 因為預設解析器會拼接 view != null 就return了
	 * if (this.viewResolvers != null) {
     * 			for (ViewResolver viewResolver : this.viewResolvers) {
     * 				View view = viewResolver.resolveViewName(viewName, locale);
     * 				if (view != null) {
     * 					return view;
     *              }
     *   }

目標方法直接指定轉發或重定向

  • 預設返回的方式是請求轉發,然後用視圖處理器進行處理
 @RequestMapping(value = "/buy")
    public String buy(){
        return "success";
    }
  • 也可以在目標方法直接指定重定向或轉發的url 地址

請求轉發:return "forword:路徑"

return "forword:/WEB-INF/pages/my_view.jsp"

重定向:return "redirect:路徑"

return "redirect:login.jsp"

註意事項:

  1. 對於重定向,不能重定向到WEB-INF目錄下

    重定向在響應頭返回的URL中是 /工程路徑/login.jsp【此時,瀏覽器會將第一個 / 處理成 IP:埠/工程路徑/login.jsp】

  2. 對於請求轉發,是在伺服器內部進行,第一個 / 解析成 工程路徑,進行轉發

數據格式化

基本介紹

引出:在我們提交數據(比如表單時)SpringMVC 怎樣對提交的數據進行轉換和處理的??

  1. 基本數據類型可以和字元串之間自動完成轉換
  2. Spring MVC 上下文中內建了很多轉換器,可完成大多數Java 類型的轉換工作

基本數據類型和字元串自動轉換

<form:form action="save" method="post" modelAttribute="monster">
    妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
    妖怪年齡~: <form:input path="age"/> <form:errors path="age"/><br><br>
    電子郵件: <form:input path="email"/> <form:errors path="email"/><br><br>
    妖怪生日: <form:input path="birthday"/> <form:errors path="birthday"/>要求以"9999-11-11"的形式<br><br>
    妖怪薪水: <form:input path="salary"/> <form:errors path="salary"/>要求以"123,890.12"的形式<br><br>
    <input type="submit" value="添加妖怪"/>
</form:form>
  1. SpringMVC 表單標簽在顯示之前必須在request 中有一個bean, 該bean 的屬性和表單標簽的欄位要對應!
  2. request 中的key 為: form 標簽的modelAttribute 屬性值, 比如這裡的monsters
  3. SpringMVC 的form:form 標簽的action 屬性值中的/ 不代表WEB 應用的根目錄.
 @RequestMapping(value = "/addMonsterUI")
    public String addMonsterUI(Map<String, Object> map) {
        //這裡需要給request 增加一個monster , 因為jsp 頁面的modelAttribute="monster"需要
        //是springMVC 的內部的檢測機制即使是一個空的也需要,否則報錯
        map.put("monster", new Monster());
        return "datavalid/monster_addUI";
    }

說明:

  1. 當我們在瀏覽器發送 age=10 時,會把10轉換成String類型,到達後端後,又會把String轉成 int/Integer
  2. 而發送 age=aaa 時,會把aaa轉成 string類型,到達後端後,把String類型 的 aaa轉換成 int/Integer,此時會報錯

特殊數據類型和字元串間的轉換

  • 特殊數據類型和字元串之間的轉換使用註解(比如日期,規定格式的小數比如貨幣形式等)

  • 對於日期和貨幣可以使用@DateTimeFormat 和@NumberFormat 註解. 把這兩個註解標記在欄位上即可.(JavaBean上)

     @DateTimeFormat(pattern = "yyy-MM-dd")
        private Date birthday;
    

驗證國際化

  1. 對輸入的數據(比如表單數據),進行必要的驗證,並給出相應的提示信息
  2. 對於驗證表單數據,springMVC 提供了很多實用的註解, 這些註解由JSR 303 驗證框架提供.

JSR 303 驗證框架

  1. JSR 303 是Java 為Bean 數據合法性校驗提供的標準框架,它已經包含在JavaEE 中

  2. JSR 303 通過在Bean 屬性上標註類似於@NotNull、@Max 等標準的註解指定校驗規則,
    並通過標準的驗證介面對Bean 進行驗證

  3. JSR 303 提供的基本驗證註解有:

Hibernate Validator 擴展註解

  1. Hibernate Validator 和Hibernate 沒有關係,只是JSR 303 實現的一個擴展.

  2. Hibernate Validator 是JSR 303 的一個參考實現,除支持所有標準的校驗註解外,它還支
    持以下的擴展註解:

  3. 擴展註解有如下:

	@NotNull(message = "年齡必須1-100")
    @Range(min = 1,max = 100)
    private Integer age;

    @NotNull
    private String name ;
    @NotNull(message = "生日必須符合格式")
    @DateTimeFormat(pattern = "yyy-MM-dd")
    private Date birthday;
    @NotNull(message = "salary必須符合格式")
    @NumberFormat(pattern = "###,###.##")
    private Float salary;
	@RequestMapping(value = "/save")
    public String save(@Valid Monster monster, Errors errors, Map<String,Object> map) {}
  1. @Valid Monster monster:表示對monster 接收的數據進行校驗
  2. Errors errors: 表示如果校驗出現錯誤,將校驗的錯誤信息保存到 errors 中
  3. Map<String,Object> map:如果校驗出現錯誤,會將校驗的錯誤信息保存到map,並且同時保存 monster對象
  • 在前端使用<form:errors path="name"/> 回顯錯誤信息
 	妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
    妖怪年齡~: <form:input path="age"/> <form:errors path="age"/><br><br>
    電子郵件: <form:input path="email"/> <form:errors path="email"/><br><br>
    妖怪生日: <form:input path="birthday"/> <form:errors path="birthday"/>要求以"9999-11-11"的形式<br><br>
    妖怪薪水: <form:input path="salary"/> <form:errors path="salary"/>要求以"123,890.12"的形式<br><br>

自定義驗證錯誤信息

1.需要在Spring配置文件中配置相關bean

 <!-- 配置國際化錯誤信息的資源處理bean -->
    <bean id="messageSource" class=
            "org.springframework.context.support.ResourceBundleMessageSource">
        <!-- 配置國際化文件名字
        如果你這樣配的話,表示messageSource 回到src/i18nXXX.properties 去讀取錯誤信息
        -->
        <property name="basename" value="i18n"></property>
    </bean>

2.需要在src目錄下創建 i18nxxx.properties去讀取錯誤信息

NotEmpty.monster.name=\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
typeMismatch.monster.age=\u5e74\u9f84\u8981\u6c42\u5728\u0031\u002d\u0031\u0035\u0030\u4e4b\u95f4
typeMismatch.monster.birthday=\u751f\u65e5\u683c\u5f0f\u4e0d\u6b63\u786e
typeMismatch.monster.salary=\u85aa\u6c34\u683c\u5f0f\u4e0d\u6b63\u786e

3.用工具將希望自定義的錯誤信息轉換成Unicode編碼填入i18nxxx.properties

註意事項

  1. 在需要驗證的Javabean/POJO 的欄位上加上相應的驗證註解.

  2. 目標方法上,在JavaBean/POJO 類型的參數前, 添加@Valid 註解. 告知SpringMVC該bean 是需要驗證的

	@RequestMapping(value = "/save")
    public String save(@Valid Monster monster, Errors errors, Map<String,Object> map) {}
  1. 在@Valid 註解之後, 添加一個Errors 或BindingResult 類型的參數, 可以獲取到驗證的錯誤信息
  2. 需要使用<form:errors path="email"></form:errors> 標簽來顯示錯誤消息, 這個標簽,需要寫在form:form 標簽內生效.
<form:form action="save" method="post" modelAttribute="monster">
    妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
</form:form>
  1. 錯誤消息的國際化文件i18n.properties , 中文需要是Unicode 編碼,使用工具轉碼.
    格式: 驗證規則.表單modelAttribute 值.屬性名=消息信息

    NotEmpty.monster.name=\u540D\u5B57\u4E0D\u80FD\u4E3A\u7A7A
    typeMismatch.monster.age=\u7C7B\u578B\u4E0D\u5339\u914D

  2. 註解@NotNull 和@NotEmpty 的區別說明

  • @NotEmpty 是判斷 null 的 並且可以接收 任何類型

  • @NotNull 是判斷null 和 empty的,接收String,collection,map和array

  • 如果是字元串驗證空, 建議使用@NotEmpty

  1. SpingMVC 驗證時,會根據不同的驗證錯誤, 返回對應的信息

註解組合使用

  • 使用@NotNull + @Range 組合使用
 	@NotNull(message = "年齡必須1-100")
    @Range(min = 1,max = 100)
    private Integer age;

取消屬性綁定 @InitBinder 註解

  • 不希望接收到某個表單對應的屬性的值,則可以通過@InitBinder 註解取消綁定.
  1. 使用@InitBinder 標識的該方法,可以對WebDataBinder 對象進行初始化。
  2. WebDataBinder 是DataBinder 的子類,用於完成由表單欄位到JavaBean 屬性的綁定
  3. @InitBinder 方法不能有返回值,它必須聲明為void。
  4. @InitBinder 方法的參數通常是是WebDataBinder
	@InitBinder
    public void initBinder(WebDataBinder webDataBinder){
        webDataBinder.setDisallowedFields("name");//表示取消屬性name的綁定; 這裡可以填寫多個欄位
    }
  • 取消屬性的綁定,那麼在JavaBean中的校驗註解也應該去掉

中文亂碼問題處理

  • Spring提供的過濾器處理中文
<filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
</filter>
<filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
</filter-mapping>

處理JSON格式

@ResponseBody

  • 伺服器 -----JSON數據-----> 客戶端/瀏覽器
@RequestMapping(value = "/json/dog")
    @ResponseBody
    public Dog getJson(){
        Dog dog = new Dog("大黃", "貝加爾");
        return dog;
    }

@RequestBody

客戶端/瀏覽器-----JSON數據-----> 伺服器

@RequestMapping(value = "/save2")
@ResponseBody
public User save2(@RequestBody User user){
    System.out.println("user= "+user);
    return user;
}
  1. 將客戶端/瀏覽器發送的json字元串數據封裝成 JavaBean對象
  2. 再把這個 JavaBean對象 以 json對象形式返回
  3. (@RequestBody User user)在形參上指定
  4. SpringMVC就會將提交的json字元串數據填充給指定JavaBean

註意事項

  1. 目標方法正常返回json需要的數據,可以是對象也可以是集合
  2. @ResponseBody可以寫在類上,這樣對該類所有的方法生效
  3. @ResponseBody + @Controller 可以直接寫成@RestController

HttpMessageConverter

  • SpringMVC 處理JSON-底層實現是依靠HttpMessageConverter來進行轉換的

工作機制簡圖

  1. 使用HttpMessageConverter 將請求信息轉化並綁定到處理方法的入參中, 或將響應結果轉為對應類型的響應信息,Spring 提供了兩種途徑:

    • 使用@RequestBody / @ResponseBody 對目標方法進行標註

    • 使用HttpEntity / ResponseEntity 作為目標方法的入參或返回值

  2. 當控制器處理方法使用到@RequestBody/@ResponseBody 或HttpEntity/ResponseEntity 時, Spring 首先根據請求頭或響應頭的Accept 屬性選擇匹配的HttpMessageConverter, 進而根據參數類型或泛型類型的過濾得到匹配的HttpMessageConverter, 若找不到可用的HttpMessageConverter 將報錯

SpringMVC文件上傳

  • 在SpringMVC 中,通過返回ResponseEntity的類型,可以實現文件下載的功能
  • 需要構建 ResponseEntity 對象,需要1.得到http響應頭 2.http響應狀態 3.下載文件的數據
@RequestMapping(value="/downFile")
    public ResponseEntity<byte[]> downFile(HttpSession session) throws  Exception{
        //1.先獲取到要下載 的 InputStream
        InputStream resourceAsStream =
                session.getServletContext().getResourceAsStream("/img/1.jpg");

        //2.開闢存放文件的byte數組 -> 支持二進位數據
        //resourceAsStream.available() 返回資源文件的大小
        byte[] bytes = new byte[resourceAsStream.available() ];

        //3. 將要下載的文件數據讀入到byte數組
        resourceAsStream.read(bytes);

        /*
        ResponseEntity 的構造器:
            public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {
            this(body, headers, (Object) status);
            }
         */
        //準備構造ResponseEntity 對象
        //4.構建httpStatus
        HttpStatus httpStatus = HttpStatus.OK;
        //5.構建http響應頭
        HttpHeaders headers = new HttpHeaders();
        //指定瀏覽器以附件的形式處理回送數據
        headers.add("Content-Disposition","attachment;filename=1.jpg");
        ResponseEntity<byte[]> responseEntity =
                new ResponseEntity<>(bytes, headers, httpStatus);
        return responseEntity;
    }
  • 文件下載響應頭的設置

    content-type 指示響應內容的格式

    content-disposition 指示如何處理響應內容,一般有兩種方式:1. inline:直接在頁面顯示 2.attchment:以附件形式下載

SpringMVC文件上傳

基本介紹

  1. Spring MVC 為文件上傳提供了直接的支持,
  2. 這種支持是通過即插即用的MultipartResolver 實現的。
  3. Spring 用Jakarta Commons FileUpload 技術實現了一個MultipartResolver 實現類:CommonsMultipartResovler
  4. Spring MVC 上下文中預設沒有裝配MultipartResovler,因此預設情況下不能處理文件的上傳工作,
  5. 如果想使用Spring 的文件上傳功能,需現在上下文中配置MultipartResolver
 	<!--文件上傳-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    </bean>
  • 需要在form表單添加屬性 enctype="multipart/form-data"
<form action="<%=request.getContextPath()%>/fileUpload" method="post" enctype="multipart/form-data">
    文件介紹:<input type="text" name="introduce"><br>
    選擇文件:<input type="file" name="file"><br>
    <input type="submit" value="上傳文件">
</form>

文件上傳

//編寫方法 處理文件上傳的請求
    @RequestMapping(value = "/fileUpload")
    public String fileUpload(@RequestParam(value = "file") MultipartFile file,
                             HttpServletRequest request, String introduce) throws IOException {

        //接收到提交的文件名
        String originalFilename = file.getOriginalFilename();
        System.out.println("提交的文件名= " + originalFilename);
        System.out.println("文件介紹= " + introduce);
        //獲取到文件保存的目標位置/路徑
        String filePath =
                request.getServletContext().getRealPath("/img/" + originalFilename);

        //創建文件
        File saveToFile = new File(filePath);

        //將上傳的文件轉存到 saveToFile
        file.transferTo(saveToFile);

        return "success";

    }

攔截器

基本介紹

  1. Spring MVC 也可以使用攔截器對請求進行攔截處理,
  2. 用戶可以自定義攔截器來實現特定的功能.
  3. 自定義的攔截器必須實現HandlerInterceptor 介面

自定義攔截器的三個方法

  1. preHandle():這個方法在業務處理器處理請求之前被調用,在該方法中對用戶請求request 進行處理。

  2. postHandle():這個方法在目標方法處理完請求後執行

  3. afterCompletion():這個方法在完全處理完請求後被調用,可以在該方法中進行一些資源
    清理的操作。

在Spring配置文件中配置攔截器

預設配置是都所有的目標方法都進行攔截, 也可以指定攔截目標方法, 比如只是攔截hi

 <mvc:interceptors>
        <ref bean="myInterceptor01"/>//直接引用對應攔截器
        <mvc:interceptor>
            <mvc:mapping path="/hi"/>
            <ref bean="myInterceptor01"/>
        </mvc:interceptor>
</mvc:interceptor>

mvc:mapping 支持通配符, 同時指定不對哪些目標方法進行攔截

 <mvc:interceptor>
            <mvc:mapping path="/h*"/>
            <mvc:exclude-mapping path="/hello"/>
            <ref bean="myInterceptor01"/>
        </mvc:interceptor>
  • 這樣配置會攔截h打頭的url指定的方法
<mvc:interceptor>
    <mvc:mapping path="/h*"/>
    <mvc:exclude-mapping path="/hello"/>
    <ref bean="myInterceptor02"/>
</mvc:interceptor>
  • 這樣配置會攔截h打頭的url指定的方法並且排除 url是hello的方法

自定義攔截器執行流程分析圖

● 自定義攔截器執行流程說明

  1. 如果preHandle 方法返回false, 則不再執行目標方法, 可以在此指定返回頁面
  2. postHandle 在目標方法被執行後執行. 可以在方法中訪問到目標方法返回的 ModelAndView 對象
  3. 若preHandle 返回true, 則afterCompletion 方法在渲染視圖之後被執行.
  4. 若preHandle 返回false, 則afterCompletion 方法不會被調用
  5. 在配置攔截器時,可以指定該攔截器對哪些請求生效,哪些請求不生效

註意事項

  1. 攔截器需要配置才生效,不配置是不生效的.
  2. 如果preHandler() 方法返回了false, 就不會執行目標方法(前提是你的目標方法被攔截了), 程式員可以在這裡根據業務需要指定跳轉頁面.

多個攔截器

註意事項

  1. 如果第1 個攔截器的preHandle() 返回false , 後面都不在執行

  2. 如果第2 個攔截器的preHandle() 返回false , 就直接執行第1 個攔截器的
    afterCompletion()方法, 如果攔截器更多,規則類似

  3. 前面說的規則,都是目標方法被攔截的前提

異常處理

基本介紹

  1. Spring MVC 通過HandlerExceptionResolver 處理程式的異常,包括Handler 映射、數據綁定以及目標方法執行時發生的異常。

  2. 主要處理Handler 中用@ExceptionHandler 註解定義的方法。

  3. ExceptionHandlerMethodResolver 內部若找不到@ExceptionHandler 註解的話, 會找@ControllerAdvice 類的@ExceptionHandler 註解方法, 這樣就相當於一個全局異常處理器

  4. 如果不去處理異常,tomcat會預設機制處理,用戶看到的頁面非常不友好

異常處理的優先順序

局部異常 > 全局異常 > SimpleMappingExceptionResolver > tomcat預設機制

局部異常

//局部異常就是直接在這個Handler 中編寫即可
    @ExceptionHandler({ArithmeticException.class, NullPointerException.class})
    public String localException(Exception ex,HttpServletRequest request){
        System.out.println("異常信息是~" + ex.getMessage());
        //如何將異常的信息帶到下一個頁面.
        request.setAttribute("reason", ex.getMessage());
        return "exception_mes";
    }

全局異常

@ControllerAdvice//@ControllerAdvice 表示了該註解,就是全局異常
public class MyGlobalException {

    @ExceptionHandler({NumberFormatException.class, ClassCastException.class})
    public String globalException(Exception ex, HttpServletRequest request){
        System.out.println("全局異常處理---"+ex.getMessage());
        request.setAttribute("reason",ex.getMessage());
        return "exception_mes";
    }
  • @ControllerAdvice 表示了該註解,就是全局異常

自定義異常

  • 通過@ResponseStatus 註解, 可以自定義異常
  • 格式:@ResponseStatus(reason = "異常原因",value = httpStatus狀態 )
@ResponseStatus(reason = "年齡需要在1-120之間",value = HttpStatus.BAD_REQUEST )
public class AgeException extends RuntimeException{//需要繼承RuntimeException/Exception
}
  • 自定義異常類需要繼承RuntimeException/Exception
  • httpStatus會有很多狀態

  • 如果想在其他頁面看到reason信息,加上帶String構造器即可

    public AgeException(String message) {
        super(message);
    }
    

統一處理異常信息

基本介紹

  1. 如果希望對所有異常進行統一處理,可以使用SimpleMappingExceptionResolver

  2. 它將異常類名映射為視圖名,即發生異常時使用對應的視圖報告異常

  3. 需要在ioc 容器中配置

<!--    統一處理異常-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.Exception">allEx</prop>
        </props>
    </property>
</bean>
  • 在這個標簽內 就可以配置出現異常需要跳轉的頁面allEx
  • key="java.lang.Exception" 是異常的範圍,這樣設置可以對未知異常進行統一處理,也就是所有異常都處理

SpringMVC執行流程以及源碼剖析

SpringMVC執行流程示意圖

執行流程-源碼剖析

  1. 發出請求url

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
       logRequest(request);
    
  2. 調用處理器映射器

    //getWebApplicationContext() 就是Spring容器
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    //走完這一步前端控制器就有了Spring容器
    
    doDispatch(request, response);//進入分發
        2.1 HandlerExecutionChain mappedHandler = null;//有屬性 處理器鏈
        2.2 mappedHandler = getHandler(processedRequest);// mappedHandler中已經有 目標Handler 和 攔截器鏈
    
  3. 調用處理器適配器

    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//得到處理器適配器
    String method = request.getMethod();得到請求方式
    
  4. 調用Handler

    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//調用Handler
    
    mav = invokeHandlerMethod(request, response, handlerMethod);
    
    invocableMethod.invokeAndHandle(webRequest, mavContainer);//調用Handler中的目標方法了
    
    ModelAndView modelAndView = new ModelAndView();//到達目標方法
    
    return invoke0(this.method, var1, var2);//目標方法執行完畢返回
    
    return getModelAndView(mavContainer, modelFactory, webRequest);
    
    return mav;//返回給前端處理器
    
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//回到起點,返回了一個 ModelAndView
    
  5. 調用視圖解析器

    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
    render(mv, request, response);//進行渲染
        //進入render 方法
            String viewName = mv.getViewName();//拿到視圖名稱
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);//進行視圖解析
    
            //調用視圖解析器
            if (this.viewResolvers != null) {
          for (ViewResolver viewResolver : this.viewResolvers) {
             View view = viewResolver.resolveViewName(viewName, locale);//解析完畢返回視圖
             if (view != null) {
                return view;//返回視圖給前端控制器
             }
         }
       }
    
    
    
  6. 視圖渲染

    view.render(mv.getModelInternal(), request, response);
    
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);//渲染合併 輸出模型
        //進入方法
            RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);//獲得RequestDispatcher
    
  7. 返迴響應

    rd.forward(request, response);//返迴響應
    

手寫SpringMVC底層機制!

  • 前景提要:實現的是SpringMVC核心機制

  • 對一些細枝末節的代碼做了簡化,比如字元串的處理...

  • 完成哪些機制

    • 機制一: 通過@RequestMapping ,可以標記一個方法,編寫路徑url,瀏覽器就能通過url完成調用
    • 機制二: 進行依賴註入,使之不需要傳統的new 一個對象,而是直接從IOC容器中獲得
    • 機制三:通過@RequestParam,如果瀏覽器傳遞的參數名和目標方法的形參不一致,可以通過value設置進行匹配
    • 機制四:在目標方法完成後,跳轉到相關頁面 請求轉發/重定向
    • 機制五:在目標方法完成後,通過@Response註解,向瀏覽器發送JSON格式數據

手寫添加配置

思路

  1. 需要配置pom.xml的依賴
  2. 需要寫一個Servlet 作為前端控制器
  3. 需要配置Web.xml 中的前端控制器 1).url 2)配置spring容器配置文件的classpath: 3)跟隨Tomcat 自啟動
  4. 需要配置spring容器配置文件
  5. 需要配置spring容器配置文件 掃描的路徑 <component-scan ...>

實現

  • 需要配置pom.xml的依賴
<!--  配置原生Servlet-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    <!--  <scope> 表示引入的jar的作用範圍
          provided 表示該項目在打包 放到生產環境時,不需要帶上 servlet-api.jar
          因為tomcat本身有 servlet-api.jar,到時直接使用tomcat本身的 servlet-api.jar-->
    </dependency>

  <!--  配置dom4j-->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.1</version>
    </dependency>

  <!--  配置常用的工具類-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.12.0</version>
    </dependency>
  • 需要寫一個Servlet 作為前端控制器
public class ZyDispatcherServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ZyDispatcherServlet-doPost--");
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ZyDispatcherServlet-doGet--");
    }
}
  • 需要配置Web.xml 中的前端控制器 1).url 2)配置spring容器配置文件的classpath: 3)跟隨Tomcat 自啟動

  • 需要配置spring容器配置文件 掃描的路徑 <component-scan ...>

 <servlet>
    <servlet-name>ZyDispatcherServlet</servlet-name>
    <servlet-class>com.code_study.zyspringmvc.servlet.ZyDispatcherServlet</servlet-class>
    
    <!--配置參數,指定要操作的spring配置文件-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:zyspringmvc.xml</param-value>
    </init-param>

    <!--跟隨tomcat自啟動-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>ZyDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  • 需要配置spring容器配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <!--    要掃描的包-->
    <component-scan base-package="com.code_study.controller,com.code_study.service"></component-scan>
</beans>

完成瀏覽器可以請求控制層

思路

  • 創建@Controller和自己的Controller
  • 編寫工具類XMLParser,解析spring容器配置文件
  • 開發自己的 Spring容器,得到掃描類的全路徑列表
  • 開發自己的 前端控制器,實例化對象到容器中
  • 完成請求的URL和控制器方法的映射關係
  • 完成前端控制器分發請求到對應控制器
  1. 自定義註解@Controller
  2. 創建Controller
  3. 需要寫一個工具類XMLParser來解析在spring容器配置文件 掃描的路徑 <component-scan ...> 的包 返回所有的路徑
  4. 這個所有的路徑 一個split(",")分隔,都進行掃描
  5. 需要寫自己的 前端控制器
  6. 需要寫自己的 Spring容器
  7. 在前端控制器中 需要添加方法 scanPackage() 掃描 XMLParser 解析出來的路徑
  8. 在Spring容器中 需要添加一個屬性 classFullPathList 來保存掃描出來的類的全路徑
  9. 需要添加一個屬性 ioc 來存放反射生成的bean對象 也就是過濾classFullPathList 中沒有@Controller註解的一些路徑 並實例化
  10. 需要添加類Handler 這個類要保存 一個url 對應的 一個控制器的方法的 映射 ,也就是說,根據這個url,可以找到對應控制器的對應方法
  11. 需要添加一個屬性 HandlerList 用於 保存Handler 【url 和 控制器的映射】
  12. 需要添加三個方法 一個是initHandlerMapping(),完成 url 對應的 一個控制器的方法的 映射,即 將ioc 中bean 中的 方法進行反射,獲取url,將 url,method,bean 封裝成Handler 放入HandlerList 保存
  13. 添加第二個方法 getHandler(),需要將瀏覽器發送的request請求中的 uri拿出來,遍歷HandlerList 進行配對,如果有 就返回對應的Handler
  14. 添加第三個方法 executeDispatch(),進行分發處理,需要 調用getHandler() 獲取瀏覽器發送的request請求 對應的 Handler ,獲取Handler 中的method 進行反射調用,method .invoke() 實現分發請求。

實現

  1. 自定義註解@Controller
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String value() default "";
}
  1. 創建Controller
@Controller
public class MonsterController {
}
  1. 需要寫一個工具類XMLParser來解析在spring容器配置文件 掃描的路徑 <component-scan ...> 的包 返回所有的路徑
public class XMLParser {

    public static String getBasePackage(String xmlFile){
        SAXReader saxReader = new SAXReader();
        ClassLoader classLoader = XMLParser.class.getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream(xmlFile);
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            Element element = rootElement.element("component-scan");
            String basePackage = element.attribute("base-package").getText();
            return basePackage;
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
    }
}
  1. 這個所有的路徑 一個split(",")分隔,都進行掃描
  2. 需要寫自己的 前端控制器
  3. 需要寫自己的 Spring容器
  4. 在前端控制器中 需要添加方法 scanPackage() 掃描 XMLParser 解析出來的路徑
public void scanPackage(String pack) {
        //獲得包所在的工作路徑 [絕對路徑]
        URL url =
                this.getClass().getClassLoader().//獲取類的載入器
                        //得到指定包對應的工作路徑 [絕對路徑]
                                getResource("/" + pack.replaceAll("\\.", "/"));

        // System.out.println("url= "+url);
        //根據得到的路徑,對其進行掃描,把類的全路徑 保存到 classFullPathList
        String path = url.getFile();
        //在io中 把目錄也是為一個文件
        File file = new File(path);
        //遍歷file 【遍歷出文件和子目錄】
        for (File f : file.listFiles()) {
            if (f.isDirectory()) {//如果是目錄
                //需要遞歸掃描 找子目錄
                scanPackage(pack + "." + f.getName());
            } else {
                //的確是個文件
                //掃描到的文件可能是 .class 文件 也可能是其他文件
                //就算是.class 文件 也需要判斷是否需要註入容器 有無加 @Controller註解
                //目前無法拿到註解 因為沒法反射 所以先把文件的全路徑都保存到 classFullPathList 之後在註入對象到容器時再處理
                String classFullPath =
                        //類的全路徑不需要.class 去掉.class
                        pack + "." + f.getName().replaceAll(".class", "");

                //保存到 classFullPathList
                classFullPathList.add(classFullPath);
            }
        }
    }
  1. 在Spring容器中 需要添加一個屬性 classFullPathList 來保存掃描出來的類的全路徑
//保存掃描的包/子包類的全路徑
    private List<String> classFullPathList =
            new ArrayList<>();
  1. 需要添加一個屬性 ioc 來存放反射生成的bean對象 也就是過濾classFullPathList 中沒有@Controller註解的一些路徑 並實例化
//定義屬性 ioc -> 存放反射生成的bean對象 比如Controller / Service /Dao
public ConcurrentHashMap<String, Object> ioc =
        new ConcurrentHashMap<>();
  1. 編寫方法,將掃描到的類,在滿足情況下 反射到ioc容器
//編寫方法,將掃描到的類,在滿足情況下 反射到ioc容器
    public void executeInstance() {
        if (classFullPathList.size() == 0) {
            //說明沒有掃描到類
            return;
        }

        //遍歷classFullList
        for (String classFullPath : classFullPathList) {
            try {
                Class<?> clazz = Class.forName(classFullPath);
                if (clazz.isAnnotationPresent(Controller.class)) {//處理@Controller
                    String className = clazz.getSimpleName();

                    Object instance = clazz.newInstance();
                    String value = clazz.getAnnotation(Controller.class).value();
                    if (!"".equals(value)) {
                        className = value;
                    } else {
                        className = StringUtils.uncapitalize(className);
                    }
                    ioc.put(className, instance);
                }
                else if (clazz.isAnnotationPresent(Service.class)) {//處理@Service
                    String className = clazz.getSimpleName();//類名

                    Service serviceAnnotation = clazz.getAnnotation(Service.class);
                    String value = serviceAnnotation.value();

                    if (!"".equals(value)) {
                        className = value;
                        Object instance = clazz.newInstance();
                        ioc.put(className, instance);
				}
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
  1. 需要添加類Handler 這個類要保存 一個url 對應的 一個控制器的方法的 映射 ,也就是說,根據這個url,可以找到對應控制器的對應方法
ZyHandler {
    private String url;
    private Method method;
    private Object controller;

    public ZyHandler() {
    }

    public ZyHandler(String url, Method method, Object controller) {
        this.url = url;
        this.method = method;
        this.controller = controller;
    }
//需要提供getter和setter方法...
  1. 需要添加一個屬性 HandlerList 用於
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 重載(Overloading):所謂重載是指不同的函數實體共用一個函數名稱。例如以下代碼所提到的CPoint之中,有兩個member functions的名稱同為x(): 1 class CPoint{ 2 3 public: 4 float x(); 5 void x(float xval); 6 ...
  • 實驗要求一:對比分析 對比分析墨刀、Axure、Mockplus等原型設計工具的各自的適用領域及優缺點。 一丶墨刀 墨刀是一款線上的產品設計協作軟體,可以解決產設研團隊中存在的項目管理許可權不明、版本管理混亂、協作低效等諸多問題。 優點: 功能強大:可滿足產品經理、設計師、開發在產品設計和團隊協作上的 ...
  • title: 文本語音互相轉換系統設計 date: 2024/4/24 21:26:15 updated: 2024/4/24 21:26:15 tags: 需求分析 模塊化設計 性能優化 系統安全 智能化 跨平臺 區塊鏈 第一部分:導論 第一章:背景與意義 文本語音互相轉換系統的定義與作用 文本語 ...
  • 參考:https://www.cnblogs.com/mc-74120/p/13622008.html pom文件 <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency> 啟動 ...
  • 新手下載python和anaconda3要註意哪些 1、python 關於python下載其實很簡單,直接在官網下載就行。 官網:Welcome to Python.org 當然,到了官網下載是預設最新版本,如果你需要舊版本,那就需要找一下了,這裡提供一下windows的各版本的官網鏈接: Pyth ...
  • 來源:https://www.cnblogs.com/405845829qq/p/7552736.html 前言 公司最近在搞服務分離,數據切分方面的東西,因為單張包裹表的數據量實在是太大,並且還在以每天60W的量增長。 之前瞭解過資料庫的分庫分表,讀過幾篇博文,但就只知道個模糊概念, 而且現在回想 ...
  • 大家好,我是 Java陳序員。 我們無論是日常生活還是辦公,常常需要使用一些工具軟體來記錄筆記、代辦事項等。 今天,給大家介紹一款支持私有化部署、支持多端使用的筆記軟體。 關註微信公眾號:【Java陳序員】,獲取開源項目分享、AI副業分享、超200本經典電腦電子書籍等。 項目介紹 Blossom ...
  • 手寫 SpringMVC 底層機制 前景提要:實現的是SpringMVC核心機制 對一些細枝末節的代碼做了簡化,比如字元串的處理... 完成哪些機制 機制一: 通過@RequestMapping ,可以標記一個方法,編寫路徑url,瀏覽器就能通過url完成調用 機制二: 進行依賴註入,使之不需要傳統 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:本文代碼示例演示瞭如何在WPF中使用LiveCharts庫創建動態條形圖。通過創建數據模型、ViewModel和在XAML中使用`CartesianChart`控制項,你可以輕鬆實現圖表的數據綁定和動態更新。我將通過清晰的步驟指南包括詳細的中文註釋,幫助你快速理解並應用這一功能。 先上效果: 在 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • 概述:本示例演示了在WPF應用程式中實現多語言支持的詳細步驟。通過資源字典和數據綁定,以及使用語言管理器類,應用程式能夠在運行時動態切換語言。這種方法使得多語言支持更加靈活,便於維護,同時提供清晰的代碼結構。 在WPF中實現多語言的一種常見方法是使用資源字典和數據綁定。以下是一個詳細的步驟和示例源代 ...
  • 描述(做一個簡單的記錄): 事件(event)的本質是一個委托;(聲明一個事件: public event TestDelegate eventTest;) 委托(delegate)可以理解為一個符合某種簽名的方法類型;比如:TestDelegate委托的返回數據類型為string,參數為 int和 ...
  • 1、AOT適合場景 Aot適合工具類型的項目使用,優點禁止反編 ,第一次啟動快,業務型項目或者反射多的項目不適合用AOT AOT更新記錄: 實實在在經過實踐的AOT ORM 5.1.4.117 +支持AOT 5.1.4.123 +支持CodeFirst和非同步方法 5.1.4.129-preview1 ...
  • 總說周知,UWP 是運行在沙盒裡面的,所有許可權都有嚴格限制,和沙盒外交互也需要特殊的通道,所以從根本杜絕了 UWP 毒瘤的存在。但是實際上 UWP 只是一個應用模型,本身是沒有什麼許可權管理的,許可權管理全靠 App Container 沙盒控制,如果我們脫離了這個沙盒,UWP 就會放飛自我了。那麼有沒... ...
  • 目錄條款17:讓介面容易被正確使用,不易被誤用(Make interfaces easy to use correctly and hard to use incorrectly)限制類型和值規定能做和不能做的事提供行為一致的介面條款19:設計class猶如設計type(Treat class de ...
  • title: 從零開始:Django項目的創建與配置指南 date: 2024/5/2 18:29:33 updated: 2024/5/2 18:29:33 categories: 後端開發 tags: Django WebDev Python ORM Security Deployment Op ...
  • 1、BOM對象 BOM:Broswer object model,即瀏覽器提供我們開發者在javascript用於操作瀏覽器的對象。 1.1、window對象 視窗方法 // BOM Browser object model 瀏覽器對象模型 // js中最大的一個對象.整個瀏覽器視窗出現的所有東西都 ...