這里續(xù)寫上一章博客(110章博客):
現(xiàn)在我們來學習一下高級的技術(shù),前面的mvc知識,我們基本可以在67章博客及其后面相關(guān)的博客可以學習到,現(xiàn)在開始學習精髓:
Spring MVC 高級技術(shù):
攔截器(Inteceptor)使用:
監(jiān)聽器、過濾器和攔截器對?(前面兩個在53章博客可以學習到,后面只是名稱上的解釋,如果可以,那么前面兩個也可以說成是攔截器,所以存在很多框架的攔截器):
Servlet:處理Request請求和Response響應
過濾器(Filter):對Request請求起到過濾的作用,作用在Servlet之前,如果配置為/*可以對所 有的資源訪問(servlet、js/css靜態(tài)資源等)進行過濾處理
監(jiān)聽器(Listener):實現(xiàn)了javax.servlet.ServletContextListener 接?的服務器端組件,它隨Web應用的啟動?啟動,只初始化一次,然后會一直運行監(jiān)視,隨Web應用的停??銷毀
監(jiān)聽器的作用與過濾器雖然都有攔截的意思,但是偏重不同
監(jiān)聽器可以選擇對一些數(shù)據(jù)或者說數(shù)據(jù)變化進行監(jiān)聽以及攔截,而過濾則是對過來的請求直接攔截,而不是更加里面的數(shù)據(jù)攔截,所以一般情況下,過濾器通常是在監(jiān)聽器之前進行攔截的
那么說監(jiān)聽器一般有如下的作用:
作用一:做一些初始化工作,web應用中spring容器啟動ContextLoaderListener
作用?:監(jiān)聽web中的特定事件,?如HttpSession,ServletRequest的創(chuàng)建和銷毀,變量的創(chuàng)建、 銷毀和修改等,可以在某些動作前后增加處理,實現(xiàn)監(jiān)控,?如統(tǒng)計在線?數(shù),利?HttpSessionLisener等
攔截器(Interceptor):是SpringMVC、Struts等表現(xiàn)層框架自己的,不會攔截jsp/html/css/image的訪問等,只會攔截訪問的控制器方法(Handler),一般來說,這個攔截在一定程度上使用了過濾器以及監(jiān)聽器,因為需要確定攔截的數(shù)據(jù),通常需要先獲得,所以mvc的攔截器的實現(xiàn)方式通常在于過濾器或者說監(jiān)聽器(注意,只是因為他需要對應的數(shù)據(jù),所以他才會在于其他的器,如過濾和監(jiān)聽,否則攔截器一般只是攔截指定數(shù)據(jù)的處理,而不是在于什么)
根據(jù)上面的說明,其實從配置的?度也能夠總結(jié)發(fā)現(xiàn):serlvet、?lter、listener是配置在web.xml中的,?interceptor是 配置在表現(xiàn)層框架自己的配置?件中的,所以Interceptor一般是框架自己的
根據(jù)前面的說明,可以知道如下:
攔截器會在如下的情況可能會發(fā)生攔截:
Handler業(yè)務邏輯執(zhí)行之前攔截一次(操作url)
在Handler邏輯執(zhí)行完畢但未跳轉(zhuǎn)??之前攔截一次(操作轉(zhuǎn)發(fā),如果沒有,那么一般操作響應數(shù)據(jù),如果也沒有,那么雖然攔截,但并未做什么)
在跳轉(zhuǎn)??之后攔截一次(比如對應的json的處理,或者并未處理)
前面我們知道了這個圖:
以及他的說明:
流程說明:
第一步:用戶發(fā)送請求?前端控制器DispatcherServlet
第?步:DispatcherServlet收到請求調(diào)?HandlerMapping處理器映射器(一般是map保存的)
第三步:處理器映射器根據(jù)請求Url找到具體的Handler(后端控制器,可以根據(jù)xml配置、注解進行查找,因為查找,所以是映射),?成處理器對象及處理器攔截器(如果有則?成)一并返回DispatcherServlet,他負責創(chuàng)建
第四步:DispatcherServlet調(diào)?HandlerAdapter處理器適配器去調(diào)?Handler
第五步:處理器適配器執(zhí)?Handler(controller的方法,生成對象了,這里相當于調(diào)用前面的handle01方法,他負責調(diào)用)
第六步:Handler執(zhí)行完成給處理器適配器返回ModelAndView,即處理器適配器得到返回的ModelAndView,這也是為什么前面我們操作方法時,是可以直接操作他并返回的,而返回給的人就是處理器適配器,就算你不返回,那么處理器適配器或者在之前,即他們兩個中間,可能會進行其他的處理,來設置ModelAndView,并給處理器適配器
第七步:處理器適配器向前端控制器返回 ModelAndView(因為適配或者返回數(shù)據(jù),所以是適配),ModelAndView 是SpringMVC 框架的一個 底層對 象,包括 Model 和 View
第?步:前端控制器請求視圖解析器去進行視圖解析,根據(jù)邏輯視圖名來解析真正的視圖(加上前后的補充,即前面的配置視圖解析器)
第九步:視圖解析器向前端控制器返回View
第?步:前端控制器進行視圖渲染,就是將模型數(shù)據(jù)(在 ModelAndView 對象中)填充到 request 域,改變了servlet,最終操作servlet來進行返回
第?一步:前端控制器向用戶響應結(jié)果(jsp的)
即可以理解:請求找路徑并返回(1,2,3),給路徑讓其判斷路徑并返回且獲得對應對象(4,5,6,7),變成參數(shù)解析(如拼接) 進行轉(zhuǎn)發(fā)(8,9),然后到jsp(10),最后渲染(11)
所以說:
第一次攔截:在1和5中間處理
第二次攔截:在6到8中間處理
第三次攔截:在9到11中間處理
其實通過圖片,我們應該知道,第一次攔截應該是在3和4中在前端控制器旁邊處理,而7和8就是第二次攔截,10到11則是第三次攔截(這里也可能是9到10)
所以可以知道,其實mvc自帶的有一些攔截,這也是對應注解,比如@RequestBody或者@ResponseBody可以操作的原因
當然,我們也可以進行添加攔截,這在后面會說明的
為了更加的知道攔截器的處理,我們直接來進行實戰(zhàn):
注意:mvc的攔截器是里面的,也就是說,servlet原本的過濾器必然先處理或者后處理
實際上我們學習源碼很大程度是必須要有實戰(zhàn)的,因為一個框架的源碼我們基本是不可能全部讀完的,這取決于一個框架是由很長時間的迭代,以及很多人一起開發(fā)完成的,當然,如果你的框架夠小,那么可以是單獨完成,在這種情況下,學習框架中,用閱讀源碼來學習,我們只能知道他的一點實現(xiàn)方式,所以學習框架通常需要直接的實戰(zhàn)來進行學習,來直接的確定他的作用,而不是單獨看源碼來確定作用(你怎么知道他有沒有其他關(guān)聯(lián),并且要知道這個關(guān)聯(lián)需要看更多的源碼),也就是說,實際上源碼的解析大多數(shù)是讓你知道他的實現(xiàn)方式,而不是具體細節(jié)(比如,他為什么這樣定義變量等等),當然,除了實現(xiàn)方式有時候也需要學習設計模式,這個在以后會單獨給一個博客來進行處理的,先了解一些框架的設計模式再說
那么,既然要實戰(zhàn),我們首先需要操作一個項目,項目如下:
對應的依賴,在前面我們已經(jīng)給過多次了,這里我們繼續(xù)給出吧
< packaging> war</ packaging> < dependencies> < dependency> < groupId> org.springframework</ groupId> < artifactId> spring-webmvc</ artifactId> < version> 5.1.5.RELEASE</ version> </ dependency> < dependency> < groupId> javax.servlet</ groupId> < artifactId> javax.servlet-api</ artifactId> < version> 3.1.0</ version> </ dependency> < dependency> < groupId> com.fasterxml.jackson.core</ groupId> < artifactId> jackson-databind</ artifactId> < version> 2.9.8</ version> </ dependency> < dependency> < groupId> com.fasterxml.jackson.core</ groupId> < artifactId> jackson-core</ artifactId> < version> 2.9.8</ version> </ dependency> < dependency> < groupId> com.fasterxml.jackson.core</ groupId> < artifactId> jackson-annotations</ artifactId> < version> 2.9.0</ version> </ dependency> </ dependencies>
如果需要補充,自行補充吧
對應的web.xml:
<?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" > < servlet> < servlet-name> dispatcherServlet</ servlet-name> < servlet-class> org.springframework.web.servlet.DispatcherServlet</ servlet-class> < init-param> < param-name> contextConfigLocation</ param-name> < param-value> classpath:springmvc.xml</ param-value> </ init-param> </ servlet> < servlet-mapping> < servlet-name> dispatcherServlet</ servlet-name> < url-pattern> /</ url-pattern> </ servlet-mapping> </ web-app>
index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<button id="btn">ajax提交</button>
<script>$("#btn").click(function () {let url = 'test/in';let data = '[{"id":1,"username":"張三"},{"id":2,"username":"李四"}]';$.ajax({type: 'POST',//大小寫可以忽略url: url,data: data,contentType: 'application/json;charset=utf-8',success: function (data) {console.log(data);alert(data)}})})
</script>
</body>
</html>
springmvc.xml:
< beans xmlns = " http://www.springframework.org/schema/beans" xmlns: mvc= " http://www.springframework.org/schema/mvc" xmlns: context= " http://www.springframework.org/schema/context" xmlns: xsi= " http://www.w3.org/2001/XMLSchema-instance" xsi: schemaLocation= " http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd" > < context: component-scan base-package = " com.controller" /> < mvc: annotation-driven> </ mvc: annotation-driven>
</ beans>
entity里面的User類:
package com. entity ; public class User { String id; String username; public String getId ( ) { return id; } public void setId ( String id) { this . id = id; } public String getUsername ( ) { return username; } public void setUsername ( String username) { this . username = username; } @Override public String toString ( ) { return "User{" + "id='" + id + '\'' + ", username='" + username + '\'' + '}' ; }
}
test類:
package com. controller ; import com. entity. User ;
import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. RequestBody ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. ResponseBody ; import java. util. List ; @Controller
@RequestMapping ( "/test" )
public class test { @RequestMapping ( "in" ) @ResponseBody public List < User > ajax ( @RequestBody List < User > list) { System . out. println ( list) ; return list; }
}
自行配置tomcat,啟動,運行看看結(jié)果,那么我們基礎操作搭建完畢,現(xiàn)在我們來操作一下攔截:
在com包下創(chuàng)建Interceptor包,然后創(chuàng)建MyInterceptor類:
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ; public class MyInterceptor implements HandlerInterceptor {
}
其中HandlerInterceptor接口如下:
package org. springframework. web. servlet ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import org. springframework. lang. Nullable ; public interface HandlerInterceptor { default boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true ; } default void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }
}
這三個代表了三個地方,前面的三個攔截,比如:
preHandle是第一個攔截,postHandle是第二個攔截,afterCompletion是第三個攔截,實際上一個框架的編寫,除了給出一些功能外,還需要存在擴展功能,有的話最好,而攔截的處理基本就是框架基本的擴展了(所以在spring中也存在多個攔截的處理,包括mybatis,當然,他們可能并沒有特別的說明是攔截,但是你也或多或少可以知道,可以操作一些類來實現(xiàn)在中間進行處理的方式,這其實也算是一種攔截,因為是我們自行處理的,所以攔截器在某些方面可以是這樣的認為:框架自身給出可以擴展的方式都可以稱為攔截器)
實際上通過前面我們也明白,前端控制器底層基本上也是操作get和post,而servlet也是,但是mvc是建立在servlet上的,所以前端控制器通常也是生成了servlet,在前面我們學習了,前端控制器只是生成一個servlet(一般也可以是他自己),其中只是操作了攔截進行的處理,這個攔截或多或少使用了過濾或者監(jiān)聽,所以說,具體的是否監(jiān)聽或者過濾的處理,可能也是對應的配置導致進行的某些配置再處理(因為你刪除了配置前端控制器的,攔截也就不復存在了),那么在這種情況下,我們的三次攔截,也只是其中多個攔截的擴展,那么如果這個時候,你操作了傳統(tǒng)的攔截,那么就需要看配置的先后順序
我們繼續(xù)修改MyInterceptor類的內(nèi)容:
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System . out. println ( handler) ; System . out. println ( "Handler業(yè)務邏輯執(zhí)行之前攔截一次,我是第一次" ) ; return true ; } @Override public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System . out. println ( handler) ; System . out. println ( modelAndView) ; System . out. println ( "在Handler邏輯執(zhí)行完畢但未跳轉(zhuǎn)頁面之前攔截一次,我是第二次" ) ; } @Override public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System . out. println ( handler) ; System . out. println ( ex) ; System . out. println ( "在跳轉(zhuǎn)頁面之后攔截一次,我是第三次" ) ; }
}
我們寫好了攔截器,自然需要進行使用,在mvc中使用,是必須需要配置的,并且他是mvc的,所以是需要在mvc的對應的配置文件中進行配置:
對應的springmvc.xml的補充:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
實際上mvc:interceptors代表可以配置多個,在配置多個時,一般需要操作路徑,防止都進行攔截的處理(這個時候不寫可能報錯,具體可以測試),而不是指定的攔截(因為默認是/**),所以存在mvc:interceptors中存在mvc:interceptor,所以如果你只有一個并且需要是/ * *,那么可以這樣寫:
< mvc: interceptors> < bean class = " com.Interceptor.MyInterceptor" > </ bean> </ mvc: interceptors>
一般我們建議使用這樣的方式來進行編寫:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
當然其實還有功能,這是mvc設計出來的功能,他也可以進行去掉,比如他可以選擇性的不攔截一個路徑的請求:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < mvc: exclude-mapping path = " /demo/**" /> < bean class = " com.Interceptor.MyInterceptor" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
這樣就能不攔截demo開頭的處理了,一般情況下,servlet或者mvc的普通攔截都是在項目路徑下的直接攔截,比如前面的測試的路徑test/in,就是放在項目路徑后面,而不是端口路徑
編寫好后,我們執(zhí)行前面的代碼,看看后端的結(jié)果:
打印如下:
這個時候,你可以選擇將返回值變成false,那么我們看看這個打印結(jié)果:
我們可以發(fā)現(xiàn),他甚至連對應的方法都不執(zhí)行了,而方法都不執(zhí)行,默認情況下,是沒有響應體信息的,那么在前端顯示的就是空白(這個時候,甚至都不會操作視圖,也就是單純的返回空響應),也就是說,這個true也決定了對應的controller的方法的執(zhí)行,這也是為什么默認情況下,對應的HandlerInterceptor方法的返回值是true的一個原因
現(xiàn)在我們添加一個前端jsp,test.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<%System.out.println(1);
%>
1
</script>
</body>
</html>
上面的這個地方加上代碼:
<%System.out.println(1);
%>
這個是jsp的語法(在51章博客有說明),在轉(zhuǎn)譯和編譯的情況下,會進行處理的,最終操作攔截,那么我們在springmvc.xml中加上如下的配置:
< bean id = " viewResolver" class = " org.springframework.web.servlet.view.InternalResourceViewResolver" > < property name = " prefix" value = " /" > </ property> < property name = " suffix" value = " .jsp" > </ property> </ bean>
然后再test類中加上這個代碼:
@RequestMapping ( "ix" ) public String ix ( ) { return "test" ; }
首先還是false的返回值,然后直接的在url中后面加上test/ix訪問即可,這個時候我們查看后端的打印:
因為false不會經(jīng)過方法,所以在前端是顯示空白的
經(jīng)過這兩次的測試,可以發(fā)現(xiàn)handler的打印信息包含了,訪問權(quán)限,返回值,對有包路徑的方法及其參數(shù)列表等等,我們給ix方法加上兩個參數(shù)列表,即:
public String ix ( String a, Integer b) {
對應的打印信息如下:
即也的確如此,現(xiàn)在我們給false,變成true的返回值,然后看看打印結(jié)果:
也驗證了前面注釋中的:如果你在jsp中操作了輸出語句,那么這個值輸出后,這個方法才會進行處理
但是還有一個問題,如果controller對應的方法沒有返回值呢,因為沒有返回值說明他不會經(jīng)過視圖,而不經(jīng)過視圖,那么第三個攔截是否不會進行了,所以我們修改ix方法:
@RequestMapping ( "ix" ) public void ix ( String a, Integer b) { }
因為當我們沒有給出視圖名時,會將請求(參數(shù))進行拼接,一般是@RequestMapping的整個拼接
我們還是true,因為false修改與不修改是一樣的,反正都不執(zhí)行,那么他的修改沒有意義,我們看看執(zhí)行后的打印結(jié)果:
但是這里還存在一些細節(jié),這是前面并沒有說明的,我們看如下:
我們繼續(xù)修改:
@RequestMapping ( "ix" ) public String ix ( String a, Integer b) throws IOException { return null ; }
執(zhí)行之后,結(jié)果與上面的一樣,也就證明了,其實對應的視圖默認是null(是默認,雖然會根據(jù)組件得到路徑的視圖),所以當你返回類型為void時,他的視圖結(jié)果與返回null值是一樣的,然而,雖然根據(jù)路徑得到了視圖,但是其實在沒有視圖時也會得到結(jié)果,為什么,我們看這個代碼:
@RequestMapping ( "ix" ) public void ix ( String a, Integer b, HttpServletResponse mm) throws IOException { mm. setContentType ( "text/html;charset=utf-8" ) ; PrintWriter writer = mm. getWriter ( ) ; writer. println ( "哈哈哈" ) ; }
后端打印:
可以發(fā)現(xiàn)沒有視圖了,沒有視圖相當于直接的返回的操作(直接操作響應體信息了),但是為什么加上這些就會沒有呢,這是因為如果你要自己加上響應信息的話,那么必然會與原來的如jsp而發(fā)生沖突(一般來說io并不能覆蓋,只是增加),導致對應的jsp信息出現(xiàn)問題,所以他們是需要分開的,所以只要你存在要自行操作響應體信息的,那么視圖就會為null,通過測試,只要你在參數(shù)列表中加上HttpServletResponse mm,比如:
@RequestMapping ( "ix" ) public void ix ( String a, Integer b, HttpServletResponse mm) throws IOException {
}
那么視圖就為null,即執(zhí)行的操作響應體信息,如果你沒有設置,自然前端顯示空白
而打印信息中,第二次和第三次無論是否操作了HttpServletResponse或者無論是否報錯,都會出現(xiàn),也就是說,后面兩個并沒有互相聯(lián)系的不能讓對方放行的處理,所以他們基本必然都會打印了,但是報錯的話,視圖是找不到了,那么就不會出現(xiàn)對應的轉(zhuǎn)譯編譯才對,為什么第三次攔截也會出現(xiàn)呢,在前面我們說過了"10到11則是第三次攔截(這里也可能是9到10)",雖然在第8次報錯,但是第9次的操作需要返回報錯信息,這個時候是會經(jīng)過第三次攔截的(簡單來說,有返回,就算是空的,他通常也會操作)
至此,我們的細節(jié)基本說明完畢
攔截器的執(zhí)行流程:
在運行程序時,攔截器的執(zhí)行是有一定順序的,該順序與配置?件中所定義的攔截器的順序相關(guān),單個 攔截器,在程序中的執(zhí)行流程如下圖所示:
1:程序先執(zhí)?preHandle()方法,如果該方法的返回值為true,則程序會繼續(xù)向下執(zhí)行處理器中的方 法,否則將不再向下執(zhí)行
2:在業(yè)務處理器(即控制器Controller類)處理完請求后,會執(zhí)?postHandle()方法,然后會通過DispatcherServlet向客戶端返回響應
3:在DispatcherServlet處理完請求后,才會執(zhí)?afterCompletion()方法
再結(jié)合這個圖吧:
根據(jù)前面的說明,他的流程也可以說是對的
總結(jié)一下:
上面是單個攔截器的流程,那么多個攔截器的執(zhí)行流程呢:
多個攔截器(假設有兩個攔截器Interceptor1和Interceptor2,并且在配置?件中, Interceptor1攔截 器配置在前),在程序中的執(zhí)行流程如下圖所示:
從圖可以看出,當有多個攔截器同時工作時,它們的preHandle()方法會按照配置?件中攔截器的配置 順序執(zhí)行,?它們的postHandle()方法和afterCompletion()方法則會按照配置順序的反序執(zhí)行
我們來看例子:
首先創(chuàng)建兩個類在Interceptor包中:
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; public class MyInterceptor1 implements HandlerInterceptor { @Override public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System . out. println ( "preHandle1...." ) ; return true ; } @Override public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System . out. println ( "postHandle1...." ) ; } @Override public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System . out. println ( "afterCompletion1...." ) ; }
}
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; public class MyInterceptor2 implements HandlerInterceptor { @Override public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System . out. println ( "preHandle2...." ) ; return true ; } @Override public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System . out. println ( "postHandle2...." ) ; } @Override public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System . out. println ( "afterCompletion2...." ) ; }
}
我們進行配置(去掉原來的):
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor1" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor2" > </ bean> </ mvc: interceptor> </ mvc: interceptors> < bean id = " viewResolver" class = " org.springframework.web.servlet.view.InternalResourceViewResolver" > < property name = " prefix" value = " /" > </ property> < property name = " suffix" value = " .jsp" > </ property> </ bean>
后端(之前處理的):
@RequestMapping ( "ix" ) public String ix ( String a, Integer b) throws IOException { return "test" ; }
現(xiàn)在我們執(zhí)行(操作上面的test.jsp)看看打印信息:
也的確是第一個是順序(后配置的在后面,你修改配置中攔截的順序即可),后面兩個是反序,現(xiàn)在我們來測試,讓其中一個
配置放在前面:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor2" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor1" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
打印結(jié)果:
也的確如此,那么如果其中一個不放行呢,我們將preHandle2…的對應方法的返回值設置為false看看,上面的配置不變,執(zhí)行后,看看打印結(jié)果:
只有這一個,那么我們將配置順序改變回來,執(zhí)行看看結(jié)果:
感覺到了,如果你的返回值是false,那么你后面的攔截都不能進行處理,包括后面的第一次攔截,但是,如果存在第一次攔截執(zhí)行完畢,那么允許他執(zhí)行第三次攔截,雖然是這樣說,但是也只是一個結(jié)論,我們選擇繼續(xù)添加一個類來進行處理:
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; public class MyInterceptor3 implements HandlerInterceptor { @Override public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System . out. println ( "preHandle3...." ) ; return false ; } @Override public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System . out. println ( "postHandle3...." ) ; } @Override public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System . out. println ( "afterCompletion3...." ) ; }
}
將preHandle2…的對應方法的返回值設置為true(修改回來),然后配置如下:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor1" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor3" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor2" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
執(zhí)行看看結(jié)果:
可以發(fā)現(xiàn),結(jié)論正確,但是為什么會這樣,按道理說,postHandle1…應該也會執(zhí)行啊,為什么沒有呢,實際上如果沒有false,那么結(jié)果應該是:
其中中間的處理基本都是方法的處理,方法沒有進行處理,自然沒有視圖,沒有視圖,前端自然返回空白信息(響應體沒有信息),那么這個false會導致后面的不進行處理,但是afterCompletion1…對應的方法的攔截是在響應哪里,而這個由于preHandle1…是返回true的,他的方法是你造成的不處理,而不是我自己,所以說afterCompletion1…會進行打印,那么這個攔截的關(guān)系就有意思了,其中preHandle1…(以后就這樣說明了,一般這樣說是說他對應的方法,注意即可)的返回值的確影響后面的放行,但是對后面兩個方法的放行的影響是不同的,postHandle1…是根據(jù)true造成的放行來決定執(zhí)行的,也就是說,只要你放行了(true影響放行的參數(shù)),那么我就會執(zhí)行,但是如果你沒有放行,那么我自然也執(zhí)行不了,而afterCompletion1…只看你的返回值,而不看你是否放行,所以在單獨的攔截器的時候,true和false的結(jié)果都是他們兩個執(zhí)行或者不執(zhí)行,但是存在其他的攔截時,那么放行這個處理和判斷true的處理的區(qū)別就出現(xiàn)了,這也是為什么afterCompletion1…會執(zhí)行,但是postHandle1…不會執(zhí)行
為了驗證這樣的結(jié)果,我們修改配置文件:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor1" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor2" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor3" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
執(zhí)行看看結(jié)果:
可以發(fā)現(xiàn),之前的結(jié)論是正確的(后面先打印afterCompletion2…,是反序的原因)
那么還有個問題,為什么mvc的攔截器中,第一次攔截是順序的,其他兩個是反序的或者說倒序的:
解釋如下:
這個默認規(guī)則的原因是為了在執(zhí)行攔截器時提供更多的靈活性和可能性,考慮以下情況:
第一個攔截器通常用于做一些準備工作,如日志記錄、身份驗證等,按順序執(zhí)行有助于確保這些準備工作在控制器方法之前完成
控制器方法執(zhí)行后,倒序執(zhí)行其他攔截器可以用于清理工作、日志記錄和一些其他操作,這確保了在請求處理完畢后執(zhí)行這些操作,因為這些工作一般先創(chuàng)建的后清理
但是實際上順序的問題,大多數(shù)我們并不需要注意,并且,也存在可以修改順序的處理,只是可能需要某些版本(好像現(xiàn)在基本都不行了)
處理multipart形式的數(shù)據(jù):
前面雖然我們使用mvc處理過問題,但是還是有些操作我們沒有說明,比如方便我們操作的api,現(xiàn)在來補充一下:
首先加上依賴:
< dependency> < groupId> commons-fileupload</ groupId> < artifactId> commons-fileupload</ artifactId> < version> 1.3.1</ version> </ dependency>
配置上傳文件解析器:
< bean id = " multipartResolver" class = " org.springframework.web.multipart.commons.CommonsMultipartResolver" > < property name = " maxUploadSize" value = " 1000000000" /> </ bean>
對應的前端(index.jsp):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" action="demo/upload"><input type="file" name="uploadFile"/><input type="submit" value="上傳"/>
</form>
</body>
</html>
對應的后端(我們創(chuàng)建FileController類):
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. multipart. MultipartFile ; import javax. servlet. http. HttpServletRequest ;
import java. io. File ;
import java. io. IOException ;
import java. text. SimpleDateFormat ;
import java. util. Date ;
import java. util. UUID ; @Controller
@RequestMapping ( "/demo" )
public class FileController { @RequestMapping ( "upload" ) public String upload ( MultipartFile uploadFile, HttpServletRequest request) throws IOException { String originalFilename = uploadFile. getOriginalFilename ( ) ; String extendName = originalFilename. substring ( originalFilename. lastIndexOf ( "." ) + 1 , originalFilename. length ( ) ) ; String uuid = UUID . randomUUID ( ) . toString ( ) ; String newName = uuid + "." + extendName; String realPath = request. getSession ( ) . getServletContext ( ) . getRealPath ( "/" ) ; System . out. println ( realPath) ; String datePath = new SimpleDateFormat ( "yyyy-MM-dd" ) . format ( new Date ( ) ) ; File floder = new File ( realPath + "/" + datePath) ; if ( ! floder. exists ( ) ) { floder. mkdirs ( ) ; } uploadFile. transferTo ( new File ( floder, newName) ) ; return "success" ; }
}
然后如果你按照流程來的話,對應的視圖解析器應該是配置的,所以我們在index.jsp同級別創(chuàng)建一個success.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
1
</body>
</html>
執(zhí)行后,我們上傳一個文件或者說給一個文件,然后看看當前項目是否出現(xiàn)對應的文件或者目錄,然而并沒有,為什么
這里就需要考慮maven的操作與傳統(tǒng)的web的區(qū)別了,首先,我們可以選擇實現(xiàn)一個傳統(tǒng)的web處理(這里可以選擇看看第50章博客):
為此這里我也不得不給出三種創(chuàng)建web的方式,空項目,傳統(tǒng)web,maven項目
首先我們從空項目來進行處理,由于空項目并沒有使用到maven或者并沒有進行指定使用maven,所以一般空項目只能到傳統(tǒng)的web(即空項目到web一般與傳統(tǒng)的web項目是一樣的),然而這是一般的說法,基本上任何形式都可以從空項目變化而來,因為他就是在idea中,所以這里應該有五種方式:空項目(操作java程序),空項目到web,空項目到maven,(傳統(tǒng))web,maven等等
創(chuàng)建ser1空項目:
然后進入如下:
空項目是什么都沒有,有時也會包括模塊,一般情況下,一個項目是對應一個模塊的,如果沒有模塊,我們可以創(chuàng)建模塊:
這里了解即可,創(chuàng)建模塊一般是如下:
創(chuàng)建后,選擇關(guān)閉idea,打開我們創(chuàng)建好模塊的這個目錄即可,然后可以這樣:
從上面我們可以看到,這個目錄后面有個sources root,這個再后面說明,他代表這個目錄下面是專門寫java的,所以這個時候創(chuàng)建的目錄是包的意思(否則不是)
直接執(zhí)行看看結(jié)果即可(一般sources root的出現(xiàn)除了配置外,執(zhí)行java代碼也會出現(xiàn),在項目中,他雖然也是文件夾,但是他也由于是項目,所以可以直接創(chuàng)建java文件(雖然不能再其里面的文件夾里面創(chuàng)建java文件),右鍵可以看到與其里面的目錄有是不同的選項的),但是一般情況下,我們需要src這個目錄,但是要明白src這個目錄其實也是認為創(chuàng)造的,在maven或者web中,使用而已,而單純的java并不一定使用他,但是為了統(tǒng)一所以我們可以選擇創(chuàng)建src這個包括來操作也就是:
至此可以說空項目(操作java程序)操作完畢,現(xiàn)在我們來完成空項目到web:
空項目到web,需要這樣的處理,一般情況下,是需要很多的東西,當然我們也可以選擇創(chuàng)建一個web項目來觀察一下,然后再從空項目到web,歸根揭底,web和空項目的區(qū)別就是對應目錄賦予一些操作屬性,讓他作為資源文件,以及相關(guān)web中的對應文件也賦予屬性(由于是賦予的,所以對應的文件名稱并非需要固定,比如web對應的資源文件可以變成webb,只是有些插件或者說idea的某些自動處理(比如maven的依賴自動判斷文件名稱來自動配置)會處理這些文件名稱,所以我們大多數(shù)都會配置對應的固定文件,比如web中,配置war包,那么對應的文件若是web或者webapp通常會自動配置,這里也要注意,隨著時間的推移這些名稱可能會變,所以注意即可,一般好像只有webapp了,web已經(jīng)沒有了,如果在以前博客中有說明,那么大概是以前的某個版本或者是以前的操作(以前的版本也是可能會發(fā)生改變的,因為官方也是維護的,除非是固定的版本)),這些操作再老版本的idea中可能需要手動的處理,但是新版的一般并不存在這樣的處理了,或者忽略一些處理,所以這里我們來操作一些賦予這個操作:
其他的刪除,回到這里:
然后這樣:
這樣src就可以操作java代碼,而再xianmu的直接目錄里面就不行了,很明顯,這個選項是賦予這個目錄存放java或者被編譯器編譯的地方,更加可以說,編譯器只會去這個屬性中操作java代碼,自然導致操作發(fā)生改變(如這個時候右鍵xianmu這個目錄時,出現(xiàn)的選項發(fā)生變化了),這些操作之所以會這樣,其實是idea軟件自身處理的,或者idea也處理了jdk的某些配置,以及或者說idea他手動的幫我們選擇編譯以及執(zhí)行,然后將結(jié)果放在控制臺中(這個的顯示自然也可以認為是一個文件中,或者直接的顯示而不是文件中,這些操作都可以被我們處理,因為二進制就是可以這樣,二進制出現(xiàn)顯示還是復雜的,就算在操作系統(tǒng)中,顯示的處理一般也并沒有具體說明,因為是需要通過硬件完成對規(guī)定排列的映射的,這里了解即可)
好了,既然src的情況我們說明完畢,但是空項目操作(到)web還不知道如何操作,現(xiàn)在我們來操作一下:
然后操作如下:
這樣對應的前面就會出現(xiàn)如下:
這樣你就可以操作啟動服務器了(具體自行配置,可以看50章博客),如果啟動后,出現(xiàn)對應的數(shù)據(jù),那么操作成功,這就是空項目到web的處理,那么傳統(tǒng)web是如何處理(實際上也就是一次性到這里):
其中idea或者說web項目,通常會有隱藏的設置,即自動讀取在WEB-INF/lib/下面的jar包,這里了解即可,當然,其實我們也可以手動的指定,但是一般情況下,新版的可能沒有這樣的設置的,所以建議指定
一般情況下,傳統(tǒng)項目的處理基本只會在老版本的idea中,新版的沒有,具體在50章博客中可以看到,所以這里我們就不多說了
現(xiàn)在我們先操作一下這個(前面的"這里就需要考慮maven的操作與傳統(tǒng)的web的區(qū)別了"):
先加上這個,具體的下載地址是如下:
鏈接:https://pan.baidu.com/s/1Yyd662YY99X7wEGIZ4FN7w
提取碼:alsk
然后配置如下:
點擊這個,在目錄中選擇lib文件或者直接選擇jar包都可,選擇文件說明是操作里面的所有jar包的
然后總體操作是如下:
在web.xml中配置如下:
<?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" > < servlet> < servlet-name> ConfigServlet</ servlet-name> < servlet-class> servlet</ servlet-class> </ servlet> < servlet-mapping> < servlet-name> ConfigServlet</ servlet-name> < url-pattern> /config</ url-pattern> </ servlet-mapping>
</ web-app>
補充對應的servlet的部分內(nèi)容:
@Override public void service ( ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException , IOException { String realPath = servletRequest. getServletContext ( ) . getRealPath ( "/" ) ; System . out. println ( realPath) ; }
啟動,訪問對應的config(記得加上),看看打印結(jié)果(這里就是我們需要查看的區(qū)別)
F : \xianmu\out\artifacts\xianmu_Web_exploded\
即他操作的是編譯后最終的結(jié)果,其實也可以看出來,他的操作在很大程度上是對應的這個結(jié)果:
我們可以在啟動時(查看下方啟動日志即可),查看臨時的tomcat(簡稱為臨時處理,或者說tomcat副本),比如我的就是:
C : \Users \33988 \AppData \Local \JetBrains \IntelliJIdea2021 .3 \tomcat\062 b022d- 1657 - 4d 8 b- b4e9- da3eeae3d227\conf\Catalina \localhost
里面的配置文件就是這個:F:\xianmu\out\artifacts\xianmu_Web_exploded",即他還是操作臨時的tomcat
那么現(xiàn)在就剩下兩個了,即空項目到maven,以及直接的maven,空項目到maven其實也并不是很難:
我們創(chuàng)建如下的空項目:
然后我們操作如此:
配置這些:
那么怎么變成maven項目呢,我們可以思考,要變成對應的這個項目,必然是需要使用到maven,這就需要我們配置使用了,具體如何配置看如下:
先創(chuàng)建pom.xml(與src同級別):
<?xml version="1.0" encoding="UTF-8"?>
< project xmlns = " http://maven.apache.org/POM/4.0.0" xmlns: xsi= " http://www.w3.org/2001/XMLSchema-instance" xsi: schemaLocation= " http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion> 4.0.0</ modelVersion> < groupId> org.example</ groupId> < artifactId> testma</ artifactId> < version> 1.0-SNAPSHOT</ version> < properties> < maven.compiler.source> 11</ maven.compiler.source> < maven.compiler.target> 11</ maven.compiler.target> </ properties> </ project>
但是他并沒有進行處理,我們需要如下的配置(maven本身也可以看成一個框架,而idea需要被他支持,就如空項目到web時需要指定jar包一樣,當然,也可以添加框架(實際上也就是對應的jar包),但是由于maven的jar比較多,所以這里我們就選擇添加框架了):
一般來說,這個文件添加好后,右下角就會出現(xiàn)這個,當然,一般都會出現(xiàn)了,否則可能你需要重新的刪除在創(chuàng)建(因為基本沒有其他辦法來構(gòu)建maven,因為功能也并非都會提供了)
之后的選擇,基本選擇第一個即可,當然,還是需要看具體情況,最好翻譯一下
然后你可以選擇在其內(nèi)容加上:
< packaging> war</ packaging>
一般這個時候,由于刷新了,那么他基本就出現(xiàn)了對應的顯示(M)
某種程度上,點擊右下角后,把這里出現(xiàn)的這個勾勾去掉也行:
而maven的創(chuàng)建操作,我們就不處理了,因為我們操作過很多次了
至此,對應的五個方式(空項目(操作java程序),空項目到web,空項目到maven,(傳統(tǒng))web,maven)我們操作完畢,其中
maven操作web我們就不處理了,因為我們可以只設置打包方式即可,比較簡單
但是為了看看前面的問題,所以我們還是需要進行處理的,首先創(chuàng)建webapp文件:
如下:
如果前面再pom.xml中沒有寫上maven,而是testma,那么項目名稱后面一般會有[testma]
依賴如下:
< dependencies> < dependency> < groupId> javax.servlet</ groupId> < artifactId> javax.servlet-api</ artifactId> < version> 3.1.0</ version> < scope> provided</ scope> </ dependency> </ dependencies>
即對應的配置與前面的一樣,我們看看maven與傳統(tǒng)web的區(qū)別是什么:
我們經(jīng)過測試:
當操作方式為如下時會出現(xiàn)不同的結(jié)果(配置tomcat時,會出現(xiàn)的):
如圖:
至于他們里面的配置,你可以選擇的再引入或者添加時,點擊來自誰即可,也可以手動處理(一般exploded的基本相同,可能部分不同,但是好像也并不影響,注意即可,在選項中點擊+號一般就會知道了)
所以maven:war exploded和xianmu:Web exploded由于對應的類型的對象基本是一樣的是臨時的,我們可以測試看看:
首先進入maven:war時,對應的臨時處理,看看其配置文件的結(jié)果:
< Context path = " /maven" docBase = " F:\maven\target\maven-1.0-SNAPSHOT.war" />
F : \Program Files \apache- tomcat- 8.5 .50 \webapps\maven\
進入maven:war exploded,看看其結(jié)果:
< Context path = " /maven" docBase = " F:\maven\target\maven-1.0-SNAPSHOT" />
F:\maven\target\maven-1.0-SNAPSHOT\
進入之前測試的xianmu:Web exploded,看看其結(jié)果:
<Context path="/xianmu" docBase="F:\xianmu\out\artifacts\xianmu_Web_exploded" />
F:\xianmu\out\artifacts\xianmu_Web_exploded\
很明顯,帶有exploded的結(jié)果與配置文件一致,而沒有的,則是操作本來的tomcat,并且放在里面,為什么,這就需要一些隱藏的配置處理了,在明顯的指定war時,那么他的操作就會自動在本來的tomcat中處理,而不是臨時的處理目錄,這也可以說是默認的處理,具體情況還是看tomcat的源碼了,可能是因為需要這樣的情況才會弄出來吧
如果沒有值呢,是空值呢,如對應的代碼:
String realPath = servletRequest. getServletContext ( ) . getRealPath ( "/" ) ;
上面中的"/"不加,而是變成:
String realPath = servletRequest. getServletContext ( ) . getRealPath ( "" ) ;
結(jié)果如何:
經(jīng)過測試,默認的結(jié)果還是加上"/“的結(jié)果一樣,所以默認加上”/“,并且經(jīng)過測試,所以如果是getRealPath(“a”);或者getRealPath(”/a");,他們的結(jié)果都是"/a",這種情況得到的結(jié)果就是在路徑后面加上a(所以這個a我們最好寫成一下比較好的目錄,比如文件文件相關(guān)的目錄,比如uploads),所以這里參數(shù)的意思也就是加上參數(shù)得到的整體路徑的意思了,所以大多數(shù)為了得到完整的路徑,一般都會顯示的操作"/"即可
當然,為了在后面解決很多的疑問或者說以前的疑問,我決定統(tǒng)一的將web的mvc相關(guān)對于路徑的說法以及傳統(tǒng)web關(guān)于路徑的說法進行處理:
一般在web中關(guān)于路徑的說明存在多種,一般主要是四種,比如:轉(zhuǎn)發(fā),重定向,xml的路徑,注解的路徑等等
然而這些的說明在以前也說明了,這里就不多說,比如可以到67章博客看看(若有遺漏,也可以選擇到50章博客補充,如果還有,那么可以自己進行測試,一般加上50章博客的話是沒有遺漏的)
所以關(guān)于getRealPath路徑的區(qū)別我們說明完畢,回到之前的操作:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. multipart. MultipartFile ; import javax. servlet. http. HttpServletRequest ;
import java. io. File ;
import java. io. IOException ;
import java. text. SimpleDateFormat ;
import java. util. Date ;
import java. util. UUID ; @Controller
@RequestMapping ( "/demo" )
public class FileController { @RequestMapping ( "upload" ) public String upload ( MultipartFile uploadFile, HttpServletRequest request) throws IOException { String originalFilename = uploadFile. getOriginalFilename ( ) ; String extendName = originalFilename. substring ( originalFilename. lastIndexOf ( "." ) + 1 , originalFilename. length ( ) ) ; String uuid = UUID . randomUUID ( ) . toString ( ) ; String newName = uuid + "." + extendName; String realPath = request. getSession ( ) . getServletContext ( ) . getRealPath ( "/" ) ; System . out. println ( realPath) ; String datePath = new SimpleDateFormat ( "yyyy-MM-dd" ) . format ( new Date ( ) ) ; File floder = new File ( realPath + "/" + datePath) ; if ( ! floder. exists ( ) ) { floder. mkdirs ( ) ; } uploadFile. transferTo ( new File ( floder, newName) ) ; return "success" ; }
}
執(zhí)行后,我們上傳一個文件或者說給一個文件,然后看看當前項目是否出現(xiàn)對應的文件或者目錄,然而并沒有,為什么,就是因為由于其操作的是不帶有exploded的,所以到原本的tomcat中里面生成了,然而一般在idea中可能并不會顯示,因為他可能是只會顯示在起始的目錄(如target里面,也可能還要里面)或者其他的隱藏(F:\Program Files\apache-tomcat-8.5.50\webapps\maven\),這里了解即可
但是上面的操作中,由于沒有到當前項目的路徑,那么他是不是有問題的,實際上一般情況下,這只是不同系統(tǒng)中的tomcat的處理而已,而當我們發(fā)版(也就是部署到服務器提供給用戶使用時)時,一般在linux中,而這個系統(tǒng)下,一般都是當前項目所在,即指向的是當前項目里面(類似于前面的F:\Program Files\apache-tomcat-8.5.50\webapps\maven\),所以我們這個代碼是沒有問題的,只是環(huán)境不同而已,這個時候如果是沒有exploded的,就會到當前項目中(實際上發(fā)版的也就是這個),所以一般的,我們并不考慮在開發(fā)中的路徑處理,因為最后一定是沒有exploded的,即路徑在生產(chǎn)中是基本對的(開發(fā)代表在idea中操作(寫)代碼,生產(chǎn)代表已經(jīng)發(fā)版),當然idea中也可以考慮,只是需要一些設置而已(如路徑的處理,以及tomcat選擇的處理),這些可以百度查看,這里就不說明了
至此,對處理multipart形式的數(shù)據(jù)的一些補充,我們補充完畢
實際上對于UUID來說,也是可能出現(xiàn)重復,因為他存在如下的情況:
在控制器中處理異常:
在前面我們知道有HandlerExceptionResolver這樣的組件,是來處理異常的,我們自然也會圍繞這個來處理(一般來說,對應注解的識別就是由他來處理的)
在controller包下創(chuàng)建GlobalExceptionResolver類:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. ControllerAdvice ;
import org. springframework. web. bind. annotation. ExceptionHandler ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
@Controller
public class GlobalExceptionResolver { @ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; } @RequestMapping ( "err" ) public String ix ( String a, Integer b) throws IOException { System . out. println ( 1 ) ; int i = 1 / 0 ; return "err" ; }
}
對應的jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
錯誤是:${msg}
</body>
</html>
一般情況下,我們訪問err(具體訪問自己應該知道了),一般來說異常不會經(jīng)過第二個攔截,這是因為出現(xiàn)異常自然不會考慮數(shù)據(jù)或者視圖的問題,所以一般就會規(guī)定不會經(jīng)過第二次攔截,只會依靠固有的數(shù)據(jù)或者視圖進行處理異常,即:
modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ;
modelAndView. setViewName ( "error" ) ;
所以注釋這個:
自然第二個攔截沒有,且第三個攔截打印出對應的信息(我們寫上的),所以他的作用只是在出現(xiàn)異常時,進一步的處理視圖操作,否則按照默認的異常視圖進行處理,你可以在瀏覽器看看注釋后的界面就知道了
一般情況下,寫在當前controller中的對應的異常處理,只會對自身進行生效,并且,如果存在多個,比如:
@ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; } @ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException1 ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) + "22" ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; }
那么會報錯,由上所述,我們應該需要統(tǒng)一的異常處理,所以我們需要一個全局的處理,那么我們再次的創(chuàng)建一個類:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. ExceptionHandler ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletResponse ; @Controller
public class Global { @ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; }
}
然后把GlobalExceptionResolver類中的異常處理都進行刪除,然后啟動看看,是否在這個類里面處理了異常,發(fā)現(xiàn)并沒有,說明的確對應的異常只能操作自身的controller的,那么怎么將這個異常處理變成全局呢,我們操作如下:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. ControllerAdvice ;
import org. springframework. web. bind. annotation. ExceptionHandler ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletResponse ; @ControllerAdvice
@Controller
public class Global { @ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; }
}
現(xiàn)在我們繼續(xù)執(zhí)行,會發(fā)現(xiàn)他處理異常了,也就是說,@ControllerAdvice可以使得這個類里面或者說這個controller里面的異常的那個處理變成全局的處理,當然,并不必須需要他是controller(寫上也沒有關(guān)系),首先controller的主要作用是定位(如果不作為全局異常的話,兩個都可以單獨寫上進行處理),而@ControllerAdvice也是算一個定位,只不過他是異常的定位而已,當然,你寫上controller也沒有關(guān)系,可以在不處理異常時,作為controller使用也行,而在處理異常時,@ControllerAdvice可以作為controller使用的(雖然他并不是),所以這個時候可以不加對應的controller,照樣的可以進行操作(因為@ExceptionHandler只有一個,也只能有一個,否則報錯)
那么如果存在多個全局呢,誰先使用,答:看下圖
經(jīng)過大量的測試,發(fā)現(xiàn),由于windows中,A和a是一樣的,所以可以得到:當字母的數(shù)量或者說文件名長度相等時,按照Ascii來決定,越小那么就越優(yōu)先(上面沒有測試數(shù)字,實際上數(shù)字也是的,由于1<a,那么Gloaa1優(yōu)先于Gloaaa),否則的話,長度越長越優(yōu)先
當然,上面的說明并不重要,因為全局的一個就夠了
基于Flash屬性的跨重定向請求數(shù)據(jù)傳遞:
在前面我們知道一個組件FlashMapManager,一般就用于這里
重定向時請求參數(shù)會丟失,我們往往需要重新攜帶請求參數(shù),我們可以進行?動參數(shù)拼接如下:
我們創(chuàng)建一個類:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. RequestMapping ; @Controller
public class Redirect { @RequestMapping ( "/re" ) public String re ( ) { return "redirect:ree?name=" + 1 ;
} @RequestMapping ( "/ree" ) public String ree ( String name) { System . out. println ( name) ; System . out. println ( 1 ) ; return "ree" ; }
}
先訪問re,看看結(jié)果吧
上述拼接參數(shù)的方法屬于get請求,攜帶參數(shù)?度有限制,參數(shù)安全性也不?(因為在url上),此時,我們可以使?SpringMVC提供的?ash屬性機制,向上下?中添加?ash屬性,框架會在session中記錄該屬性值,當 跳轉(zhuǎn)到??之后框架會?動刪除?ash屬性,不需要我們?動刪除,通過這種方式進行重定向參數(shù)傳遞, 參數(shù)?度和安全性都得到了保障,如下:
繼續(xù)在上面的類中加上方法:
@RequestMapping ( "/reee" ) public String reee ( RedirectAttributes redirectAttributes, HttpSession session) { redirectAttributes. addFlashAttribute ( "name" , "22" ) ; Object name = session. getAttribute ( "name" ) ; System . out. println ( name) ; return "redirect:reeee?name=" + 1 ; } @RequestMapping ( "/reeee" )
public String reeee ( String name, HttpSession session, @ModelAttribute ( "name" ) String na, ModelAndView modelAndView) { System . out. println ( modelAndView) ; System . out. println ( na) ; String name2 = ( String ) session. getAttribute ( "name" ) ; System . out. println ( name2) ; System . out. println ( 44444444 ) ; System . out. println ( name) ; Object name1 = session. getAttribute ( "name" ) ; System . out. println ( name1) ; return "ree" ; }
執(zhí)行后,測試一下吧,但是為什么flash(英文是顯示的意思)類型屬性只會在兩次請求之間有效,解釋如下:
至此解釋完畢,現(xiàn)在我們來?寫 MVC 框架,上面雖然說明了很多知識,但是基本只有手寫出來,我們才能知道更加深層次的東西,現(xiàn)在開始手寫MVC框架:
回顧SpringMVC執(zhí)行的?致原理,后續(xù)根據(jù)這個模仿?寫自己的mvc框架
spring的維護是必須的,因為他最終保存的對象(數(shù)據(jù),內(nèi)容)是用來判斷映射的,難道是憑空保存或者直接保存嗎,總需要一個總地方吧(spring的地方)
具體的說明就是:前端控制器進行初始化的配置,而掃描交給spring來處理(springmvc有spring的),然后根據(jù)前端控制器初始化的信息(在一個servlet中,是可以操作同一個請求頭和響應頭的,或者說請求信息和響應信息),來決定調(diào)用誰,也就是映射,后面進行一系列的處理(如jsp的響應),最終得到我們的結(jié)果,具體說明看后面就知道了,我們手寫一個類似的即可
手寫MVC框架之注解開發(fā)(也可以存在對應的xml開發(fā),只是對與mvc來說,通常是需要注解的,單純的xml并不好處理,具體可以百度,一般mybatis,spring,springmvc基本都是需要xml和注解一起的,反正那個方便使用那個):
通常來說,注解的性能會比xml慢點(通常指啟動的時候,有時候運行時也會,但是方便許多,特別的是考慮到注解的掃描(當包非常多時,有些不需要的可能也會掃描到))
在手寫之前,我們創(chuàng)建一個項目:
web.xml中是:
< web-app> </ web-app>
現(xiàn)在開始編寫,首先,我們創(chuàng)建com.mvc.framework包,然后再該包下創(chuàng)建DispatcherServlet類:
上面是需要服務器的包的,自然需要引入,也就是:
< dependencies> < dependency> < groupId> javax.servlet</ groupId> < artifactId> javax.servlet-api</ artifactId> < version> 3.1.0</ version> < scope> provided</ scope> </ dependency> </ dependencies>
然后我們創(chuàng)建這個類(包名自行創(chuàng)建,可以看下面的這個:package com.mvc.framework;來創(chuàng)建):
package com. mvc. framework ; import javax. servlet. http. HttpServlet ;
public class DispatcherServlet extends HttpServlet {
}
然后修改web.xml(補充服務器的讀取方式,這是固定的,除非你修改服務器,也就是tomcat的處理(servlet),當然,由于框架是建立在這個上面的,所以自然是保留的):
<?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> create</ display-name> < servlet> < servlet-name> mvc</ servlet-name> < servlet-class> com.mvc.framework.DispatcherServlet</ servlet-class> </ servlet> < servlet-mapping> < servlet-name> mvc</ servlet-name> < url-pattern> /*</ url-pattern> </ servlet-mapping>
</ web-app>
這個超級熟悉了吧,但是我們需要明白,這里是操作原生的servlet的(我們mvc是建立在他之上的,具體到50章博客學習),我們在操作映射之前,首先需要得到對應的對象來操作方法,從而操作映射,所以我們需要定義一些注解,或者說,完成Spring相關(guān)的操作
現(xiàn)在,我們在framework包下創(chuàng)建servlet包,將DispatcherServlet移動到這個包里面,然后再在framework包下創(chuàng)建annotations包,然后在該包下創(chuàng)建幾個注解:
package com. mvc. framework. annotations ; import java. lang. annotation. * ; @Documented
@Target ( ElementType . TYPE )
@Retention ( RetentionPolicy . RUNTIME )
public @interface Controller { String value ( ) default "" ;
}
package com. mvc. framework. annotations ; import java. lang. annotation. * ; @Documented
@Target ( ElementType . TYPE )
@Retention ( RetentionPolicy . RUNTIME )
public @interface Service { String value ( ) default "" ;
}
package com. mvc. framework. annotations ; import java. lang. annotation. * ; @Documented
@Target ( { ElementType . TYPE , ElementType . METHOD } )
@Retention ( RetentionPolicy . RUNTIME )
public @interface RequestMapping { String value ( ) default "" ;
}
package com. mvc. framework. annotations ; import java. lang. annotation. * ; @Documented
@Target ( ElementType . FIELD )
@Retention ( RetentionPolicy . RUNTIME )
public @interface Autowired { String value ( ) default "" ;
}
很明顯,我們需要操作Controller到Service中進行處理的,注解我們定義好了,現(xiàn)在我們開始進行開發(fā)
為了操作到Service的注解的處理,我們需要在framework包下,創(chuàng)建service包,再創(chuàng)建如下的兩個類或者接口:
package com. mvc. framework. service ; public interface DemoService { String get ( String name) ;
}
package com. mvc. framework. service. impl ; import com. mvc. framework. service. DemoService ; public class DemoServiceImpl implements DemoService { @Override public String get ( String name) { System . out. println ( "打印:" + name) ; return name; }
}
回到DispatcherServlet,在這里自然是需要進行包的掃描的,并且這個掃描需要知道往那里進行掃描(這里考慮在mvc相關(guān)xml中進行處理),然后Spring管理掃描后的結(jié)果,最后進行映射的處理,現(xiàn)在首先是處理掃描:
補充DispatcherServlet:
package com. mvc. framework. servlet ; import javax. servlet. ServletException ;
import javax. servlet. http. HttpServlet ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
public class DispatcherServlet extends HttpServlet { @Override public void init ( ServletConfig config) throws ServletException { } @Override protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { doPost ( req, resp) ; } @Override protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { }
}
由于這個是主要的操作,所以相關(guān)的代碼就應該寫在對應的doGet或者doPost的,具體細節(jié)在前面可能有些許的說明,但是還是需要看這里怎么處理
我們繼續(xù)補充或者修改(可以將get也到post,因為post可以包含get的處理,一般來說mvc也是如此,但是他一般是get操作get,post操作post(但是是同一個方法),與HttpServlet相關(guān),具體是操作了service,只是在中間可能會操作注解(前提是設置了),判斷是否是對應的請求方式而進行報錯的,比如進入到了get,那么在里面判斷注解是否是get相關(guān),可以選擇再次的得到請求,而進行補充判斷):
@Override public void init ( ServletConfig config) throws ServletException { }
在資源文件夾下補充mvc的配置文件mvc.xml:
< beans>
< component-scan base-package = " com.mvc.framework" />
</ beans>
在web.xml中加上如下:
<?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/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version = " 4.0" > < display-name> create</ display-name> < servlet> < servlet-name> mvc</ servlet-name> < servlet-class> com.mvc.framework.servlet.DispatcherServlet</ servlet-class> < init-param> < param-name> contextConfigLocation</ param-name> < param-value> mvc.xml</ param-value> </ init-param> </ servlet> < servlet-mapping> < servlet-name> mvc</ servlet-name> < url-pattern> /*</ url-pattern> </ servlet-mapping>
</ web-app>
回到后端初始化,在這里寫上如下:
package com. mvc. framework. servlet ; 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 ;
public class DispatcherServlet extends HttpServlet { @Override public void init ( ServletConfig config) throws ServletException { String contextConfigLocation = config. getInitParameter ( "contextConfigLocation" ) ; String s = doLoadconfig ( contextConfigLocation) ; doScan ( s) ; doInstance ( ) ; doAutoWired ( ) ; initHandlerMapping ( ) ; System . out. println ( "初始化完成...,等待請求與映射匹配了" ) ; } private void initHandlerMapping ( ) { } private void doAutoWired ( ) { } private void doInstance ( ) { } private void doScan ( String path) { } private String doLoadconfig ( String contextConfigLocation) { return "" ; } @Override protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { doPost ( req, resp) ; } @Override protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { }
}
實際上在上面使用靜態(tài)塊也可以操作,只是沒有對應的請求數(shù)據(jù),所以我們使用初始化,一般情況下,我們需要xml相關(guān)依賴來讀取對應的xml信息:
那么我們加上如下的依賴:
< dependency> < groupId> dom4j</ groupId> < artifactId> dom4j</ artifactId> < version> 1.6.1</ version> </ dependency> < dependency> < groupId> jaxen</ groupId> < artifactId> jaxen</ artifactId> < version> 1.1.6</ version> </ dependency>
在真正編寫之前,我們需要補充一些代碼,在framework包下,創(chuàng)建controller包,然后在里面創(chuàng)建如下的類:
package com. mvc. framework. controller ; import com. mvc. framework. annotations. Autowired ;
import com. mvc. framework. annotations. Controller ;
import com. mvc. framework. annotations. RequestMapping ;
import com. mvc. framework. service. DemoService ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; @Controller
@RequestMapping ( "/demo" )
public class DemoController { @Autowired private DemoService demoService; @RequestMapping ( "/query" ) public String query ( HttpServletRequest request, HttpServletResponse response, String name) { String s = demoService. get ( name) ; return s; } }
然后在DispatcherServlet中添加如下的代碼:
package com. mvc. framework. servlet ; import com. mvc. framework. annotations. Autowired ;
import com. mvc. framework. annotations. Controller ;
import com. mvc. framework. annotations. RequestMapping ;
import com. mvc. framework. annotations. Service ;
import org. dom4j. Document ;
import org. dom4j. Element ;
import org. dom4j. io. SAXReader ; 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. File ;
import java. io. IOException ;
import java. io. InputStream ;
import java. lang. reflect. Field ;
import java. lang. reflect. Method ;
import java. net. URLDecoder ;
import java. nio. charset. StandardCharsets ;
import java. util. ArrayList ;
import java. util. HashMap ;
import java. util. List ;
import java. util. Map ;
public class DispatcherServlet extends HttpServlet { private static List < String > classNames = new ArrayList < > ( ) ; private static Map < String , Object > map = new HashMap < > ( ) ; private static List < String > fieldsAlreayProcessed = new ArrayList < > ( ) ; private static Map < String , Method > handlerMapping = new HashMap < > ( ) ; @Override public void init ( ServletConfig config) { String contextConfigLocation = config. getInitParameter ( "contextConfigLocation" ) ; String s = doLoadconfig ( contextConfigLocation) ; doScan ( s) ; doInstance ( ) ; doAutoWired ( ) ; initHandlerMapping ( ) ; System . out. println ( "初始化完成...,等待請求與映射匹配了" ) ; } private void initHandlerMapping ( ) { if ( map. isEmpty ( ) ) { return ; } for ( Map. Entry < String , Object > entry : map. entrySet ( ) ) { Class < ? > aClass = entry. getValue ( ) . getClass ( ) ; if ( aClass. isAnnotationPresent ( Controller . class ) ) { String baseUrl = "" ; if ( aClass. isAnnotationPresent ( RequestMapping . class ) ) { String value = aClass. getAnnotation ( RequestMapping . class ) . value ( ) ; baseUrl += value; } Method [ ] methods = aClass. getMethods ( ) ; for ( int j = 0 ; j < methods. length; j++ ) { Method method = methods[ j] ; if ( method. isAnnotationPresent ( RequestMapping . class ) ) { RequestMapping annotation = method. getAnnotation ( RequestMapping . class ) ; String value = annotation. value ( ) ; String url = baseUrl; url += value; handlerMapping. put ( url, method) ; } } } } } private void doAutoWired ( ) { if ( map. isEmpty ( ) ) { return ; } for ( Map. Entry < String , Object > entry : map. entrySet ( ) ) { try { doObjectDependancy ( entry. getValue ( ) ) ; } catch ( Exception e) { e. printStackTrace ( ) ; } } } private static void doObjectDependancy ( Object object) { Field [ ] declaredFields = object. getClass ( ) . getDeclaredFields ( ) ; if ( declaredFields == null || declaredFields. length == 0 ) { return ; } for ( int i = 0 ; i < declaredFields. length; i++ ) { Field declaredField = declaredFields[ i] ; if ( ! declaredField. isAnnotationPresent ( Autowired . class ) ) { continue ; } if ( fieldsAlreayProcessed. contains ( object. getClass ( ) . getName ( ) + "." + declaredField. getName ( ) ) ) { continue ; } Object dependObject = null ; Autowired annotation = declaredField. getAnnotation ( Autowired . class ) ; String value = annotation. value ( ) ; if ( "" . equals ( value. trim ( ) ) ) { dependObject = map. get ( declaredField. getType ( ) . getName ( ) ) ; if ( dependObject == null ) { dependObject = map. get ( lowerFirst ( declaredField. getType ( ) . getSimpleName ( ) ) ) ; } } else { dependObject = map. get ( value+ declaredField. getType ( ) . getName ( ) ) ; }
上面的代碼在保存實例時,保存了與spring完全不一樣的模式,是保存相關(guān)的任何處理:
在給出之前,先看看如下:
package com. mvc. framework. servlet ; public class a extends b implements f { public static void main ( String [ ] args) { getkeyClass ( a. class ) ; } private static void getkeyClass ( Class a) { Class superclass = a. getSuperclass ( ) ; if ( "java.lang.Object" . equals ( superclass. getName ( ) ) ) { System . out. println ( a. getName ( ) ) ; } else { System . out. println ( a. getName ( ) ) ; getkeyClass ( superclass) ; } Class [ ] interfaces = a. getInterfaces ( ) ; for ( Class anInterface : interfaces) { getkeyInterface ( anInterface) ; } } private static void getkeyInterface ( Class a) { Class [ ] interfaces1 = a. getInterfaces ( ) ; if ( interfaces1. length <= 0 ) { System . out. println ( a. getName ( ) ) ; return ; } System . out. println ( a. getName ( ) ) ; Class aClass = interfaces1[ 0 ] ; getkeyInterface ( aClass) ; }
}
package com. mvc. framework. servlet ; public class b implements c{
}
package com. mvc. framework. servlet ; public interface c extends d{
}
package com. mvc. framework. servlet ; public interface d {
}
package com. mvc. framework. servlet ; public interface f extends g{
}
package com. mvc. framework. servlet ; public interface g {
}
對應執(zhí)行的結(jié)果是拿取其所有父類情況的全限定名,這個時候我們看這里:
dependObject = map. get ( value+ declaredField. getType ( ) . getName ( ) ) ;
他是根據(jù)對應的類型來的,而由于多態(tài),那么可能存在非常多的類型,這也是我們這里的處理方式,而不是spring的遍歷,或者指定名稱(注意:在spring中,指定多個相同實例名稱時也會報錯,在id哪里,你也會或多或少的知道什么)
那么在保存時應該是在這里:
在這之前,我們首先在framework包下,創(chuàng)建util包,然后在里面創(chuàng)UtilGetClassInterfaces類加上如下:
package com. mvc. framework. util ; import java. util. Map ; public class UtilGetClassInterfaces { public static void getkeyClass ( String beanName, Class a, Map map, Object o) { Class superclass = a. getSuperclass ( ) ; if ( "java.lang.Object" . equals ( superclass. getName ( ) ) ) { map. put ( beanName + a. getName ( ) , o) ; } else { map. put ( beanName + a. getName ( ) , o) ; getkeyClass ( beanName, superclass, map, o) ; } Class [ ] interfaces = a. getInterfaces ( ) ; for ( Class anInterface : interfaces) { getkeyInterface ( beanName, anInterface, map, o) ; } } private static void getkeyInterface ( String beanName, Class a, Map map, Object o) { Class [ ] interfaces1 = a. getInterfaces ( ) ; if ( interfaces1. length <= 0 ) { map. put ( beanName + a. getName ( ) , o) ; return ; } map. put ( beanName + a. getName ( ) , o) ; Class aClass = interfaces1[ 0 ] ; getkeyInterface ( beanName, aClass, map, o) ; } }
在對應的代碼中可以看到:
if ( ju== 1 ) { UtilGetClassInterfaces . getkeyClass ( beanName, aClass, map, o) ; } else { map. put ( beanName, o) ; }
這里就是解決的辦法,但是如果這種辦法是好的,為什么spring不使用呢,其實我們也可以看到,他們是各有利弊的,但是spring并沒有隱患,或者隱患很小,而這種有隱患,取決于對應保存的相同的對象非常多,那么出現(xiàn)操作相同對象時也會出現(xiàn)共享的問題,當然,這些問題并不大,因為實例通常只是提供方法操作而已,所以這也算是解決的辦法
上面的代碼,需要仔細看看,當然,如果出現(xiàn)問題,后面也會給出的,通過上面的說明,可以發(fā)現(xiàn),在doPost方法中,出現(xiàn)問題了,其中就是路徑的問題,一般我們可以這樣的解決:
String requestURI = req. getRequestURI ( ) ; String contextPath = req. getContextPath ( ) ; System . out. println ( "項目名稱:" + contextPath) ; String substring = requestURI. substring ( contextPath. length ( ) , requestURI. length ( ) ) ; System . out. println ( "拿取的路徑:" + substring) ; Method method = handlerMapping. get ( substring) ;
當然,一般來說,對應的/demo/query中,可能存在就算你不寫/demo,只是寫demo的情況(只需要判斷開頭的情況,因為其他情況會默認看成路徑的),而在mvc中,通常是默認加上的,所以我們可以修改initHandlerMapping方法:
在這之前,我們需要考慮一件事,你可能也從來沒有考慮過,也就是說,不在方法上加上RequestMapping注解,只在類上加上,并且單純的訪問類上的路徑,那么他會訪問到這個方法嗎,答:并不會,一般會報錯,在mvc中就是如此,而這里,我們可以看到,如果類上的注解有,那么繼續(xù)往下走,只有存在對應的注解(方法上)的才會進行保存映射,然而這里我們還沒有進行處理是否報錯的問題,因為我們還沒有寫上,這里我們后面考慮,先考慮"/"的情況:
String value = aClass. getAnnotation ( RequestMapping . class ) . value ( ) ; if ( "/" . equals ( value. substring ( 0 , 1 ) ) == false ) { value = "/" + value; }
String value = annotation. value ( ) ; if ( "/" . equals ( value. substring ( 0 , 1 ) ) == false ) { value = "/" + value; }
這兩個地方寫上,來完成默認加上"/"的處理
針對上面的考慮的一件事的問題,其實最終的得到的值是null(因為只有存在對應的注解(方法上)的才會進行保存映射,而map在沒有對應的key時,返回值的value自然就是null),考慮null是否報錯即可(后面會給出的)
通過上面我們得到了一個Method,我們先看一個案例:
package com. mvc. framework. servlet ; import java. lang. reflect. Method ; public class a { public void fa ( String a, Integer b) { System . out. println ( 1 + a + b) ; } public void fb ( ) { System . out. println ( 1 ) ; } public static void main ( String [ ] args) { try { Class < a> aClass = a. class ; a a = aClass. newInstance ( ) ; Method [ ] methods = aClass. getMethods ( ) ; for ( int i = 0 ; i < methods. length; i++ ) { System . out. println ( methods[ i] . getName ( ) ) ; } Method fa = aClass. getMethod ( "fb" ) ; fa. invoke ( a) ; Method fb = aClass. getMethod ( "fa" , String . class , Integer . class ) ; fb. invoke ( a, "2" , 1 ) ; } catch ( Exception e) { e. printStackTrace ( ) ; } }
}
我們可以發(fā)現(xiàn),要執(zhí)行invoke的處理來調(diào)用類方法,我們需要一個類對象,也就是當前的對應類對象,對應與mvc中的DemoController類對象,并且有時候也需要參數(shù),所以我們需要思考這里,由于map中,幾乎只能保存一個信息,那么這里很明顯,我們需要創(chuàng)建一個(實體)類:
我們在framework包下,創(chuàng)建pojo包,然后創(chuàng)建Handler類:
package com. mvc. framework. pojo ; import java. lang. reflect. Method ;
import java. util. HashMap ;
import java. util. Map ;
import java. util. regex. Pattern ; public class Handler { private Object controller; private Method method; private Pattern pattern; private Map < String , Integer > paramIndexMapping; public Handler ( Object controller, Method method, Pattern pattern) { this . controller = controller; this . method = method; this . pattern = pattern; this . paramIndexMapping = new HashMap < > ( ) ; } public Object getController ( ) { return controller; } public void setController ( Object controller) { this . controller = controller; } public Method getMethod ( ) { return method; } public void setMethod ( Method method) { this . method = method; } public Pattern getPattern ( ) { return pattern; } public void setPattern ( Pattern pattern) { this . pattern = pattern; } public Map < String , Integer > getParamIndexMapping ( ) { return paramIndexMapping; } public void setParamIndexMapping ( Map < String , Integer > paramIndexMapping) { this . paramIndexMapping = paramIndexMapping; }
}
現(xiàn)在我們修改initHandlerMapping方法的內(nèi)容(我這里將注釋去掉了,自己對應一下):
前提,我們將之前創(chuàng)建的private static Map<String, Method> handlerMapping = new HashMap<>();注釋掉,因為不使用這個了,而是使用這個:
private List < Handler > handlerMapping = new ArrayList < > ( ) ;
還有一個前提,我們需要知道這個:
package com. mvc. framework. servlet ; import java. util. regex. Matcher ;
import java. util. regex. Pattern ; public class a { public static void main ( String [ ] args) { String a = "[0-9]{3}" ; String b = "122" ; CharSequence aa = b; System . out. println ( b. matches ( a) ) ; Pattern pattern = Pattern . compile ( "[0-9]{3}" ) ; Matcher m = pattern. matcher ( "122" ) ; boolean matches = m. matches ( ) ; System . out. println ( matches) ; System . out. println ( pattern) ; }
}
現(xiàn)在開始修改:
if ( method. isAnnotationPresent ( RequestMapping . class ) ) { RequestMapping annotation = method. getAnnotation ( RequestMapping . class ) ; String value = annotation. value ( ) ; if ( "/" . equals ( value. substring ( 0 , 1 ) ) == false ) { value = "/" + value; } String url = baseUrl; url += value; Handler handler = new Handler ( entry. getValue ( ) , method, Pattern . compile ( url) ) ; Parameter [ ] parameters = method. getParameters ( ) ; for ( int i = 0 ; i < parameters. length; i++ ) { Parameter parameter = parameters[ i] ; if ( parameter. getType ( ) == HttpServletRequest . class || parameter. getType ( ) == HttpServletResponse . class ) { handler. getParamIndexMapping ( ) . put ( parameter. getType ( ) . getSimpleName ( ) , i) ; } else { handler. getParamIndexMapping ( ) . put ( parameter. getName ( ) , i) ; } } handlerMapping. add ( handler) ; }
自己對應一下吧,然后我們修改doPost方法:
到這里我們可以知道框架的作用了,他是定義編寫代碼方式以及方便代碼編寫,而中間件是在編寫好的代碼基礎上的一個補充或者增強
由于單純的代碼都寫在doPost方法里面比較麻煩,所以我們定義一個方法(然而我也只是定義了一個小的方法),看如下就知道了:
在之前,我們需要知道這個:
package com. mvc. framework. servlet ; import java. lang. reflect. Method ; public class a { public void fa ( String a, Integer b, int j) { } public static void main ( String [ ] args) throws Exception { Class < a> aClass = a. class ; Method fa = aClass. getMethod ( "fa" , String . class , Integer . class , int . class ) ; Class < ? > [ ] parameterTypes = fa. getParameterTypes ( ) ; for ( int i = 0 ; i < parameterTypes. length; i++ ) { Class < ? > parameterType = parameterTypes[ i] ; System . out. println ( parameterType. getSimpleName ( ) ) ; } }
}
@Override protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { Handler handler = getHandler ( req) ; if ( handler == null ) { resp. getWriter ( ) . write ( "404 not found" ) ; return ; } Class < ? > [ ] parameterTypes = handler. getMethod ( ) . getParameterTypes ( ) ; Object [ ] objects = new Object [ parameterTypes. length] ; int [ ] ii = new int [ parameterTypes. length] ; Map < String , String [ ] > parameterMap = req. getParameterMap ( ) ; Set < Map. Entry < String , String [ ] >> entries = parameterMap. entrySet ( ) ; for ( Map. Entry < String , String [ ] > param : entries) { String value = "" ; for ( int i = 0 ; i < param. getValue ( ) . length; i++ ) { if ( i >= param. getValue ( ) . length - 1 ) { value = param. getValue ( ) [ i] ; continue ; } value = param. getValue ( ) [ i] + "," ; } if ( ! handler. getParamIndexMapping ( ) . containsKey ( param. getKey ( ) ) ) { continue ; } Integer integer = handler. getParamIndexMapping ( ) . get ( param. getKey ( ) ) ; if ( "String" . equals ( parameterTypes[ integer] . getSimpleName ( ) ) ) { objects[ integer] = value; } if ( "Integer" . equals ( parameterTypes[ integer] . getSimpleName ( ) ) || "int" . equals ( parameterTypes[ integer] . getSimpleName ( ) ) ) { value = value. split ( "," ) [ 0 ] ; Integer i = null ; try { i = Integer . parseInt ( value) ; } catch ( Exception e) { e. printStackTrace ( ) ; throw new RuntimeException ( "String轉(zhuǎn)換Integet報錯,參數(shù)名稱是:" + param. getKey ( ) ) ; } objects[ integer] = i; } ii[ integer] = 1 ; } Integer integer = handler. getParamIndexMapping ( ) . get ( HttpServletRequest . class . getSimpleName ( ) ) ; objects[ integer] = req; ii[ integer] = 1 ; integer = handler. getParamIndexMapping ( ) . get ( HttpServletResponse . class . getSimpleName ( ) ) ; objects[ integer] = resp; ii[ integer] = 1 ; for ( int i = 0 ; i < ii. length; i++ ) { if ( ii[ i] == 0 ) { if ( "int" . equals ( parameterTypes[ i] . getSimpleName ( ) ) ) { objects[ i] = 0 ; } } } try { handler. getMethod ( ) . invoke ( handler. getController ( ) , objects) ; } catch ( Exception e) { e. printStackTrace ( ) ; } } private Handler getHandler ( HttpServletRequest req) { if ( handlerMapping. isEmpty ( ) ) { return null ; } String requestURI = req. getRequestURI ( ) ; String contextPath = req. getContextPath ( ) ; String substring = requestURI. substring ( contextPath. length ( ) , requestURI. length ( ) ) ; for ( Handler handler : handlerMapping) { Matcher matcher = handler. getPattern ( ) . matcher ( substring) ; if ( ! matcher. matches ( ) ) { continue ; } return handler; } return null ; }
上面解決了mvc中int的默認賦值null的錯誤,但是我們也可以發(fā)現(xiàn),他需要循環(huán),mvc使用報錯來解決也并不是不行,節(jié)省一點性能,當然,這點性能也并不大,所以可以認為是mvc的一個疏忽,然而這些疏忽并不是很影響具體開發(fā),所以mvc就不操作改變了
至此,我們可以選擇來測試一下:
我們回到這里:
package com. mvc. framework. controller ; import com. mvc. framework. annotations. Autowired ;
import com. mvc. framework. annotations. Controller ;
import com. mvc. framework. annotations. RequestMapping ;
import com. mvc. framework. service. DemoService ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; @Controller
@RequestMapping ( "/demo" )
public class DemoController { @Autowired private DemoService demoService; @RequestMapping ( "/query" ) public String query ( HttpServletRequest request, HttpServletResponse response, String name) { String s = demoService. get ( name) ; return s; } }
修改一下:
@RequestMapping ( "/query" ) public String query ( HttpServletRequest request, HttpServletResponse response, String name) { System . out. println ( request) ; System . out. println ( response) ; String s = demoService. get ( name) ; return s; }
然后修改這個:
package com. mvc. framework. service. impl ; import com. mvc. framework. annotations. Service ;
import com. mvc. framework. service. DemoService ; @Service
public class DemoServiceImpl implements DemoService { @Override public String get ( String name) { System . out. println ( "打印:" + name) ; return name; }
}
然后啟動服務器,訪問這個:http://localhost:8080/springmvc_xie/demo/query?name=1234,其中springmvc_xie是項目名稱
訪問后你會發(fā)現(xiàn),打印的值是null,為什么,通過我的調(diào)試,對應的這個地方得到的名稱不是參數(shù)名稱:
if ( parameter. getType ( ) == HttpServletRequest . class || parameter. getType ( ) == HttpServletResponse . class ) { handler. getParamIndexMapping ( ) . put ( parameter. getType ( ) . getSimpleName ( ) , i) ; } else { handler. getParamIndexMapping ( ) . put ( parameter. getName ( ) , i) ; }
經(jīng)過上面的問題,我們來創(chuàng)建一個類:
package com. mvc. framework. servlet ; import java. lang. reflect. Method ;
import java. lang. reflect. Parameter ; public class a { public void fa ( String nam2, String name1) { } public void fb ( String nam2, String name1) { } public static void main ( String [ ] args) { Class < a> aClass = a. class ; Method [ ] fa = aClass. getMethods ( ) ; for ( int i = 0 ; i < fa. length; i++ ) { Method method = fa[ i] ; System . out. println ( method. getName ( ) ) ; Parameter [ ] parameters = method. getParameters ( ) ; for ( int ii = 0 ; ii < parameters. length; ii++ ) { Parameter parameter = parameters[ ii] ; System . out. println ( parameter. getName ( ) ) ; } } }
}
為什么會這樣,這一般需要看版本,一般在jdk8開始就這樣處理了,當然,這樣的還有私有的,也就是設置可以訪問private變量的變量值,在jdk8之前可能不用設置,但是之后(包括jdk8)不能直接的訪問私有屬性了(可能隨著時間的推移,也會改變),因為需要進行設置這個,所以不能直接訪問私有屬性了
為什么要這樣處理:這是為了提高字節(jié)碼的緊湊性和安全性,參數(shù)名稱在編譯后被抹去,主要有以下原因:
隱私和安全性:在字節(jié)碼中包含參數(shù)名稱可能泄漏程序的細節(jié)信息,可能會被濫用,這對于某些應用來說是不可接受的,因為它可以暴露敏感信息
字節(jié)碼緊湊性:字節(jié)碼文件通常被用于分發(fā)和執(zhí)行,如果包含了大量參數(shù)名稱,將會增加文件大小,在資源受限的環(huán)境中,較小的字節(jié)碼文件可以提高性能和減小存儲需求
字節(jié)碼的緊湊型還比較合理,但是為什么字節(jié)碼會影響安全,舉例:
反編譯和逆向工程:如果參數(shù)名稱包含在字節(jié)碼中,惡意用戶或攻擊者可以更容易地反編譯和逆向工程你的應用程序,以獲取關(guān)于應用程序的內(nèi)部結(jié)構(gòu)和邏輯的信息,這可以幫助他們發(fā)現(xiàn)潛在的漏洞或弱點,甚至濫用應用程序
敏感信息泄露:在某些情況下,方法的參數(shù)名稱可能包含敏感信息,如果這些名稱泄露到字節(jié)碼中,可能會暴露敏感數(shù)據(jù)、業(yè)務邏輯或應用程序內(nèi)部細節(jié),從而引發(fā)安全風險
安全性問題披露:如果方法參數(shù)名稱包含有關(guān)應用程序內(nèi)部的信息,攻擊者可能會更容易發(fā)現(xiàn)應用程序的潛在漏洞,從而加大應用程序受攻擊的風險
當然,spring是解決的或者說mvc是解決的(這里我們稱為spring,因為最終是使用spring的主要內(nèi)容的),但是他是通過其他方式,所以并不會暴露,然而,對方也可以通過spring來獲取該參數(shù),所以也只是避免了方便的獲取,那么在不使用spring時,他既然會出現(xiàn)arg0這樣的名稱,那么有沒有操作讓他不這樣做,其實是有的,只需要加上這個操作:
- parameters,編譯選項
一般情況他是操作編譯的,那么應該是如此的:javac -parameters YourClass.java,然而我們是操作項目,不可能這樣處理,應該需要配置來使得項目默認這樣處理,這里我們就以maven為例:
在maven中的pom.xml中操作如下:
< build> < plugins> < plugin> < groupId> org.apache.maven.plugins</ groupId> < artifactId> maven-compiler-plugin</ artifactId> < version> 3.8.0</ version> < configuration> < source> 11</ source> < target> 11</ target> < compilerArguments> < parameters /> </ compilerArguments> </ configuration> </ plugin> </ plugins> </ build>
執(zhí)行后,可以發(fā)現(xiàn),他獲得了對應的參數(shù)名稱,我們試一下修改版本,將上面的11修改成1.8,執(zhí)行后,可以發(fā)現(xiàn),也獲得了參數(shù)名稱了,當然,也并不是說任何版本都可以這樣,可能有些版本這樣的操作是不行的(現(xiàn)在還不確定,看以后,并且隨著jdk的變化(并不是說對方不會對1.8或者11繼續(xù)修改),可能1.8或者11也會不行),但是我們也可以發(fā)現(xiàn),在之前操作mvc中,我們并沒有處理這個,也就是說明了spring并不是使用這個方式,那么他是使用什么方式呢:
spring是使用字節(jié)碼分析庫(如 ASM)來動態(tài)分析類文件,而不依賴于編譯器生成的字節(jié)碼,這種技術(shù)使 Spring 能夠獲取參數(shù)名稱,因為他是直接分析文件的,也就可以說,他內(nèi)部的代碼是相當于跳過了操作了-parameters選項,而訪問并處理字節(jié)碼文件(實際上再怎么處理字節(jié)碼文件中都會保留原始參數(shù)名稱,否則的話,也不可能使用該名稱的變量了,他只是不讓你直接獲取即可,那么如果說選項中他-parameters是解決某一個開關(guān)導致的不讓獲取,那么spring是直接從文件找,也自然就跳過了這個開關(guān))
那么就明白了,也就是直接的分析字節(jié)碼文件,好吧,這對現(xiàn)在的我們來說幾乎不可能手動寫好,因為需要學習太多知識,并且細節(jié)非常多,基本不是我們個人能夠完成的,所以我們拿取spring提供給我們的這個操作代碼,但是引入spring一般我們并不能找到對應的類,所以我們選擇使用第三方的代碼,思考:為什么main的參數(shù)需要是對應的args數(shù)組:一般他代表命令行參數(shù),這取決于對應的main是程序的入口,一般我們可以在命令行中執(zhí)行java時傳遞,比如我們有一個java文件:
public class ceshi { public static void main ( String [ ] args) { for ( String arg : args) { System . out. println ( arg) ; } }
}
在命令行中(在目錄下),執(zhí)行javac ceshi.java,然后執(zhí)行java ceshi,發(fā)現(xiàn)什么都沒有,但是如果是這樣的執(zhí)行:java ceshi 1 2 3,那么會打印1和2和3,具體自行測試,也就是說他存在這樣的作用:
命令行參數(shù)的傳遞:命令行參數(shù)是用戶在終端或命令提示符中輸入的,它們以字符串的形式傳遞給程序,因此,main函數(shù)需要一個字符串數(shù)組來接收這些參數(shù),以便程序能夠解析和使用它們
靈活性:通過使用命令行參數(shù),程序可以根據(jù)用戶提供的輸入進行不同的操作,參數(shù)的個數(shù)和內(nèi)容可以根據(jù)需要進行調(diào)整,從而增加程序的靈活性
標準化:采用參數(shù)數(shù)組的方式,使得命令行參數(shù)的處理在不同的編程語言和操作系統(tǒng)中更加一致和標準化,這有助于開發(fā)人員編寫跨平臺的代碼
現(xiàn)在我們修改1.8回到11(看你自己,可以選擇修改),開始使用spring的方式,這里我們選擇拿取第三方的代碼(這里我們使用ASM,spring也是使用類似的或者相同的),先在framework包下創(chuàng)建包config,然后在該包下我們創(chuàng)建幾個類(在spring中有類似的這樣的處理,這里我們手動寫一個),這幾個類在后面會給出
在手寫之前,我們使用上面的配置,啟動運行一下,然后操作mvc看看結(jié)果,如果出現(xiàn)了打印的值不是null,而是1234,那么我們可以說初步完成(這個時候,你可以選擇給@Service操作value,然后@Autowired對應(在spring中,我們并沒有這樣的處理,這里我們是這樣處理的),經(jīng)過測試,也打印了1234,也可以測試將String name變成int name,如果打印為0,說明也操作成功(我們的方式,而不是Spring的mvc的報錯),然后我們將路徑中,比如:/demo的開頭的/去掉,訪問,可以發(fā)現(xiàn),也可以,那么說明我們的操作代碼編寫完畢,當然,可能有些地方有問題,但是暫時沒有,且功能都正確,如果出現(xiàn)了,請自行修改一下吧),然后我們使用spring對應的方式來解決:
首先我們需要引入依賴:
< dependency> < groupId> org.ow2.asm</ groupId> < artifactId> asm</ artifactId> < version> 9.2</ version> </ dependency>
然后去掉這個依賴配置:
< build> < plugins> < plugin> < groupId> org.apache.maven.plugins</ groupId> < artifactId> maven-compiler-plugin</ artifactId> < version> 3.8.0</ version> < configuration> < source> 1.8</ source> < target> 1.8</ target> < compilerArguments> < parameters /> </ compilerArguments> </ configuration> </ plugin> </ plugins> </ build>
執(zhí)行服務器,先看看是否得到結(jié)果,很明顯,沒有得到,并且加上這個類來測試時,也是沒有得到參數(shù)名稱,這個類是之前的:
package com. mvc. framework. servlet ; import java. lang. reflect. Method ;
import java. lang. reflect. Parameter ; public class a { public void fa ( String nam2, String name1) { } public void fb ( String nam2, String name1) { } public static void main ( String [ ] args) { Class < a> aClass = a. class ; Method [ ] fa = aClass. getMethods ( ) ; for ( int i = 0 ; i < fa. length; i++ ) { Method method = fa[ i] ; System . out. println ( method. getName ( ) ) ; Parameter [ ] parameters = method. getParameters ( ) ; for ( int ii = 0 ; ii < parameters. length; ii++ ) { Parameter parameter = parameters[ ii] ; System . out. println ( parameter. getName ( ) ) ; } } }
}
這個時候,我們使用上面的依賴來解決不加這個配置的問題:
首先我們在framework包下,找到前面我們創(chuàng)建的config包(沒有,現(xiàn)在創(chuàng)建也行),然后創(chuàng)建下面兩個(前面的幾個)類:
package com. mvc. framework. config ; import org. objectweb. asm. ClassVisitor ;
import org. objectweb. asm. Label ;
import org. objectweb. asm. MethodVisitor ;
import org. objectweb. asm. Opcodes ; import java. util. HashMap ;
import java. util. Map ; public class ParameterNameVisitor extends ClassVisitor { private String methodName; private Map map; public ParameterNameVisitor ( String methodName) { super ( Opcodes . ASM7 ) ; this . methodName = methodName; } @Override public MethodVisitor visitMethod ( int access, String name, String descriptor, String signature, String [ ] exceptions) { if ( name. equals ( methodName) ) { return new MethodParameterVisitor ( ) ; } return null ; } public Map getParameterNames ( ) { return map; } class MethodParameterVisitor extends MethodVisitor { public MethodParameterVisitor ( ) { super ( Opcodes . ASM7 ) ; } @Override public void visitLocalVariable ( String name, String descriptor, String signature, Label start, Label end, int index) { if ( index >= 0 ) { if ( map == null ) { map = new HashMap < > ( ) ; } map. put ( index - 1 , name) ; } } }
}
package com. mvc. framework. config ; import org. objectweb. asm. * ; import java. util. Map ; public class ParameterNameExtractor { public static Map getParameterNames ( String className, String methodName) throws Exception { ClassReader reader = new ClassReader ( className) ; ParameterNameVisitor visitor = new ParameterNameVisitor ( methodName) ; reader. accept ( visitor, 0 ) ; return visitor. getParameterNames ( ) ; }
}
然后我們改造這個a類:
package com. mvc. framework. servlet ; import com. mvc. framework. config. ParameterNameExtractor ;
import java. util. Map ; public class a { String name; public a ( String name) { this . name = name; } public a ( String name, String j) { this . name = name; } public void fa ( String nam2, String name1) { } public void fb ( String nam2, String name1) { } public static void main ( String [ ] args) throws Exception { String className = "com.mvc.framework.servlet.a" ; String methodName = "fa" ; Map map = ParameterNameExtractor . getParameterNames ( className, methodName) ; for ( int i = 0 ; i < map. size ( ) ; i++ ) { System . out. println ( "參數(shù)列表的位置:" + i + ",名稱是: " + map. get ( i) ) ; } methodName = "fb" ; map = ParameterNameExtractor . getParameterNames ( className, methodName) ; for ( int i = 0 ; i < map. size ( ) ; i++ ) { System . out. println ( "參數(shù)列表的位置:" + i + ",名稱是: " + map. get ( i) ) ; } }
}
執(zhí)行后,可以發(fā)現(xiàn),得到了參數(shù)列表,那么我們修改mvc的對應的方法,也就是這里:
Map < String , String > prmap = null ; try { prmap = ParameterNameExtractor . getParameterNames ( aClass. getName ( ) , method. getName ( ) ) ; } catch ( Exception e) { e. printStackTrace ( ) ; } Parameter [ ] parameters = method. getParameters ( ) ; for ( int i = 0 ; i < parameters. length; i++ ) { Parameter parameter = parameters[ i] ; if ( parameter. getType ( ) == HttpServletRequest . class || parameter. getType ( ) == HttpServletResponse . class ) { handler. getParamIndexMapping ( ) . put ( parameter. getType ( ) . getSimpleName ( ) , i) ; } else { handler. getParamIndexMapping ( ) . put ( prmap. get ( i) , i) ; } } handlerMapping. add ( handler) ;
執(zhí)行服務器,訪問后,可以發(fā)現(xiàn),打印值是null,即沒有出來,為什么:通過調(diào)試,我們可以發(fā)現(xiàn),對應的值是完全可以的,但是報錯了,為了找到問題,這里可以給出測試,首先在a類里面操作或者修改如下:
package com. mvc. framework. servlet ; import com. mvc. framework. config. ParameterNameExtractor ; import java. util. Map ; public class a { public static void main ( String [ ] args) throws Exception { String className = "com.mvc.framework.controller.DemoController" ; String methodName = "query" ; Map map = ParameterNameExtractor . getParameterNames ( className, methodName) ; for ( int i = 0 ; i < map. size ( ) ; i++ ) { System . out. println ( "參數(shù)列表的位置:" + i + ",名稱是: " + map. get ( i) ) ; } }
}
執(zhí)行后,可以得到對應的參數(shù)列表,然后再mvc(我們的模擬)中的init中加上如下(注意:先把他里面的調(diào)用都注釋):
@Override public void init ( ServletConfig config) { String className = "com.mvc.framework.controller.DemoController" ; String methodName = "query" ; Map map = ParameterNameExtractor . getParameterNames ( className, methodName) ; for ( int i = 0 ; i < map. size ( ) ; i++ ) { System . out. println ( "參數(shù)列表的位置:" + i + ",名稱是: " + map. get ( i) ) ; } }
然后執(zhí)行,發(fā)現(xiàn)就是對應我們啟動服務器的處理,那么肯定的是對應的服務器的關(guān)系,并且一般這個錯誤是Class not found,且錯誤發(fā)生再ClassReader reader = new ClassReader(className);:
我們看看他的源碼:
public ClassReader ( String className) throws IOException { this ( readStream ( ClassLoader . getSystemResourceAsStream ( className. replace ( '.' , '/' ) + ".class" ) , true ) ) ; } private static byte [ ] readStream ( InputStream inputStream, boolean close) throws IOException { if ( inputStream == null ) { throw new IOException ( "Class not found" ) ; } else { . . .
可以發(fā)現(xiàn)這樣的:
經(jīng)過上面的說明,我們還需要修改對應的方法,修改如下:
private void initHandlerMapping ( ServletConfig config) { String ba = aClass. getName ( ) . replace ( '.' , '/' ) + ".class" ; prmap = ParameterNameExtractor . getParameterNames ( config. getServletContext ( ) . getRealPath ( "/" ) + "WEB-INF\\classes\\" + ba, method. getName ( ) ) ; InputStream inputStream = new FileInputStream ( className) ; ClassReader reader = new ClassReader ( inputStream) ;
修改后,我們執(zhí)行,訪問一下看看結(jié)果,發(fā)現(xiàn)打印對應的值出來了,說明我們操作成功
至此,我們可以說mvc的模擬框架編寫完成,到這里你會發(fā)現(xiàn),但凡與結(jié)構(gòu)相關(guān)的,基本上反射都可以做到,也就是說注解他是一個結(jié)構(gòu),那么可以絕對的說,注解就是由反射來完成操作關(guān)聯(lián)的(其實被注解操作的方法大多數(shù)只是定義(如參數(shù)列表),具體可能由反射來調(diào)用或者說操作),并且但凡需要與結(jié)構(gòu)信息相關(guān)聯(lián)的,反射也都可以做到,所以以后,如果出現(xiàn)與結(jié)構(gòu)進行關(guān)聯(lián)操作的,那么使用反射吧
到這里,應該很明顯知道,比spring要困難多了吧,spring只需要考慮循環(huán)依賴,而這里除了考慮spring,還需要考慮很多的細節(jié),特別的,是字節(jié)碼文件的解析問題,以及反射的細節(jié)使用,還有保存對應的關(guān)聯(lián)
當然,上面的編寫只是mvc的一部分,特別的,關(guān)于視圖方面的以及攔截方面的(如我們好像并沒有判斷處理/*的操作)我們也沒有給出,所以說才只是一部分
由于博客字數(shù)限制,其他內(nèi)容,請到下一篇博客(112章博客)去看