SpringMVC 是基於 MVC 開發模式的框架,用來優化控制器,是 Spring 家族的一員,同時它也具備 IOC 和 AOP ...
1、什麼是 SpringMVC?
SpringMVC 是基於 MVC 開發模式的框架,用來優化控制器。它是 Spring 家族的一員,它也具備 IOC 和 AOP。
- 什麼是MVC?
MVC 是一種開發模式,它是模型視圖控制器的簡稱,所有的 web 應用都是基於 MVC 開發。
1)M:模型層,包含實體類,業務邏輯層,數據訪問層
2)V:視圖層,html,javaScript,vue 等都是視圖層,用來顯現數據
3)C:控制器,它是用來接收客戶端的請求,並返迴響應到客戶端的組件,Servlet就是組件
2、SpringMVC 框架的優點?
1)輕量級,基於 MVC 的框架
2)易於上手,容易理解,功能強大
3)它具備 IOC 和 AOP
4)完全基於註解開發
3、Springmvc 的執行流程:
執行流程說明:
1)向伺服器發送HTTP請求,請求被前端控制器 DispatcherServlet 捕獲。
2)DispatcherServlet 根據<servlet-name>中的配置對請求的URL進行解析,得到請求資源標識符(URI)。然後根據該URI,調用 HandlerMapping 獲得該Handler配置的所有相關的對象(包括Handler對象以及Handler對象對應的攔截器),最後以 HandlerExecutionChain 對象的形式返回。
3)DispatcherServlet 根據獲得的 Handler,選擇一個合適的 HandlerAdapter。
4)提取 Request 中的模型數據,填充 Handler 入參,開始執行 Handler(Controller)。在填充 Handler的入參過程中,根據你的配置,Spring 將幫你做一些額外的工作:
HttpMessageConveter:將請求消息(如Json、xml等數據)轉換成一個對象,將對象轉換為指定的響應信息。
數據轉換:對請求消息進行數據轉換。如String轉換成Integer、Double等。
數據格式化:對請求消息進行數據格式化。如將字元串轉換成格式化數字或格式化日期等。
數據驗證:驗證數據的有效性(長度、格式等),驗證結果存儲到BindingResult或Error中。
5)Handler(Controller)執行完成後,向 DispatcherServlet 返回一個 ModelAndView 對象。
6)根據返回的ModelAndView,選擇一個適合的 ViewResolver(必須是已經註冊到Spring容器中的ViewResolver)返回給DispatcherServlet。
7)ViewResolver 結合Model和View,來渲染視圖。
8)視圖負責將渲染結果返回給客戶端
4、基於註解的 SpringMVC 框架開發的步驟:
1)新建項目,選擇 webapp 模板
2)修改目錄,添加缺失的 test 目錄,java目錄,resources目錄(兩套),並修改目錄屬性
3)修改 pom.xml 文件,添加 SpringMVC 的依賴,添加 Servlet 的依賴
<!--添加springmvc的依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--添加servlet的依賴-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
4)添加 springmvc.xml 配置文件,指定包掃描,添加視圖解析器.
SpringMVC 框架為了避免請求資源路徑與擴展名上的冗餘,在視圖解析器 InternalResouceViewResolver 中引入了請求的前輟與後輟。而 action 中只需給出要跳轉頁面的文件名即可,對於具體的文件路徑與文件擴展名,視圖解析器會自動完成拼接;
<context:component-scan>:用來進行包掃描,這裡用於指定@Controller註解所在的包路徑。
<!--添加包掃描-->
<context:component-scan base-package="com.bjpowernode.controller"></context:component-scan>
<!--添加視圖解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置首碼-->
<property name="prefix" value="/admin/"></property>
<!--配置尾碼-->
<property name="suffix" value=".jsp"></property>
</bean>
5)刪除 web.xml 文件,新建 web.xml
6)在 web.xml 文件中註冊 springMVC 框架(所有的 web 請求都是基於 servlet 的)
<!--註冊SpringMVC框架-->
<servlet>
<servlet-name>springmvc</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>springmvc</servlet-name>
<!--
指定攔截什麼樣的請求
<a href="${pageContext.request.contextPath}/demo.action">訪問伺服器</a>
-->
<!--指定攔截以.action結尾的請求,交給核心處理器DispatcherServlet處理。-->
<url-pattern>*.action</url-pattern>
</servlet-mapping>
7)在 webapp 目錄下新建 admin 目錄,在 admin 目錄下新建 main.jsp 頁面,刪除 index.jsp 頁面,並新建
8)開發控制器(Servlet),這是一個普通的類,不用繼承和實現介面。類中的每個方法就是一個具體的action控制器。
@Controller //表示當前類為處理器,交給Spring去創建對象
public class DemoAction {
/**
* 以前的Servlet的規範
* protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}
* action中所有的功能實現都是由方法來完成的
* action方法的規範
* 1)訪問許可權是public
* 2)方法的返回值任意
* 3)方法名稱任意
* 4)方法可以沒有參數,如果有可是任意類型
* 5)要使用@RequestMapping註解來聲明一個訪問的路徑(名稱)
*
*/
@RequestMapping("/demo")
public String demo(){
System.out.println("伺服器被訪問到了.......");
return "main"; //可以直接跳到/admin/main.jsp頁面上
}
}
9)添加 tomcat 進行測試功能
5、分析 web 請求:
web請求執行的流程:
index.jsp<--------------->DispatcherServlet<------------------->SpringMVC的處理器(加了@Controller的類中的方法)
one.jsp <--------------->DispatcherServlet<------------------->SpringMVC的處理器(加了@Controller的類中的方法)
- DispatcherServlet 要在 web.xml 文件中註冊才可用
6、@RequestMapping 註解詳解:
- 此註解就是來映射伺服器訪問的路徑。@RequestMapping 的 value 屬性用於定義所匹配請求的 URI。
1)此註解可加在方法上,是為此方法註冊一個可以訪問的名稱(路徑)。
@RequestMapping("/demo")
public String demo(){
System.out.println("伺服器被訪問到了.......");
return "main"; //可以直接跳到/admin/main.jsp頁面上
}
//<a href="${pageContext.request.contextPath}/demo.action">訪問伺服器</a>
2)此註解可以加在類上,相當於是包名(虛擬路徑),區分不同類中相同的 action 的名稱。URI 的請求是相對於 Web 的根目錄。換個角度說,要訪問處理器的指定方法,必須要在方法指定 URI 之前加上處理器類前定義的模塊名稱。
@RequestMapping("/user")
public class DemoAction1 {..}
//<a href="${pageContext.request.contextPath}/user/demo.action">訪問伺服器</a>
3)此註解可區分 get 請求和 post 請求:
- 對於@RequestMapping,其有一個屬性 method,用於對被註解方法所處理請求的提交方式進行限制,即只有滿足該 method 屬性指定的提交方式的請求,才會執行該被註解方法。
@Controller
public class ReqAction {
@RequestMapping(value = "/req",method = RequestMethod.GET)
public String req(){
System.out.println("我是處理get請求的........");
return "main";
}
@RequestMapping(value = "/req" ,method = RequestMethod.POST)
public String req1(){
System.out.println("我是處理post請求的........");
return "main";
}
}
7、五種數據提交方式:
- 前四種數據註入的方式,會自動進行類型轉換。但無法自動轉換日期類型。
1)單個提交數據
- 在方法中聲明一個和表單提交的參數名稱相同的參數,由框架按照名稱直接註入。
慄子:
/*
<form action="${pageContext.request.contextPath}/one.action">
姓名:<input name="myname"><br>
年齡:<input name="age"><br>
<input type="submit" value="提交">
</form>
*/
@RequestMapping("/one")
public String one(String myname,int age){ ===>自動註入,並且類型轉換
System.out.println("myname="+myname+",age="+(age+100));
return "main";
}
2)對象封裝提交數據:
-
聲明一個自定義的實體類參數,框架調用實體類中相應的setter方法註入屬性值,只要保證實體類中成員變數的名稱與提交請求的name屬性值一致即可。
實體類:
public class Users {
private String name;
private int age;
//構造方法,setter方法等省略。。。
}
頁面:
<form action="${pageContext.request.contextPath}/two.action" method="post">
姓名:<input name="name"><br>
年齡:<input name="age"><br>
<input type="submit" value="提交">
</form>
action:
@RequestMapping("/two")
public String two(Users u){
System.out.println(u);
return "main";
}
3)動態占位符提交
- 僅限於超鏈接或地址攔提交數據,它是一杠一值,一杠一大括弧,使用註解 @PathVariable 來解析。
//<a href="${pageContext.request.contextPath}/three/張三/22.action">動態提交</a>
@RequestMapping("/three/{uname}/{uage}")
public String three(@PathVariable("uname") //用來解析路徑中的請求參數
String name,
@PathVariable("uage")
int age){
System.out.println("name="+name+",age="+(age+100));
return "main";
}
4)映射名稱不一致
- 提交請求參數與action方法的形參的名稱不一致,使用註解@RequestParam來解析
/**
* 姓名:<input name="name"><br>
* 年齡:<input name="age"><br>
*/
@RequestMapping("/four")
public String four(@RequestParam("name") //專門用來解決名稱不一致的問題
String uname,
@RequestParam("age")
int uage){
System.out.println("uname="+uname+",uage="+(uage+100));
return "main";
}
5)使用HttpServletRequest對象提取;
/**
* 姓名:<input name="name"><br>
* 年齡:<input name="age"><br>
*/
@RequestMapping("/five")
public String five(HttpServletRequest request){
String name = request.getParameter("name");
int age = Integer.parseInt(request.getParameter("age"));
System.out.println("name="+name+",age="+(age+100));
return "main";
}
8、中文亂碼解決方案:
- 對於前面所接收的請求參數,若含有中文,則會出現中文亂碼問題。Spring 對於請求參數中的中文亂碼問題,給出了專門的字元集過濾器:
- spring-web-5.2.5.RELEASE.jar 的 org.springframework.web.filter 包下的 CharacterEncodingFilter 類。
解決方案:
- 在 web.xml 中註冊字元集過濾器,即可解決 Spring 的請求參數的中文亂碼問題。不過,最好將該過濾器註冊在其它過濾器之前。因為過濾器的執行是按照其註冊順序進行的。
<filter>
<filter-name>encode</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--
配置參數
private String encoding;
private boolean forceRequestEncoding;
private boolean forceResponseEncoding;
-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encode</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
9、action方法的返回值:
1)String:
處理器方法返回的字元串可以指定邏輯視圖名,通過視圖解析器解析可以將其轉換為物理視圖地址;
可以自動拼接首碼和尾碼,可以指定返回的路徑;
當然,也可以直接返回資源的物理視圖名。不過,此時就不需要再在視圖解析器中再配置前輟與後輟了。
2)Object:
返回 json 格式的對象,自動將對象或集合轉為 json,使用的 jackson 工具進行轉換,必須要添加 jackson 依賴,一般用於 ajax 請求;
處理器方法也可以返回 Object 對象,這個 Object 可以是 Integer,自定義對象,Map,List 等。但返回的對象不是作為邏輯視圖出現的,而是作為直接在頁面顯示的數據出現的。返回對象,需要使用@ResponseBody 註解,將轉換後的 JSON 數據放入到響應體中。Ajax請求多用於Object返回值類型。
3)void:
無返回值,一般用於ajax請求;
若處理器對請求處理後,無需跳轉到其它任何資源,此時可以讓處理器方法返回 void。
4)基本數據類型,用於 ajax 請求。
5)ModelAndView:返回數據和視圖對象,現在用的很少。
10、完成 ajax 請求訪問伺服器,返回學生集合:
1)添加jackson依賴
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
2)在 webapp 目錄下新建 js目錄,添加 jQuery 函數庫
3)在 index.jsp 頁面上導入函數庫:
4)在 action 上添加註解@ResponseBody,用來處理ajax請求
@Controller
public class AjaxAction {
//處理ajax請求,一定要加@ResponseBody
@ResponseBody
@RequestMapping("/ajax")
public List<Student> ajax(){
Student stu1 = new Student("張三",22);
Student stu2 = new Student("李四",24);
Student stu3 = new Student("王五",23);
List<Student> list = new ArrayList<>();
list.add(stu1);
list.add(stu2);
list.add(stu3);
//調用json轉換工具ObjectMapper進行轉換
return list; //===>springmvc負責轉換成json
}
}
5)在 springmvc.xml 文件中添加註解驅動 <mvc:annotationdriven/>,它用來解析 @ResponseBody 註解
<mvc:annotation-driven></mvc:annotation-driven>
11、SpringMVC 的四種跳轉方式:
- 本質就是兩種跳轉:請求轉發和重定向,衍生出四種是請求轉發頁面,轉發action,重定向頁面,重定向action;
- 預設的跳轉是請求轉發,直接跳轉到 jsp 頁面展示;
可以使用框架提供的關鍵字 redirect: ,進行一個重定向操作,包括重定向頁面和重定向action;
可以使用框架提供的關鍵字forward:,進行伺服器內部轉發操作,包括轉發頁面和轉發action;
- 當使用 redirect: 和 forward: 關鍵字時,視圖解析器中首碼尾碼的拼接就無效了。
慄子:
@RequestMapping("/one")
public String one(){
System.out.println("這是請求轉發頁面跳轉.........");
return "main"; //預設是請求轉發,使用視圖解析器拼接首碼尾碼進行頁面跳轉
}
@RequestMapping("/two")
public String two(){
System.out.println("這是請求轉發action跳轉.........");
// /admin/ /other.action .jsp
//forward: 這組字元串可以屏蔽首碼和尾碼的拼接.實現請求轉發跳轉
return "forward:/other.action"; //預設是請求轉發,使用視圖解析器拼接首碼尾碼進行頁面跳轉
}
@RequestMapping("/three")
public String three(){
System.out.println("這是重定向頁面.......");
//redirect: 這組字元串可以屏蔽首碼和尾碼的拼接.實現重定向跳轉
return "redirect:/admin/main.jsp";
}
@RequestMapping("/four")
public String four(){
System.out.println("這是重定向action.......");
//redirect: 這組字元串可以屏蔽首碼和尾碼的拼接.實現重定向跳轉
return "redirect:/other.action";
}
@RequestMapping("/five")
public String five(){
System.out.println("這是隨便跳.......");
return "forward:/fore/login.jsp";
}
12、SpringMVC 預設的參數類型:
不需要去創建,直接拿來使用即可。
1)HttpServletRequest
2)HttpServletResponse
3)HttpSession
4)Model
5)Map
6)ModelMap
慄子:
//做一個數據,傳到main.jsp頁面上
Users u = new Users("張三",22);
//傳遞數據
request.setAttribute("requestUsers",u);
session.setAttribute("sessionUsers",u);
model.addAttribute("modelUsers",u);
map.put("mapUsers",u);
modelMap.addAttribute("modelMapUsers",u);
註意:Map,Model,ModelMap 和 request 一樣,都使用請求作用域進行數據傳遞,所以伺服器端的跳轉必須是請求轉發,頁面才可以得到值。
13.日期處理:
1)日期的提交處理
A、單個日期處理
- 要使用註解@DateTimeFormat,此註解必須搭配 springmvc.xml 文件中的 <mvc:annotationdriven標簽>
在方法的參數上使用@DateTimeFormat註解;
在類的成員setXXX()方法上使用@DateTimeFormat註解
- 但這種解決方案要在每個使用日期類型的地方都去添加使用@DateTimeFormat註解,比較麻煩,我們可以使用@InitBinder註解來進行類中統一日期類型的處理。
B.類中全局日期處理
註冊一個註解,用來解析本類中所有的日期類型,自動轉換
@InitBinder;
public void initBinder(WebDataBinder dataBinder){
dataBinder.registerCustomEditor(Date.class,new CustomDateEditor(sf,true));
}
2)日期的顯示處理:
- 在頁面上顯示好看的日期,必須使用 JSTL
步驟:
A)添加依賴 jstl
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
B)在頁面上導入標簽庫 :
- 如果是單個日期對象,直接轉為好看的格式化的字元串進行顯示
如果是 list 中的實體類對象的成員變數是日期類型,則必須使用 jstl 進行顯示。
<%--導入jstl核心標簽庫--%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--導入jstl格式化標簽庫--%>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
C)使用標簽顯示數據:
<table width="800px" border="1">
<tr>
<th>姓名</th>
<th>生日</th>
</tr>
<c:forEach items="${list}" var="stu">
<tr>
<td>${stu.name}</td>
<td>${stu.birthday}------ <fmt:formatDate value="${stu.birthday}" pattern="yyyy-MM-dd"></fmt:formatDate></td>
</tr>
</c:forEach>
</table>
14、SpringMVC 的攔截器:
- 針對請求和響應進行的額外的處理,在請求和響應的過程中添加預處理,後處理和最終處理
攔截器執行的時機:
1)preHandle():在請求被處理之前進行操作(預處理)
2)postHandle():在請求被處理之後,但結果還沒有渲染前進行操作,可以改變響應結果(後處理)
3)afterCompletion:所有的請求響應結束後執行善後工作,清理對象,關閉資源(最終處理)
攔截器實現的兩種方式:
1)繼承 HandlerInterceptorAdapter 的父類
2)實現 HandlerInterceptor 介面
攔截器實現的步驟:
1)創建登錄方法,在 session 中存儲用戶信息,用於進行許可權驗證
@RequestMapping("/login")
public String login(String name, String pwd, HttpServletRequest request){
if("zar".equalsIgnoreCase(name) && "123".equalsIgnoreCase(pwd)){
//在session中存儲用戶信息,用於進行許可權驗證
request.getSession().setAttribute("users",name);
return "main";
}else{
request.setAttribute("msg","用戶名或密碼不正確!");
return "login";
}
}
2)開發攔截器的功能,實現 HandlerInterceptor 介面,重寫 preHandle() 方法:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//是否登錄過的判斷
if(request.getSession().getAttribute("users") == null){
//此時就是沒有登錄,打回到登錄頁面,並給出提示
request.setAttribute("msg","您還沒有登錄,請先去登錄!");
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
return false;
}
return true;//放行請求
}
}
3)在 springmvc.xml 文件中註冊攔截器:
<mvc:interceptors>
<mvc:interceptor>
<!--映射要攔截的請求-->
<mvc:mapping path="/**"/>
<!--設置放行的請求-->
<mvc:exclude-mapping path="/showLogin"></mvc:exclude-mapping>
<mvc:exclude-mapping path="/login"></mvc:exclude-mapping>
<!--配置具體的攔截器實現功能的類-->
<bean class="com.bjpowernode.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
資源在WEB-INF目錄下:
很多企業會將動態資源放在WEB-INF目錄下,這樣可以保證資源的安全性,因為在WEB-INF目錄下的動態資源不可以直接訪問,必須要通過請求轉發的方式進行訪問,這樣避免了通過地址欄直接對資源的訪問,重定向也無法訪問動態資源。
慄子:
@Controller
public class WebInfAction {
@RequestMapping("/showMain")
public String showMain(){//通過請求轉發的方式訪問webapp目錄下的WEB-INF目錄下的main.jsp
System.out.println("訪問main.jsp");
return "main";
}
@RequestMapping("/showLogin")
public String showLogin(){////通過請求轉發的方式訪問webapp目錄下的WEB-INF目錄下的login.jsp
System.out.println("訪問login.jsp");
return "login";
}
}