小伙伴私信說他最近自己在鼓搗spring mvc小項目,但是框架搭起來后無法解決靜態文件讀取問題,我就整理了一下在spring mvc訪問靜態資源的幾種方式
首先為什么會讀取不到靜態資源?
如果你的DispatcherServlet攔截 *.do這樣的URL,就不存在訪問不到靜態資源的問題。如果你的DispatcherServlet攔截“/”,攔截了所有的請求,同時對*.js,*.jpg的訪問也就被攔截了,這就是問題原因。web.xml下對spring的DispatcherServlet請求url映射的配置,原配置如下:
<servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <span style="BACKGROUND-COLOR: #ffff33"><url-pattern>/</url-pattern> </span> </servlet-mapping>
分析原因:<servlet-mapping>的<url-pattern>/</url-pattern>把所有的請求都交給spring去處理了,而所有available的請求url都是在Constroller里使用類似@RequestMapping(value="/login/{user}", method=RequestMethod.GET)這樣的注解配置的,這樣的話對js/css/jpg/gif等靜態資源的訪問就會得不到。
所以我們的的目的:可以正常訪問靜態文件,不要找不到靜態文件報404。
方案一:激活Tomcat的defaultServlet來處理靜態文件
在web.xml里添加如下配置
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping>
要配置多個,每種文件配置一個。
要寫在DispatcherServlet的前面, 讓defaultServlet先攔截,這個就不會進入Spring了,性能應該是最好的吧。
Tomcat, Jetty, JBoss, and GlassFish 默認 Servlet的名字 -- "default"
Google App Engine 默認 Servlet的名字 -- "_ah_default"
Resin 默認 Servlet的名字 -- "resin-file"
WebLogic 默認 Servlet的名字 -- "FileServlet"
WebSphere 默認 Servlet的名字 -- "SimpleFileServlet"
方案二: 在spring3.0.4以后版本提供了mvc:resources
<mvc:resources> 的使用方法:
<!--對靜態資源文件的訪問-->
<mvc:resources mapping="/images/**" location="/images/" />
/images /**映射到 ResourceHttpRequestHandler 進行處理,location指定靜態資源的位置.可以是web application根目錄下、jar包里面,這樣可以把靜態資源壓縮到jar包中。cache-period可以使得靜態資源進行web cache。
如果出現下面的錯誤,可能是沒有配置 <mvc:annotation-driven /> 的原因。
報錯WARNING: No mapping found for HTTP request with URI [/mvc/user/findUser/lisi/770] in DispatcherServlet with name 'springMVC'
使用 <mvc:resources/> 元素,把 mapping 的 URI 注冊到 SimpleUrlHandlerMapping的urlMap 中,key 為 mapping 的 URI pattern值,而value為 ResourceHttpRequestHandler,這樣就巧妙的把對靜態資源的訪問由 HandlerMapping 轉到ResourceHttpRequestHandler 處理并返回,所以就支持 classpath 目錄, jar 包內靜態資源的訪問.另外需要注意的一點是,不要對 SimpleUrlHandlerMapping 設置 defaultHandler. 因為對 static uri 的 defaultHandler 就是ResourceHttpRequestHandler,否則無法處理static resources request.
mapping:映射
location:本地資源路徑,注意必須是webapp根目錄下的路徑。
兩個*,它表示映射resources/下所有的URL,包括子路徑(即接多個/)
配置的location一定要是webapp根目錄下才行,如果你將資源目錄,放置到webapp/WEB-INF下面的話,則就會訪問失敗。這個問題常常會犯。
錯誤方式:
WEB-INF目錄作用
WEB-INF是Java的WEB應用的安全目錄。所謂安全就是客戶端無法訪問,只有服務端可以訪問的目錄。
如果想在頁面中直接訪問其中的文件,必須通過web.xml文件對要訪問的文件進行相應映射才能訪問。
當然,你非要放在WEB-INF中,則必須修改resources映射,如:
<span style="BACKGROUND-COLOR: #ffff33"><mvc:resources mapping="/js/**" location="/WEB-INF/js/" /></span>
推薦方式:maven項目的目錄結構為如下圖所示。
方案三 ,使用<mvc:default-servlet-handler/>
<mvc:default-servlet-handler/>會把 "/**" url,注冊到 SimpleUrlHandlerMapping 的 urlMap 中,把對靜態資源的訪問由 HandlerMapping 轉到 org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler 處理并返回.DefaultServletHttpRequestHandler 使用就是各個 Servlet 容器自己的默認 Servlet.
補充說明:多個HandlerMapping的執行順序問題:
DefaultAnnotationHandlerMapping 的 order 屬性值是:0
<mvc:resources/ >自動注冊的 SimpleUrlHandlerMapping 的 order 屬性值是: 2147483646
<mvc:default-servlet-handler/>自動注冊的 SimpleUrlHandlerMapping 的 order 屬性值是:2147483647
spring 會先執行 order 值比較小的。當訪問一個 a.jpg 圖片文件時,先通過 DefaultAnnotationHandlerMapping 來找處理器,一定是找不到的,我們沒有叫 a.jpg 的 Action。再按 order 值升序找,由于最后一個 SimpleUrlHandlerMapping 是匹配 "/**" 的,所以一定會匹配上,再響應圖片。
springmvc攔截器是我們項目開發中用到的一個功能,常常用于對Handler進行預處理和后處理。本案例來演示一個較簡單的springmvc攔截器的使用,并通過分析源碼來探究攔截器的執行順序是如何控制的。
? 該步驟不再截圖說明
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
? 配置springmvc核心控制器DispatcherServlet,由于需要加載springmvc.xml,所以需要創建一個springmvc.xml文件(文件參考源碼附件)放到classpath下
<!-- 前端控制器(加載classpath:springmvc.xml 服務器啟動創建servlet) -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置初始化參數,創建完DispatcherServlet對象,加載springmvc.xml配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
? 兩個攔截器分別命名為MyInterceptor1、MyInterceptor2
public class MyInterceptor1 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("==1-1====前置攔截器1 執行======");
return true; //ture表示放行
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("==1-2=====后置攔截器1 執行======");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("==1-3======最終攔截器1 執行======");
}
}public class MyInterceptor2 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("==2-1====前置攔截器2 執行======");
return true; //ture表示放行
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("==2-2=====后置攔截器2 執行======");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("==2-3======最終攔截器2 執行======");
}
}
<!--配置攔截器-->
<mvc:interceptors>
<!--配置攔截器-->
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.itheima.interceptor.MyInterceptor1" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.itheima.interceptor.MyInterceptor2" />
</mvc:interceptor>
</mvc:interceptors>
這兩個攔截器攔截規則相同,并且配置順序攔截器1在攔截器2之前!
@Controller
public class BizController {
@RequestMapping("testBiz")
public String showUserInfo(Integer userId, Model model){
System.out.println(">>>>>業務代碼執行-查詢用戶ID為:"+ userId);
User user=new User(userId);
user.setName("宙斯");
model.addAttribute("userInfo",user);
return "user_detail";
}
}
該controller會轉發到user_detail.jsp頁面
<html>
<head>
<title>detail</title>
</head>
<body>
用戶詳情:
${userInfo.id}:${userInfo.name}
<%System.out.print(">>>>>jsp頁面的輸出為:");%>
<%System.out.println(((User)request.getAttribute("userInfo")).getName());%>
</body>
</html>
? 啟動項目后,在地址欄訪問/testBiz?userId=1,然后查看IDE控制臺打印:
==1-1====前置攔截器1 執行========2-1====前置攔截器2 執行======>>>>>業務代碼執行-查詢用戶ID為:1==2-2=====后置攔截器2 執行========1-2=====后置攔截器1 執行======>>>>>jsp頁面的輸出為:宙斯==2-3======最終攔截器2 執行========1-3======最終攔截器1 執行======
通過打印日志發現,攔截器執行順序是: 攔截器1的前置>攔截器2的前置>業務代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終
? 經過測試發現攔截器執行順序如下:
攔截器1的前置>攔截器2的前置>業務代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終
我們通過分析源碼來探究下攔截器是如何執行的
? 當瀏覽器發送/testBiz?userId=1的請求時,會經過DispatcherServlet的doDispatch方法,我們將其取出并觀察其核心代碼(省略非關鍵代碼)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
try {
try {
ModelAndView mv=null;
Object dispatchException=null;
try {
processedRequest=this.checkMultipart(request);
multipartRequestParsed=processedRequest !=request;
//1.獲取執行鏈
mappedHandler=this.getHandler(processedRequest);
if (mappedHandler==null) {
this.noHandlerFound(processedRequest, response);
return;
}
//2.獲取處理器適配器
HandlerAdapter ha=this.getHandlerAdapter(mappedHandler.getHandler());
//...
//【3】.執行前置攔截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//4.執行業務handler
mv=ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
//【5】.執行后置攔截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException=var20;
} catch (Throwable var21) {
dispatchException=new NestedServletException("Handler dispatch failed", var21);
}
//【6】.處理頁面響應,并執行最終攔截器
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
}finally {
//...
}
}
代碼中有關攔截器執行的位置我都添加了注釋,其中注釋中標識的步驟中,3、5、6步驟是攔截器的關鍵步驟
其中,第一步中"獲取執行鏈",執行鏈內容可以通過debug調試查看內容:
可以看到我們自定義的兩個攔截器按順序保存
? 在doDispatch方法中,我們添加的注釋的第【3】、【5】、【6】步驟是對攔截器的執行處理,現在分別來查看第【3】、【5】、【6】步驟執行的具體方法的源碼
//3.執行前置攔截器中的詳細代碼
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
//獲得本次請求對應的所有攔截器
HandlerInterceptor[] interceptors=this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//按照攔截器順序依次執行每個攔截器的preHandle方法.
//并且,interceptorIndex值會一次 + 1 (該值是給后面的最終攔截器使用的)
for(int i=0; i < interceptors.length; this.interceptorIndex=i++) {
HandlerInterceptor interceptor=interceptors[/color][i][color=black];
//只要每個攔截器不返回false,則繼續執行,否則執行最終攔截器
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
//最終返回true
return true;
}
? 我們可以看到攔截器的preHandler(前置處理)方法是按攔截器(攔截器1、攔截器2)順序執行的,然后我們再來看步驟【5】
//5.執行后置攔截器
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
//獲得本次請求對應的所有攔截器
HandlerInterceptor[] interceptors=this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//按倒敘執行每個攔截器的postHandle方法——所以我們看到先執行的攔截器2的postHandle,再執行攔截器1的postHandle
for(int i=interceptors.length - 1; i >=0; --i) {
HandlerInterceptor interceptor=interceptors[/color][color=black];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
會發現,后置處理是按照攔截器順序倒敘處理的!
? 我們最后來看下最終攔截器
//執行的方法
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
//...
if (mv !=null && !mv.wasCleared()) {
//處理響應
this.render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
}
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (mappedHandler !=null) {
//6、執行攔截器的最終方法們
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
}
其中,有一個render()方法,該方法會直接處理完response。再后則是觸發triggerAfterCompletion方法:
//6、執行攔截器的最終方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
HandlerInterceptor[] interceptors=this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//倒敘執行每個攔截器(interceptorIndex為前置攔截器動態計算)的afterCompletion方法
for(int i=this.interceptorIndex; i >=0; --i) {
HandlerInterceptor interceptor=interceptors[/color][/i][color=black][i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable var8) {
logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
}
}
}
}
由此可以看到,攔截器的最終方法的執行也是按照倒敘來執行的,而且是在響應之后。
? 攔截器常用于初始化資源,權限監控,會話設置,資源清理等的功能設置,就需要我們對它的執行順序完全掌握,我們通過源碼可以看到,攔截器類似于對我們業務方法的環繞通知效果,并且是通過循環收集好的攔截器集合來控制每個攔截器方法的執行順序,進而可以真正做到深入掌握攔截器的執行機制!
MVC是一種軟件架構的思想,將軟件按照模型、視圖、控制器來劃分
MVC的工作流程:
用戶通過視圖層發送請求到服務器,在服務器中請求被控制層接收Controller,Controller調用相應的Model層處理請求,處理完畢將結果返回到Controller,Controller再根據請求處理的結果找到相應的View視圖,渲染數據后最終響應給瀏覽器
SpringMVC是Spring的一個后續產品,是Spring的一個子項目
SpringMVC 是 Spring 為表述層開發提供的一整套完備的解決方案。在表述層框架歷經 Strust、WebWork、Strust2 等諸多產品的歷代更迭之后,目前業界普遍選擇了 SpringMVC 作為 Java EE 項目表述層開發的首選方案。
注:三層架構分為表述層(或表示層)、業務邏輯層、數據訪問層,表述層表示前臺頁面和后臺servlet
打包方式: <packaging>war</packaging>
添加依賴:
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
scope(依賴范圍):provided(已被提供),表示服務器已經提供該依賴,所以此依賴不會被打包
注冊SpringMVC的前端控制器DispatcherServlet
此配置作用下,SpringMVC的配置文件默認位于WEB-INF下,默認名稱為<servlet-name>,例如,以下配置所對應SpringMVC的配置文件位于WEB-INF下,文件名為springMVC-servlet.xml
<!-- 配置SpringMVC的前端控制器,對瀏覽器發送的請求統一進行處理 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!--
設置springMVC的核心控制器所能處理的請求的請求路徑
/所匹配的請求可以是/login或.html或.js或.css方式的請求路徑
但是/不能匹配.jsp請求路徑的請求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
可通過init-param標簽設置SpringMVC配置文件的位置和名稱,通過load-on-startup標簽設置SpringMVC前端控制器DispatcherServlet的初始化時間
<?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">
<!-- 配置SpringMVC的前端控制器,對瀏覽器發送的請求統一進行處理 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 通過初始化參數指定SpringMVC配置文件的位置和名稱 -->
<init-param>
<!-- contextConfigLocation為固定值 -->
<param-name>contextConfigLocation</param-name>
<!-- 使用classpath:表示從類路徑查找配置文件,例如maven工程中的src/main/resources -->
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--
作為框架的核心組件,在啟動過程中有大量的初始化操作要做
而這些操作放在第一次請求時才執行會嚴重影響訪問速度
因此需要通過此標簽將啟動控制DispatcherServlet的初始化時間提前到服務器啟動時
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!--
設置springMVC的核心控制器所能處理的請求的請求路徑
/所匹配的請求可以是/login或.html或.js或.css方式的請求路徑
但是/不能匹配.jsp請求路徑的請求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
注:<url-pattern>標簽中使用/和/*的區別:
/所匹配的請求可以是.html或.js或.css方式的請求路徑,但是/不能匹配.jsp請求路徑的請求,
避免在訪問jsp頁面時,請求被DispatcherServlet處理而找不到相應的頁面。
/*則能夠匹配所有請求,例如在使用過濾器時,若需要對所有請求進行過濾,就需要使用/*的寫法
spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置包掃描-->
<context:component-scan base-package="com.birdy"></context:component-scan>
<!-- 配置Thymeleaf視圖解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 視圖前綴 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 視圖后綴 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
</beans>
前端頁面文件放置在路徑src/main/webapp/WEB-INF/templates/下
<!--index.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
<h1>hello world!</h1>
</body>
</html>
由于前端控制器對瀏覽器發送的請求進行了統一的處理,但是不同的請求可能需要不同的處理過程,因此需要創建單獨處理請求的類,即請求控制器
請求控制器中處理請求的方法成為控制器方法
因為SpringMVC的控制器由一個POJO(普通的Java類)擔任,因此需要通過@Controller注解將其標識為一個控制層組件,交給Spring的IoC容器管理,此時SpringMVC才能夠識別控制器的存在
@Controller
public class HelloController {
@RequestMapping("/")
public String index(){
//控制前將前端傳過來的路徑映射到指定文件
return "index"; //將請求轉發到index.html文件
}
}
注:之所以 return "index";就可以映射到index.html文件,是因為在springMVC.xml配置了視圖解析器處理請求的規則,即自動添加了前綴文件路徑和后綴文件類型
瀏覽器發送請求,若請求地址符合前端控制器的url-pattern,該請求就會被前端控制器DispatcherServlet處理。前端控制器會讀取SpringMVC的核心配置文件,通過掃描組件找到控制器,將請求地址和控制器中@RequestMapping注解的value屬性值進行匹配,若匹配成功,該注解所標識的控制器方法就是處理請求的方法。處理請求的方法需要返回一個字符串類型的視圖名稱,該視圖名稱會被視圖解析器解析,加上前綴和后綴組成視圖的路徑,通過Thymeleaf對視圖進行渲染,最終轉發到視圖所對應頁面
作用:顧名思義,@RequestMapping`注解的作用就是將請求和處理請求的控制器方法關聯起來,建立映射關系。SpringMVC 接收到指定的請求,就會來找到在映射關系中對應的控制器方法來處理這個請求。
@Controller
@RequestMapping("/test")
public class RequestMappingController {
//此時映射的請求路徑為:/test/testRequestMapping
@RequestMapping("/testRequestMapping")
public String testRequestMapping(){
return "success";
}
}
value屬性通過請求的請求地址匹配請求映射,@RequestMapping注解的value屬性是一個字符串類型的數組,表示該請求映射能夠匹配多個請求地址所對應的請求,必填。
<a th:href="@{/testRequestMapping}">測試@RequestMapping的value屬性-->/testRequestMapping</a><br>
<a th:href="@{/test}">測試@RequestMapping的value屬性-->/test</a><br>
@RequestMapping( value={"/testRequestMapping", "/test"})
public String testRequestMapping(){
return "success";
}
若一個請求的請求地址滿足value屬性,但是請求方式不滿足method屬性,則瀏覽器報錯405:Request method 'xxx' not supported
<a th:href="@{/test}">測試@RequestMapping的value屬性-->/test</a><br>
<form th:action="@{/test}" method="post">
<input type="submit">
</form>
@RequestMapping(
value={"/testRequestMapping", "/test"},
method={RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
return "success";
}
注:
對于處理指定請求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解
常用的請求方式有get,post,put,delete,但是目前瀏覽器只支持get和post,若在form表單提交時,為method設置了其他請求方式的字符串(put或delete),則按照默認的請求方式get處理。
若要發送put和delete請求,則需要通過spring提供的過濾器HiddenHttpMethodFilter,在RESTful部分會講到
@RequestMapping注解的params屬性通過請求的請求參數匹配請求映射,@RequestMapping注解的params屬性是一個字符串類型的數組,可以通過四種表達式設置請求參數和請求映射的匹配關系
"param":要求請求映射所匹配的請求必須攜帶param請求參數
"!param":要求請求映射所匹配的請求必須不能攜帶param請求參數
"param=value":要求請求映射所匹配的請求必須攜帶param請求參數且param=value
"param!=value":要求請求映射所匹配的請求必須攜帶param請求參數但是param!=value
示例:
<a th:href="@{/test(username='admin',password=123456)">測試@RequestMapping的params屬性-->/test</a><br>
@RequestMapping(
value={"/testRequestMapping", "/test"}
,method={RequestMethod.GET, RequestMethod.POST}
,params={"username","password!=123456"} //表示請求必須攜帶username屬性,值任意;必須攜 帶password屬性,且值必須為123456
)
public String testRequestMapping(){
return "success";
}
注:
thymeleaf使用@{}填寫路徑,且自帶tomcat中設置的默認文本路徑使用()填寫請求參數
@RequestMapping注解的headers屬性通過請求的請求頭信息匹配請求映射。@RequestMapping注解的headers屬性是一個字符串類型的數組,可以通過四種表達式設置請求頭信息和請求映射的匹配關系
"header":要求請求映射所匹配的請求必須攜帶header請求頭信息
"!header":要求請求映射所匹配的請求必須不能攜帶header請求頭信息
"header=value":要求請求映射所匹配的請求必須攜帶header請求頭信息且header=value
"header!=value":要求請求映射所匹配的請求必須攜帶header請求頭信息且header!=value
若當前請求滿足@RequestMapping注解的value和method屬性,但是不滿足headers屬性,此時頁面顯示404錯誤,即資源未找到
?:表示任意的單個字符
*:表示任意的0個或多個字符
**:表示任意的一層或多層目錄
注意:在使用**時,只能使用/**/xxx的方式
原始方式:/deleteUser?id=1
rest方式:/deleteUser/1
SpringMVC路徑中的占位符常用于RESTful風格中,當請求路徑中將某些數據通過路徑的方式傳輸到服務器中,就可以在@RequestMapping注解的value屬性中通過占位符{xxx}表示傳輸的數據,然后通過@PathVariable注解,將占位符所表示的數據賦值給控制器方法的形參
<a th:href="@{/testRest/1/admin}">測試路徑中的占位符-->/testRest</a><br>
@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id,
@PathVariable("username") String username){
System.out.println("id:"+id+",username:"+username);
return "success";
}
//最終輸出的內容為-->id:1,username:admin
將HttpServletRequest作為控制器方法的形參,此時HttpServletRequest類型的參數就是封裝了當前請求報文的對象
<a th:href="@{/servlet(username='admin',password=123)}">測試servlet傳參</a>
@RequestMapping("/servlet")
public String testParam(HttpServletRequest request){
String username=request.getParameter("username");
String password=request.getParameter("password");
System.out.println("username:"+username+",password:"+password);
return "target";
}
//打印結果:username:admin,password:123
在特定情況下才會使用原始Servlet傳參,SpringMVC自帶形參匹配功能方便傳參,在控制器方法的形參位置,設置和請求參數同名的形參,當瀏覽器發送請求,匹配到請求映射時,在DispatcherServlet中就會將請求參數賦值給相應的形參,推薦使用。
<a th:href="@{/default(username='birdy',password=123)}">測試SringMVC形參方式</a>
@RequestMapping("/default")
public String testDefault(String username, Integer password){
System.out.println("username:"+username+",password:"+password);
return "target";
}
//打印結果:username:birdy,password:123
@RequestParam: 將請求參數和控制器方法的形參創建映射關系
@RequestParam注解一共有三個屬性:
@RequestMapping("/default")
public String testDefault(
@RequestParam(value="username", required=false) String username,
@RequestParam(value="password", required=false) String password
)
{
System.out.println("username:"+username+",password:"+password);
return "target";
}
//打印結果:username:birdy,password:123
@RequestHeader是將請求頭信息和控制器方法的形參創建映射關系
@RequestHeader注解一共有三個屬性:value、required、defaultValue,用法同@RequestParam
@RequestMapping("/header")
public String testHeader(@RequestHeader(value="Host") String host,
@RequestHeader(value="Accept-Encoding") String code){
System.out.println(host+"**"+code);
return "target";
}
@CookieValue是將cookie數據和控制器方法的形參創建映射關系
@CookieValue注解一共有三個屬性:value、required、defaultValue,用法同@RequestParam
session依賴于cookie,cookie在第一次請求時由服務器創建,通過響應頭返回給瀏覽器并保存在瀏覽器中,cookie默認在瀏覽器關閉后自動清除
解決獲取請求參數的亂碼問題,可以使用SpringMVC提供的編碼過濾器CharacterEncodingFilter,需要在web.xml中進行注冊
<!--配置springMVC的編碼過濾器-->
<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>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注:SpringMVC中處理編碼的過濾器一定要配置到其他過濾器之前,否則無效
當前頁面 (pagecontext)
一次請求(requset)
設定的對象在一次請求中有效,一次請求簡單的理解為用戶點擊一次超鏈接,當用戶第二次點擊的時候瀏覽器又發送一個新的請求給服務器,所以在一次點擊后,后臺不管跳轉多少個servlet,jsp都算是一次請求,而后臺在所跳轉的任何界面都能訪問設定的對象(如登錄界面向其他管理界面傳送登錄的用戶信息等)
一次會話(session)
ServletContext(整個項目)
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
request.setAttribute("ServletRequestScope", "hello,servletAPI");
return "success";
}
<!--thymleaf: 從request域中獲取共享數據 -->
<p th:text="${ServletRequestScope}"></p>
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
/**
* ModelAndView有Model和View的功能
* Model主要用于向請求域共享數據
* View主要用于設置視圖,實現頁面跳轉
*/
ModelAndView mav=new ModelAndView();
//向請求域共享數據
mav.addObject("ModelAndViewRequestScope", "hello,ModelAndView");
//設置視圖,實現頁面跳轉
mav.setViewName("success");
return mav;
}
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("ModelRequestScope", "hello,Model");
return "success";
}
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
map.put("testScope", "hello,Map");
return "success";
}
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testScope", "hello,ModelMap");
return "success";
}
Model、ModelMap、Map類型的參數其實本質上都是 BindingAwareModelMap類型的
public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope", "hello,session");
return "success";
}
<!--thymleaf: 從session域中獲取共享數據 -->
<p th:text="${sesson.testSessionScope}"></p>
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext application=session.getServletContext();
application.setAttribute("testApplicationScope", "hello,application");
return "success";
}
<!--thymleaf: 從servlet域中獲取共享數據 -->
<p th:text="${application.testApplicationScope}"></p>
SpringMVC中的視圖是View接口,視圖的作用渲染數據,將模型Model中的數據展示給用戶
SpringMVC視圖的種類很多,默認有轉發視圖和重定向視圖
當工程引入jstl的依賴,轉發視圖會自動轉換為JstlView
若使用的視圖技術為Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的視圖解析器,由此視圖解析器解析之后所得到的是ThymeleafView
當控制器方法中所設置的視圖名稱沒有任何前綴時,此時的視圖名稱會被SpringMVC配置文件中所配置的視圖解析器解析,視圖名稱拼接視圖前綴和視圖后綴所得到的最終路徑,會通過轉發的方式實現跳轉
@RequestMapping("/testHello")
public String testHello(){
return "hello";
}
SpringMVC中默認的轉發視圖是InternalResourceView
SpringMVC中創建轉發視圖的情況:
當控制器方法中所設置的視圖名稱以forward:為前綴時,創建InternalResourceView視圖,此時的視圖名稱不會被SpringMVC配置文件中所配置的視圖解析器解析,而是會將前綴forward:去掉,剩余部分作為最終路徑通過轉發的方式實現跳轉。
例如:
forward:/
forward:/employee
@RequestMapping("/testForward")
public String testForward(){
return "forward:/testHello";
}
視圖控制器view-controller
SpringMVC中默認的重定向視圖是RedirectView
當控制器方法中所設置的視圖名稱以redirect:為前綴時,創建RedirectView視圖,此時的視圖名稱不會被SpringMVC配置文件中所配置的視圖解析器解析,而是會將前綴redirect:去掉,剩余部分作為最終路徑通過重定向的方式實現跳轉
@RequestMapping("/testRedirect")
public String testRedirect(){
return "redirect:/testHello";
}
當控制器方法中,僅僅用來實現頁面跳轉,即只需要設置視圖名稱時,可以將處理器方法使用view-controller標簽進行表示,在spring配置文件中添加
<!--
path:設置處理的請求地址
view-name:設置請求地址所對應的視圖名稱
-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--開啟mvc注解驅動-->
<mvc:annotation-driven />
此時就可以刪除控制器方法
@RequestMapping("/")
public String index(HttpServletRequest request){
return "index";
}
注:
當SpringMVC中設置任何一個view-controller時,其他控制器中的請求映射將全部失效,此時需要在SpringMVC的核心配置文件中設置開啟mvc注解驅動的標簽:<mvc:annotation-driven />
REST:Representational State Transfer,表現層資源狀態轉移。
a>資源
資源是一種看待服務器的方式,即,將服務器看作是由很多離散的資源組成。每個資源是服務器上一個可命名的抽象概念。因為資源是一個抽象的概念,所以它不僅僅能代表服務器文件系統中的一個文件、數據庫中的一張表等等具體的東西,可以將資源設計的要多抽象有多抽象,只要想象力允許而且客戶端應用開發者能夠理解。與面向對象設計類似,資源是以名詞為核心來組織的,首先關注的是名詞。一個資源可以由一個或多個URI來標識。URI既是資源的名稱,也是資源在Web上的地址。對某個資源感興趣的客戶端應用,可以通過資源的URI與其進行交互。
b>資源的表述
資源的表述是一段對于資源在某個特定時刻的狀態的描述。可以在客戶端-服務器端之間轉移(交換)。資源的表述可以有多種格式,例如HTML/XML/JSON/純文本/圖片/視頻/音頻等等。資源的表述格式可以通過協商機制來確定。請求-響應方向的表述通常使用不同的格式。
c>狀態轉移
狀態轉移說的是:在客戶端和服務器端之間轉移(transfer)代表資源狀態的表述。通過轉移和操作資源的表述,來間接實現操作資源的目的。
具體說,就是 HTTP 協議里面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。
它們分別對應四種基本操作:GET 用來獲取資源,POST 用來新建資源,PUT 用來更新資源,DELETE 用來刪除資源。
REST 風格提倡 URL 地址使用統一的風格設計,從前到后各個單詞使用斜杠分開,不使用問號鍵值對方式攜帶請求參數,而是將要發送給服務器的數據作為 URL 地址的一部分,以保證整體風格的一致性。
操作 | 傳統方式 | REST風格 |
查詢操作 | getUserById?id=1 | user/1-->get請求方式 |
保存操作 | saveUser | user-->post請求方式 |
刪除操作 | deleteUser?id=1 | user/1-->delete請求方式 |
更新操作 | updateUser | user-->put請求方式 |
由于瀏覽器只支持發送get和post方式的請求,那么該如何發送put和delete請求呢?
SpringMVC 提供了 HiddenHttpMethodFilter 幫助我們將 POST 請求轉換為 DELETE 或 PUT 請求
HiddenHttpMethodFilter處理put和delete請求的需要滿足條件:
滿足以上條件,HiddenHttpMethodFilter過濾器就會將當前請求的請求方式轉換為請求參數_method的值,因此請求參數_method的值才是最終的請求方式
在web.xml中注冊HiddenHttpMethodFilter
<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>
注:目前為止,SpringMVC中提供了兩個過濾器:CharacterEncodingFilter和HiddenHttpMethodFilter
在web.xml中注冊時,必須先注冊CharacterEncodingFilter,再注冊HiddenHttpMethodFilter
原因:CharacterEncodingFilter 中通過 request.setCharacterEncoding(encoding) 方法設置字符集的,要求前面不能有任何獲取請求參數的操作。而 HiddenHttpMethodFilter 恰恰有一個后去請求參數_method當作真實請求方式的操作
和傳統 CRUD 一樣,實現對員工信息的增刪改查。
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">
<!--配置編碼過濾器-->
<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>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置處理請求方式put和delete的HiddenHttpMethodFilter-->
<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>
<!--配置SpringMVC的前端控制器DispatcherServlet-->
<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>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
spring配置文件springMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--掃描組件-->
<context:component-scan base-package="com.birdy.rest"></context:component-scan>
<!--配置Thymeleaf視圖解析器-->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 視圖前綴 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 視圖后綴 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
<!--配置視圖控制器-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:view-controller>
<!--開放對靜態資源的訪問-->
<mvc:default-servlet-handler />
<!--開啟mvc注解驅動-->
<mvc:annotation-driven />
</beans>
實體類
package com.birdy.mvc.bean;
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id=id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName=lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email=email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender=gender;
}
public Employee(Integer id, String lastName, String email, Integer gender) {
super();
this.id=id;
this.lastName=lastName;
this.email=email;
this.gender=gender;
}
public Employee() {
}
}
準備dao模擬數據
package com.birdy.mvc.dao;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.birdy.mvc.bean.Employee;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees=null;
static{
employees=new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1));
employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1));
employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0));
employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0));
employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1));
}
private static Integer initId=1006;
public void save(Employee employee){
if(employee.getId()==null){
employee.setId(initId++);
}
employees.put(employee.getId(), employee);
}
public Collection<Employee> getAll(){
return employees.values();
}
public Employee get(Integer id){
return employees.get(id);
}
public void delete(Integer id){
employees.remove(id);
}
}
功能 | URL 地址 | 請求方式 |
訪問首頁√ | / | GET |
查詢全部數據√ | /employee | GET |
刪除√ | /employee/2 | DELETE |
跳轉到添加數據頁面√ | /toAdd | GET |
執行保存√ | /employee | POST |
跳轉到更新數據頁面√ | /employee/2 | GET |
執行更新√ | /employee | PUT |
配置view-controller
<mvc:view-controller path="/" view-name="index"/>
創建頁面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" >
<title>Title</title>
</head>
<body>
<h1>首頁</h1>
<a th:href="@{/employee}">訪問員工信息</a>
</body>
</html>
@RequestMapping(value="/employee", method=RequestMethod.GET)
public String getEmployeeList(Model model){
Collection<Employee> employeeList=employeeDao.getAll();
model.addAttribute("employeeList", employeeList);
return "employee_list";
}
創建employee_list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Employee Info</title>
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
</head>
<body>
<table border="1" cellpadding="0" cellspacing="0" style="text-align: center;" id="dataTable">
<tr>
<th colspan="5">Employee Info</th>
</tr>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>options(<a th:href="@{/toAdd}">add</a>)</th>
</tr>
<tr th:each="employee : ${employeeList}">
<td th:text="${employee.id}"></td>
<td th:text="${employee.lastName}"></td>
<td th:text="${employee.email}"></td>
<td th:text="${employee.gender}"></td>
<td>
<a class="deleteA" @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
<a th:href="@{'/employee/'+${employee.id}}">update</a>
</td>
</tr>
</table>
</body>
</html>
a>創建處理delete請求方式的表單
<!-- 作用:通過超鏈接控制表單的提交,將post請求轉換為delete請求 -->
<form id="delete_form" method="post">
<!-- HiddenHttpMethodFilter要求:必須傳輸_method請求參數,并且值為最終的請求方式 -->
<input type="hidden" name="_method" value="delete"/>
</form>
b>刪除超鏈接綁定點擊事件
引入vue.js
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
刪除超鏈接
<a class="deleteA" @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
通過vue處理點擊事件
<script type="text/javascript">
var vue=new Vue({
el:"#dataTable",
methods:{
//event表示當前事件
deleteEmployee:function (event) {
//通過id獲取表單標簽
var delete_form=document.getElementById("delete_form");
//將觸發事件的超鏈接的href屬性為表單的action屬性賦值
delete_form.action=event.target.href;
//提交表單
delete_form.submit();
//阻止超鏈接的默認跳轉行為
event.preventDefault();
}
}
});
</script>
控制器方法
@RequestMapping(value="/employee/{id}", method=RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/employee";
}
配置view-controller
<mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:view-controller>
b>創建employee_add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Add Employee</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
lastName:<input type="text" name="lastName"><br>
email:<input type="text" name="email"><br>
gender:<input type="radio" name="gender" value="1">male
<input type="radio" name="gender" value="0">female<br>
<input type="submit" value="add"><br>
</form>
</body>
</html>
a>控制器方法
@RequestMapping(value="/employee", method=RequestMethod.POST)
public String addEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
a>修改超鏈接
<a th:href="@{'/employee/'+${employee.id}}">update</a>
b>控制器方法
@RequestMapping(value="/employee/{id}", method=RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id, Model model){
Employee employee=employeeDao.get(id);
model.addAttribute("employee", employee);
return "employee_update";
}
c>創建employee_update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Update Employee</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
<input type="hidden" name="_method" value="put">
<input type="hidden" name="id" th:value="${employee.id}">
lastName:<input type="text" name="lastName" th:value="${employee.lastName}"><br>
email:<input type="text" name="email" th:value="${employee.email}"><br>
<!--
th:field="${employee.gender}"可用于單選框或復選框的回顯
若單選框的value和employee.gender的值一致,則添加checked="checked"屬性
-->
gender:<input type="radio" name="gender" value="1" th:field="${employee.gender}">male
<input type="radio" name="gender" value="0" th:field="${employee.gender}">female<br>
<input type="submit" value="update"><br>
</form>
</body>
</html>
a>控制器方法
@RequestMapping(value="/employee", method=RequestMethod.PUT)
public String updateEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
HttpMessageConverter,報文信息轉換器,將請求報文轉換為Java對象,或將Java對象轉換為響應報文
HttpMessageConverter提供了兩個注解和兩個類型:
@RequestBody可以獲取請求體,需要在控制器方法設置一個形參,使用@RequestBody進行標識,當前請求的請求體就會為當前注解所標識的形參賦值
<form th:action="@{/testRequestBody}" method="post">
用戶名:<input type="text" name="username"><br>
密碼:<input type="password" name="password"><br>
<input type="submit">
</form>
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}
//輸出結果:requestBody:username=admin&password=123456
RequestEntity封裝請求報文的一種類型,需要在控制器方法的形參中設置該類型的形參,當前請求的請求報文就會賦值給該形參,可以通過getHeaders()獲取請求頭信息,通過getBody()獲取請求體信息
@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println("requestHeader:"+requestEntity.getHeaders());
System.out.println("requestBody:"+requestEntity.getBody());
return "success";
}
輸出結果:
requestHeader:[host:"localhost:8080", connection:"keep-alive", content-length:"27", cache-control:"max-age=0", sec-ch-ua:"" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"", sec-ch-ua-mobile:"?0", upgrade-insecure-requests:"1", origin:"http://localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"]
requestBody:username=admin&password=123
@ResponseBody注解的作用是將controller的方法返回的對象通過適當的轉換器轉換為指定的格式之后,寫入到response對象的body區,通常用來返回JSON數據或者是XML。需要注意的呢,在使用此注解之后不會再走試圖處理器,而是直接將數據寫入到輸入流中,他的效果等同于通過response對象輸出指定格式的數據。
@RequestMapping("/login")
@ResponseBody
public User login(User user){
return user;
}
//User字段:userName pwd
// 那么在前臺接收到的數據為:'{"userName":"xxx","pwd":"xxx"}'
// 效果等同于如下代碼:
@RequestMapping("/login")
public void login(User user, HttpServletResponse response){
response.getWriter.write(JSONObject.fromObject(user).toString());
}
@ResponseBody是作用在方法上的,@ResponseBody 表示該方法的返回結果直接寫入 HTTP response body 中,一般在異步獲取數據時使用
@ResponseBody盡量不要用在get請求中
@ResponseBody處理json的步驟:
1、導入jackson的依賴
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
2、在SpringMVC的核心配置文件中開啟mvc的注解驅動,此時在HandlerAdaptor中會自動裝配一個消息轉換器:MappingJackson2HttpMessageConverter,可以將響應到瀏覽器的Java對象轉換為Json格式的字符串
<mvc:annotation-driven />
3、在處理器方法上使用@ResponseBody注解進行標識,將Java對象直接作為控制器方法的返回值返回,就會自動轉換為Json格式的字符串
@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
return new User(1001,"admin","123456",23,"男");
}
瀏覽器的頁面中展示的結果:
{"id":1001,"username":"admin","password":"123456","age":23,"sex":"男"}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ajax</title>
</head>
<body>
<div id="app">
<a th:href="@{/testAjax}" @click="testAjax">測試ajax</a>
</div>
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
<script type="text/javascript">
new Vue({
el: "#app",
methods: {
testAjax(event){
axios({
method: "post",
url: event.target.href,
params: {
username: "admin",
password: "123456"
}
}).then((res)=>{
alert(res.data)
})
//阻止超鏈接默認跳轉行為
event.preventDefault();
}
}
})
</script>
</body>
</html>
@PostMapping("/testAjax")
@ResponseBody
public String testAjax(){
return "hello,ajax";
}
使用ResponseEntity實現下載文件的功能
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//獲取ServletContext對象
ServletContext servletContext=session.getServletContext();
//獲取服務器中文件的真實路徑
String realPath=servletContext.getRealPath("/static/img/1.jpg");
//創建輸入流
InputStream is=new FileInputStream(realPath);
//創建字節數組
byte[] bytes=new byte[is.available()];
//將流讀到字節數組中
is.read(bytes);
//創建HttpHeaders對象設置響應頭信息
MultiValueMap<String, String> headers=new HttpHeaders();
//設置要下載方式以及下載文件的名字
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//設置響應狀態碼
HttpStatus statusCode=HttpStatus.OK;
//創建ResponseEntity對象
ResponseEntity<byte[]> responseEntity=new ResponseEntity<>(bytes,headers,statusCode);
//關閉輸入流
is.close();
return responseEntity;
}
文件上傳要求form表單的請求方式必須為post,并且添加屬性enctype="multipart/form-data"
SpringMVC中將上傳的文件封裝到MultipartFile對象中,通過此對象可以獲取文件相關信息
1、添加依賴:
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
2、在SpringMVC的配置文件中添加配置:
<!--必須通過文件解析器的解析才能將文件轉換為MultipartFile對象,且必須配置id屬性-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
3、控制器方法:
@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//獲取上傳的文件的文件名
String fileName=photo.getOriginalFilename();
//處理文件重名問題
String hzName=fileName.substring(fileName.lastIndexOf("."));
fileName=UUID.randomUUID().toString() + hzName;
//獲取服務器中photo目錄的路徑
ServletContext servletContext=session.getServletContext();
String photoPath=servletContext.getRealPath("photo");
File file=new File(photoPath);
if(!file.exists()){
file.mkdir();
}
String finalPath=photoPath + File.separator + fileName;
//實現上傳功能
photo.transferTo(new File(finalPath));
return "success";
}
SpringMVC中的攔截器用于攔截控制器方法的執行
SpringMVC中的攔截器需要實現HandlerInterceptor
@Component
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor-->preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor-->postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor-->afterCompletion");
}
}
SpringMVC的攔截器必須在Spring的配置文件中進行配置:
<mvc:interceptors>
<!--按組件名配置:注意默認情況下容器中的組件自動變駝峰命名-->
<ref bean="firstInterceptor"></ref>
</mvc:interceptors>
或
<mvc:interceptors>
<!--按類名配置-->
<bean class="com.birdy.interceptor.FirstInterceptor"></bean>
</mvc:interceptors>
<mvc:interceptor>
<!--攔截所有請求-->
<mvc:mapping path="/**"/>
<!--配置不攔截的請求-->
<mvc:exclude-mapping path="/"/>
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
SpringMVC中的攔截器有三個抽象方法:
若每個攔截器的preHandle()都返回true。此時多個攔截器的執行順序和攔截器在SpringMVC的配置文件的配置順序有關:
SpringMVC提供了一個處理控制器方法執行過程中所出現的異常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的實現類有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
SpringMVC提供了自定義的異常處理器SimpleMappingExceptionResolver,使用方式:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
properties的鍵表示處理器方法執行過程中出現的異常
properties的值表示若出現指定異常時,設置一個新的視圖名稱,跳轉到指定頁面
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--
exceptionAttribute屬性設置一個屬性名,將出現的異常信息在請求域中進行共享
-->
<property name="exceptionAttribute" value="ex"></property>
</bean>
//@ControllerAdvice將當前類標識為異常處理的組件
@ControllerAdvice
public class ExceptionController {
//@ExceptionHandler用于設置所標識方法處理的異常
@ExceptionHandler(ArithmeticException.class)
//ex表示當前請求處理中出現的異常對象
public String handleArithmeticException(Exception ex, Model model){
//將將異常信息放在域里共享
model.addAttribute("ex", ex);
return "error";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ajax</title>
</head>
<body>
<a th:href="@{/testEx}">測試異常處理</a>
</body>
</html>
出現異常后跳轉的頁面
<!--error.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
出現錯誤
<!--/*@thymesVar id="ex" type="request"*/-->
<p th:text="${ex}"></p>
</body>
</html>
在Servlet3.0環境中,容器會在類路徑中查找實現javax.servlet.ServletContainerInitializer接口的類,如果找到的話就用它來配置Servlet容器。
Spring提供了這個接口的實現,名為SpringServletContainerInitializer,這個類反過來又會查找實現WebApplicationInitializer的類并將配置的任務交給它們來完成。
Spring3.2引入了一個便利的WebApplicationInitializer基礎實現,名為AbstractAnnotationConfigDispatcherServletInitializer,當我們的類擴展了AbstractAnnotationConfigDispatcherServletInitializer并將其部署到Servlet3.0容器的時候,容器會自動發現它,并用它來配置Servlet上下文
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定spring的配置類
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/**
* 指定SpringMVC的配置類
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 指定DispatcherServlet的映射規則,即url-pattern
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 添加過濾器
* @return
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter encodingFilter=new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceRequestEncoding(true);
HiddenHttpMethodFilter hiddenHttpMethodFilter=new HiddenHttpMethodFilter();
return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
}
}
創建SpringConfig配置類,代替spring的配置文件
@Configuration
public class SpringConfig {
//ssm整合之后,spring的配置信息寫在此類中
}
創建WebConfig配置類,代替SpringMVC的配置文件
@Configuration
//掃描組件
@ComponentScan("com.atguigu.mvc.controller")
//開啟MVC注解驅動
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
//使用默認的servlet處理靜態資源
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//配置文件上傳解析器
@Bean
public CommonsMultipartResolver multipartResolver(){
return new CommonsMultipartResolver();
}
//配置攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
FirstInterceptor firstInterceptor=new FirstInterceptor();
registry.addInterceptor(TestInterceptor).addPathPatterns("/**");
}
//配置視圖控制
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
//配置異常映射
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver=new SimpleMappingExceptionResolver();
Properties prop=new Properties();
prop.setProperty("java.lang.ArithmeticException", "error");
//設置異常映射
exceptionResolver.setExceptionMappings(prop);
//設置共享異常信息的鍵
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}
//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext=ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一個ServletContext作為構造參數,可通過WebApplicationContext 的方法獲得
ServletContextTemplateResolver templateResolver=new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
//生成模板引擎并為模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine=new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成視圖解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver=new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("TestInterceptor-->preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
歡迎點贊+轉發+關注!大家的支持是我分享最大的動力!!!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。