springmvc筆記 狂神說
SpringMVC
1.回顧JavaWeb-MVC
- View: 負責顯示資料。eg: jsp
- Model層:負責實現業務,訪問資料庫 eg: service dao
- Controller層:eg: servlet
- 從View層取得表單資料
- 把表單資料提供給Model層
- 獲取Model層返回的資料,並帶著資料請求轉發或者重定向到新的頁面
回顧servlet
-
編寫前端資料
-
index.jsp
<body> <form action="${pageContext.request.contextPath}/hello" method="get"> <label> <input type="text" name="method" value="add"/> </label> <input type="submit"> </form> </body>
-
hello.jsp
<body> ${msg} </body>
-
-
編寫servlet
public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //獲取前端資料 String method = req.getParameter("method"); //進行資料處理,通常交給service和dao層去完成 if (method.equals("add")){ req.getSession().setAttribute("msg","執行了add方法"); } if (method.equals("delete")){ req.getSession().setAttribute("msg","執行了delete方法"); } //請求轉發到其他頁面 req.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
-
在web配置檔案中註冊servlet
-
啟動測試
2.hello-SpringMVC
2.1 什麼是springMVC
Spring MVC是Spring Framework的一部分,是基於Java實現MVC的輕量級Web框架。
檢視官方文件:https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/web.html#spring-web
我們為什麼要學習SpringMVC呢?
Spring MVC的特點:
- 輕量級,簡單易學
- 高效 , 基於請求響應的MVC框架
- 與Spring相容性好,無縫結合
- 約定優於配置
- 功能強大:RESTful、資料驗證、格式化、本地化、主題等
- 簡潔靈活
Spring的web框架圍繞DispatcherServlet [ 排程Servlet ] 設計。
DispatcherServlet的作用是將請求分發到不同的處理器。從Spring 2.5開始,使用Java 5或者以上版本的使用者可以採用基於註解形式進行開發,十分簡潔;正因為SpringMVC好 , 簡單 , 便捷 , 易學 , 天生和Spring無縫整合(使用SpringIoC和Aop) , 使用約定優於配置 . 能夠進行簡單的junit測試 . 支援Restful風格 .異常處理 , 本地化 , 國際化 , 資料驗證 , 型別轉換 , 攔截器 等等......所以我們要學習 。
最重要的一點還是用的人多 , 使用的公司多.
2.2 中心控制器
Spring的web框架圍繞DispatcherServlet設計。DispatcherServlet的作用是將請求分發到不同的處理器。從Spring 2.5開始,使用Java 5或者以上版本的使用者可以採用基於註解的controller宣告方式。
Spring MVC框架像許多其他MVC框架一樣, 以請求為驅動 , 圍繞一箇中心Servlet分派請求及提供其他功能,DispatcherServlet是一個實際的Servlet (它繼承自HttpServlet 基類)。
SpringMVC的原理如下圖所示:
當發起請求時被前置的控制器攔截到請求,根據請求引數生成代理請求,找到請求對應的實際控制器,控制器處理請求,建立資料模型,訪問資料庫,將模型響應給中心控制器,控制器使用模型與檢視渲染檢視結果,將結果返回給中心控制器,再將結果返回給請求者。
2.3 springMVC 的第一個程式
-
在web.xml中配置dispacterServelet
<!--1.註冊DispatcherServlet--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--關聯一個springmvc的配置檔案:【servlet-name】-servlet.xml--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!--啟動級別-1--> <load-on-startup>1</load-on-startup> </servlet> <!--/ 匹配所有的請求;(不包括.jsp)--> <!--/* 匹配所有的請求;(包括.jsp)--> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
編寫springmvc-servlet.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--處理器對映器--> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <!--處理器介面卡--> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> <!--檢視解析器:DispatcherServlet給他的ModelAndView--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver"> <!--字首--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--字尾--> <property name="suffix" value=".jsp"/> </bean> <!--註冊handler--> <bean id="/hello" class="com.iandf.controller.HelloController"/> </beans>
-
編寫Controller
public class HelloController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //建立一個模型檢視 ModelAndView mv = new ModelAndView(); //封裝物件 mv.addObject("msg", "HelloMVC"); //封裝要跳轉的檢視 mv.setViewName("hello");// /WEB-INF/jsp/hello.jsp 由InternalResourceViewResolver獲取完整路徑 return mv; } }
原生開發的步驟為
- 編寫servlet
- 在web.xml檔案中註冊servlet
springMVC開發步驟
- 在web.xml檔案中配置DispactherServlet
- 編寫springmvc-servlet.xml 配置對映器和介面卡和檢視直譯器
- 編寫Controller
- 註冊編寫完成的Controller.在對映器為BeanNameUrlHandlerMapping的情況下Controller的id屬性相當於servlet-mapping的url-pattern屬性,根據這個id找到對應的Controller
2.4 分析以上程式的執行流程
-
DispatcherServlet表示前置控制器,是整個SpringMVC的控制中心。使用者發出請求,DispatcherServlet接收請求並攔截請求。
我們假設請求的url為 : http://localhost:8080/SpringMVC/hello
如上url拆分成三部分:
- http://localhost:8080伺服器域名
- SpringMVC部署在伺服器上的web站點
- hello表示控制器
-
HandlerMapping為處理器對映。DispatcherServlet呼叫HandlerMapping,HandlerMapping根據請求url查詢Handler。
-
HandlerExecution表示具體的Handler,其主要作用是根據url查詢控制器,如上url被查詢控制器為:hello。
-
HandlerExecution將解析後的資訊傳遞給DispatcherServlet,如解析控制器對映等。
-
HandlerAdapter表示處理器介面卡,其按照特定的規則去執行Handler。
-
Handler讓具體的Controller執行。
-
Controller將具體的執行資訊返回給HandlerAdapter,如ModelAndView。
-
HandlerAdapter將檢視邏輯名或模型傳遞給DispatcherServlet。
-
DispatcherServlet呼叫檢視解析器(ViewResolver)來解析HandlerAdapter傳遞的邏輯檢視名。
-
檢視解析器將解析的邏輯檢視名傳給DispatcherServlet。
-
DispatcherServlet根據檢視解析器解析的檢視結果,呼叫具體的檢視。
-
最終檢視呈現給使用者。
SpringMVC的執行原理圖如下圖所示:
最終需要我們做的只有兩步
- 處理業務,得到資料
- 封裝資料和指明要轉發或者重定向的檢視
2.5 使用註解實現上面的例子
-
配置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"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--通過初始化引數指定SpringMVC配置檔案的位置,進行關聯--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!-- 啟動順序,數字越小,啟動越早 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
編寫springmvc-servlet.xml
- 開啟註解支援
- 自動掃描指定包下的註解
- 忽略包下的靜態檔案
- 註冊資源檢視解析器
- mvc:default-servlet-handler的作用
- springmvc會把所有的請求都當做對後臺控制器的請求,當我們對靜態資源進行訪問的時候(.html .jpg等)也會認為是對控制器的訪問,導致請求不到報404錯誤
- 使用
<mvc:default-servlet-handler/>
標籤。在 WEB 容器啟動的時候會在上下文中定義一個DefaultServletHttpRequestHandler
,它會對DispatcherServlet
的請求進行處理,如果該請求已經作了對映,那麼會接著交給後臺對應的處理程式,如果沒有作對映,就交給 WEB 應用伺服器預設的 Servlet 處理,從而找到對應的靜態資源,只有再找不到資源時才會報錯。
<?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.iandf.controller"/> <!--讓spring不處理靜態資源--> <mvc:default-servlet-handler/> <!--配置註解驅動,自動配置處理對映器和處理處理器介面卡--> <!-- 支援mvc註解驅動 在spring中一般採用@RequestMapping註解來完成對映關係 要想使@RequestMapping註解生效 必須向上下文中註冊DefaultAnnotationHandlerMapping 和一個AnnotationMethodHandlerAdapter例項 這兩個例項分別在類級別和方法級別處理。 而annotation-driven配置幫助我們自動完成上述兩個例項的注入。 --> <mvc:annotation-driven/> <!--配置檢視解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
-
編寫Controller
//@Controller註解的類會自動新增到Spring上下文中 @Controller public class HelloController { @RequestMapping("/hello") //對映訪問路徑 public String hello(Model model){ //Spring MVC會自動例項化一個Model物件用於向檢視中傳值 model.addAttribute("msg","hello_springmvc_annotation"); //返回檢視位置 return "hello"; } }
3. Controller
3.1 什麼是Controller
控制器作為應用程式邏輯的處理入口,它會負責去呼叫你已經實現的一些服務。通常,一個控制器會接收並解析使用者的請求,然後把它轉換成一個模型交給檢視,由檢視渲染出頁面最終呈現給使用者。
在註解配置成的Controller類中可以實現多個方法,意味著一個類中可以實現多個請求的業務處理。
3.2 Controller介面
Controller是一個介面,在org.springframework.web.servlet.mvc包下,介面中只有一個方法;
//實現該介面的類獲得控制器功能
public interface Controller {
//處理請求且返回一個模型與檢視物件
ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
}
- 可以使類實現controller介面,這是比較老的方式,這種方式實現的Controller只能包含一種方法,如果要處理多個請求就只能註冊多個Controller
- 可以使用註解的方式實現Controller, 可以發現,我們的兩個請求都可以指向一個檢視,但是頁面結果的結果是不一樣的,從這裡可以看出檢視是被複用的,而控制器與檢視之間是弱偶合關係。
- 使用註解實現Controller可以在一個Controller中處理多個請求
4. Resultful分格
4.1 Resultful風格簡介
概念
Restful就是一個資源定位及資源操作的風格。不是標準也不是協議,只是一種風格。基於這個風格設計的軟體可以更簡潔,更有層次,更易於實現快取等機制。
功能
資源:網際網路所有的事物都可以被抽象為資源
資源操作:使用POST、DELETE、PUT、GET,使用不同方法對資源進行操作。
分別對應 新增、 刪除、修改、查詢。
傳統方式操作資源 :通過不同的引數來實現不同的效果!方法單一,post 和 get
http://127.0.0.1/item/queryItem.action?id=1 查詢,GET
http://127.0.0.1/item/saveItem.action 新增,POST
http://127.0.0.1/item/updateItem.action 更新,POST
http://127.0.0.1/item/deleteItem.action?id=1 刪除,GET或POST
使用RESTful操作資源 :可以通過不同的請求方式來實現不同的效果!如下:請求地址一樣,但是功能可以不同!
http://127.0.0.1/item/1 查詢,GET
http://127.0.0.1/item 新增,POST
http://127.0.0.1/item 更新,PUT
http://127.0.0.1/item/1 刪除,DELETE
4.2 測試案例
4.2.1 使用傳統的URL進行請求
-
搭建環境
-
編寫Controller
@Controller public class ResultFulController { @RequestMapping("/add") public String add(int a, int b, Model model){ int result = a+b; model.addAttribute("msg",result); return "test"; } }
-
測試
在位址列輸入http://localhost:8080/add?a=1&b=2會得到結果。其引數名必須和int a, int b一樣否則會報錯,估計是後臺使用HttpServletRequest根據引數名去取值的,所以請求的時候必須一致。
4.2.2 使用ResultFul分格實現上面的案例
-
在Controller新增一個方法,接收resultful分格的請求
@RequestMapping("/addByResultFul/{a}/{b}") public String addByResultFul(@PathVariable int a,@PathVariable int b, Model model){ int result = a+b; model.addAttribute("msg",result); return "test"; }
- 使用@PathVariable將方法引數的值繫結在URL的模板變數上
-
測試
在位址列輸入http://localhost:8080/addByResultFul/1/2會自動的讓
a
和b
根據根據模板變數/addByResultFul/{a}/{b}拿到值
使用resultful分格的好處
-
是請求的路徑更加簡潔
-
獲取引數更加方便,框架會自動進行型別轉換
-
通過路徑對請求的引數進行約束,如果請求時引數錯誤,不會報轉換異常,而是報400,表示伺服器無法處理當前請求
提出問題:如果使用一個路徑,兩個不同的方法,引數不一樣,伺服器會自動識別引數得到結果嗎?
-
新增方法
@RequestMapping("/addByResultFul/{a}/{b}") public String addByResultFul1(@PathVariable int a,@PathVariable String b, Model model){ String result = a+b; model.addAttribute("msg",result); return "test"; }
-
測試
http://localhost:8080/addByResultFul/1/a報500錯誤Ambiguous mapping
結果:是不能的,請求路徑一樣,由於引數型別的不同伺服器是無法自動識別型別去處理業務的
4.2.3 使用method引數指定請求的型別
-
修改方法
@RequestMapping(value = "/addByResultFul/{a}/{b}",method = RequestMethod.POST) public String addByResultFul(@PathVariable int a,@PathVariable int b, Model model){ int result = a+b; model.addAttribute("msg",result); return "test"; }
-
測試
Spring MVC 的 @RequestMapping 註解能夠處理 HTTP 請求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。
所有的位址列請求預設都會是 HTTP GET 型別的。
方法級別的註解變體有如下幾個:組合註解
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@GetMapping 是一個組合註解,平時使用的會比較多!
它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一個快捷方式。
5. 通過SpringMVC來實現轉發和重定向
重定向 , 不需要檢視解析器 , 本質就是重新請求一個新地方嘛 , 所以注意路徑問題.
可以重定向到另外一個請求實現 .
@Controller
public class ResultSpringMVC2 {
@RequestMapping("/rsm2/t1")
public String test1(){
//轉發
return "test";
}
@RequestMapping("/rsm2/t2")
public String test2(){
//重定向
return "redirect:/index.jsp";
//return "redirect:hello.do"; //hello.do為另一個請求/
}
}
6. 獲取前端資料、將資料顯示到前端
6.1 獲取基本資料型別
6.1.1 URL中型別名字和方法中引數名一樣
提交資料 : http://localhost:8080/FundamentalTyp?name=iandf
處理方法 :
@RequestMapping("/FundamentalType")
public String getFundamentalTypeData(String name, Model model){
model.addAttribute("msg",name);
return "test";
}
6.1.2 URL中型別名字和方法中引數名不一樣
提交資料 : http://localhost:8080/FundamentalTyp?username=iandf
處理方法:
@RequestMapping("/FundamentalType")
public String getFundamentalTypeData(@RequestParam("username") String name, Model model){
model.addAttribute("msg",name);
return "test";
}
使用@RequestParam("xxx") 起別名,只要加了註解請求的URL上就要攜帶這個資料,如果不攜帶就會報400錯誤, Required String parameter 'username' is not present
6.2 獲取引用資料型別
要求提交的表單域和物件的屬性名一致 , 引數使用物件即可
-
實體類
@Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private int id; }
-
在Controller裡面實現這個請求
@RequestMapping("/referenceType") public String getReferenceTypeData(User user, Model model){ System.out.println(user); model.addAttribute("msg",user); return "test"; }
提出問題?
如果方法中有引數名和類中欄位名一樣,會怎麼樣?
提交資料 : http://localhost:8080/referenceType?name=iandf&id=1
@RequestMapping("/referenceType")
public String getReferenceTypeData(User user, Model model,String name){
System.out.println(user);
System.out.println("name -> " + name);
model.addAttribute("msg",user);
return "test";
}
結果:會共享表單提交的資料
如果提交的資料還有id沒有name,會怎麼樣
結果:name屬性會為NULL。如果沒有id,會為基本資料型別的預設值,這裡id是int所以會為0
6.3 資料顯示到前端
6.3.1 通過ModelAndView
public class ControllerTest1 implements Controller {
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
//返回一個模型檢視物件
ModelAndView mv = new ModelAndView();
mv.addObject("msg","ControllerTest1");
mv.setViewName("test");
return mv;
}
}
6.3.2 通過ModelMap
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, ModelMap model){
//封裝要顯示到檢視中的資料
//相當於req.setAttribute("name",name);
model.addAttribute("name",name);
System.out.println(name);
return "hello";
}
6.3.3 通過Model
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, Model model){
//封裝要顯示到檢視中的資料
//相當於req.setAttribute("name",name);
model.addAttribute("msg",name);
System.out.println(name);
return "test";
}
6.3.4 三種方式的對比
就對於新手而言簡單來說使用區別就是:
Model 只有寥寥幾個方法只適合用於儲存資料,簡化了新手對於Model物件的操作和理解;
ModelMap 繼承了 LinkedMap ,除了實現了自身的一些方法,同樣的繼承 LinkedMap 的方法和特性;
ModelAndView 可以在儲存資料的同時,可以進行設定返回的邏輯檢視,進行控制展示層的跳轉。
當然更多的以後開發考慮的更多的是效能和優化,就不能單單僅限於此的瞭解。
7. 亂碼問題
測試步驟:
1、我們可以在首頁編寫一個提交的表單
<form action="/e/t" method="post">
<input type="text" name="name">
<input type="submit">
</form>
2、後臺編寫對應的處理類
@Controller
public class Encoding {
@RequestMapping("/e/t")
public String test(Model model,String name){
model.addAttribute("msg",name); //獲取表單提交的值
return "test"; //跳轉到test頁面顯示輸入的值
}
}
3、輸入中文測試,發現亂碼
不得不說,亂碼問題是在我們開發中十分常見的問題,也是讓我們程式猿比較頭大的問題!以前亂碼問題通過過濾器解決 , 而SpringMVC給我們提供了一個過濾器 , 可以在web.xml中配置。
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
8. json
8.1 什麼是JSON?
- JSON(JavaScript Object Notation, JS 物件標記) 是一種輕量級的資料交換格式,目前使用特別廣泛。
- 採用完全獨立於程式語言的文字格式來儲存和表示資料。
- 簡潔和清晰的層次結構使得 JSON 成為理想的資料交換語言。
- 易於人閱讀和編寫,同時也易於機器解析和生成,並有效地提升網路傳輸效率。
在 JavaScript 語言中,一切都是物件。因此,任何JavaScript 支援的型別都可以通過 JSON 來表示,例如字串、數字、物件、陣列等。看看他的要求和語法格式:
- 物件表示為鍵值對,資料由逗號分隔
- 花括號儲存物件
- 方括號儲存陣列
JSON 鍵值對是用來儲存 JavaScript 物件的一種方式,和 JavaScript 物件的寫法也大同小異,鍵/值對組合中的鍵名寫在前面並用雙引號 "" 包裹,使用冒號 : 分隔,然後緊接著值:
{"name": "QinJiang"}
{"age": "3"}
{"sex": "男"}
很多人搞不清楚 JSON 和 JavaScript 物件的關係,甚至連誰是誰都不清楚。其實,可以這麼理解:
JSON 是 JavaScript 物件的字串表示法,它使用文字表示一個 JS 物件的資訊,本質是一個字串。
var obj = {a: 'Hello', b: 'World'}; //這是一個物件,注意鍵名也是可以使用引號包裹的
var json = '{"a": "Hello", "b": "World"}'; //這是一個 JSON 字串,本質是一個字串
8.2 程式碼測試
程式碼測試
1、新建一個module ,springmvc-05-json , 新增web的支援
2、在web目錄下新建一個 json-1.html , 編寫測試內容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSON_秦疆</title>
</head>
<body>
<script type="text/javascript">
//編寫一個js的物件
var user = {
name:"秦疆",
age:3,
sex:"男"
};
//將js物件轉換成json字串
var str = JSON.stringify(user);
console.log(str);
//將json字串轉換為js物件
var user2 = JSON.parse(str);
console.log(user2.age,user2.name,user2.sex);
</script>
</body>
</html>
3、在IDEA中使用瀏覽器開啟,檢視控制檯輸出!
8.3 在controller中返回JSON資料
Jackson應該是目前比較好的json解析工具了。
當然工具不止這一個,比如還有阿里巴巴的 fastjson 等等。
-
我們這裡使用Jackson,使用它需要匯入它的jar包;
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>
-
配置web.xml和springmvc-servlet.xml
-
編寫Controller
@Controller public class JsonController { @ResponseBody @RequestMapping("/json") public String testObject(){ User user = new User("黃鶴",1); ObjectMapper mapper = new ObjectMapper(); String json = null; try { json = mapper.writeValueAsString(user); } catch (JsonProcessingException e) { e.printStackTrace(); } return json; } }
-
測試發現亂碼
8.3.1 json亂碼問題的解決
通過requestMapping的produces屬性來解決亂碼問題
//produces:指定響應體返回型別和編碼
@RequestMapping(value = "/json1",produces = "application/json;charset=utf-8")
但是專案中使用到JSON的地方很多,所以每個前面加一行很繁瑣,這裡有一種統一的解決辦法。
在springmvc-servlet.xml中配置message-converters
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
返回json字串統一解決
在類上直接使用 @RestController ,這樣子,裡面所有的方法都只會返回 json 字串了,不用再每一個都新增@ResponseBody !我們在前後端分離開發中,一般都使用 @RestController ,十分便捷!
@RestController
public class JsonController {
@RequestMapping(value = "/json")
public String testObject(){
User user = new User("黃鶴",1);
ObjectMapper mapper = new ObjectMapper();
String json = null;
try {
json = mapper.writeValueAsString(user);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return json;
}
}
8.3.2 測試集合輸出
@RequestMapping(value = "/json1")
public String testList() {
List<User> users = new ArrayList<User>();
User user = new User("黃鶴", 1);
users.add(user);
user = new User("黃鶴", 2);
users.add(user);
user = new User("黃鶴", 3);
users.add(user);
user = new User("黃鶴", 4);
users.add(user);
ObjectMapper mapper = new ObjectMapper();
String json = null;
try {
json = mapper.writeValueAsString(users);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return json;
}
測試結果
8.3.3 測試時間輸出
@RequestMapping(value = "/json2")
public String testDate() {
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ObjectMapper mapper = new ObjectMapper();
String json = null;
try {
json = mapper.writeValueAsString(format.format(date));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return json;
}
測試結果
也可以使用jackson自帶的屬性來設定時間格式,預設格式是時間戳是1970年1月1日到當前日期的毫秒數!
ObjectMapper mapper = new ObjectMapper();
//不使用時間差的方式
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//自定義日期格式物件
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
//設定日期格式
mapper.setDateFormat(sdf);
8.3.4 抽取成工具類
前面三個方法中有大量重複的程式碼,我們可以抽取成一個工具類。減少冗餘的程式碼
public class JsonUtil {
public static String getJson(Object obj){
return getJson(obj,"yyyy-MM-dd HH:mm:ss");
}
public static String getJson(Object obj,String dateFormat){
String json = "";
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
mapper.setDateFormat(sdf);
try {
json = mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return json;
}
}
那上面的程式碼簡化為
@RequestMapping(value = "/json2")
public String testDate() {
Date date = new Date();
return JsonUtil.getJson(date);
}
9. 攔截器
9.1 interceptor簡介
SpringMVC的處理器攔截器類似於Servlet開發中的過濾器Filter,用於對處理器進行預處理和後處理。開發者可以自己定義一些攔截器來實現特定的功能。
過濾器與攔截器的區別:攔截器是AOP思想的具體應用。
過濾器
- servlet規範中的一部分,任何java web工程都可以使用
- 在url-pattern中配置了/*之後,可以對所有要訪問的資源進行攔截
攔截器
- 攔截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
- 攔截器只會攔截訪問的控制器方法, 如果訪問的是jsp/html/css/image/js是不會進行攔截的
9.2 自定義攔截器
想要自定義攔截器,必須實現 HandlerInterceptor 介面。
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("攔截前");
//必須寫true,寫true會執行下一個攔截器,寫false會卡在這個攔截器上
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("攔截後");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("處理完成後,清理");
}
}
在applicationContext.xml中配置攔截器
<!--配置攔截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--攔截所有請求-->
<mvc:mapping path="/**"/>
<bean class="com.iandf.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
Controller測試
@RestController
public class InterceptorController {
@RequestMapping("/i1")
public String test(){
System.out.println("執行請求");
return "hello";
}
}
開啟IDEA,請求controller測試結果為
9.3 使用攔截器驗證使用者是否登入
實現思路:
- 由於攔截器只會攔截通過控制器的方法,所以訪問主頁和登入頁面都走控制器
- 定義一個登入攔截器,如果是登入的請求或者session中已經攜帶相關資訊則放行否則進行轉發
程式碼實現
-
index.jsp
<body> <a href="${pageContext.request.contextPath}/user/goLogin">進入登入頁面</a> <br> <a href="${pageContext.request.contextPath}/user/goMain">進入首頁</a> </body>
-
login.jsp
<body> <form action="${pageContext.request.contextPath}/user/login" method="get"> 使用者名稱:<input type="text" name="userName" /> 密碼:<input type="text" name="password" /> <input type="submit"> </form> </body>
-
main.jsp
<body> <h1>首頁</h1> ${userName} <a href="${pageContext.request.contextPath}/user/goOut">登出</a> </body>
-
loginController
@RequestMapping("/user") @Controller public class LoginController { @RequestMapping("/goLogin") public String goLogin(){ return "login"; } @RequestMapping("/goMain") public String goMain(){ return "main"; } @RequestMapping("/login") public String login(HttpSession session,String userName, String password, Model Model){ System.out.println("userName -> "+userName); System.out.println("password -> "+password); session.setAttribute("UserInfo",userName); Model.addAttribute("userName",userName); return "main"; } @RequestMapping("/goOut") public String goOut(HttpSession session){ session.removeAttribute("UserInfo"); return "login"; } }
-
攔截器
//攔截所有user/xxx/xxx的請求,並判斷使用者是否登入此頁面 public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //如果是/user/login 或者是user/goLogin就放行 if (request.getRequestURL().toString().contains("login")){ return true; } //如果session中已經有了userInfo資訊也放行 if (request.getSession().getAttribute("UserInfo")!= null){ return true; } //如果不是上面兩種情況就轉發到登入頁面 request.getRequestDispatcher(request.getContextPath()+"/WEB-INF/jsp/login.jsp").forward(request,response); return false; } }
-
註冊攔截器
<mvc:interceptor> <mvc:mapping path="/user/**"/> <bean class="com.iandf.interceptor.LoginInterceptor"/> </mvc:interceptor>
測試結果無誤
10. 檔案的上傳和下載
10.1 檔案上傳
準備工作
檔案上傳是專案開發中最常見的功能之一 ,springMVC 可以很好的支援檔案上傳,但是SpringMVC上下文中預設沒有裝配MultipartResolver,因此預設情況下其不能處理檔案上傳工作。如果想使用Spring的檔案上傳功能,則需要在上下文中配置MultipartResolver,且id必須為。
前端表單要求:為了能上傳檔案,必須將表單的method設定為POST,並將enctype設定為multipart/form-data。只有在這樣的情況下,瀏覽器才會把使用者選擇的檔案以二進位制資料傳送給伺服器;
對錶單中的 enctype 屬性做個詳細的說明:
- application/x-www=form-urlencoded:預設方式,只處理表單域中的 value 屬性值,採用這種編碼方式的表單會將表單域中的值處理成 URL 編碼方式。
- multipart/form-data:這種編碼方式會以二進位制流的方式來處理表單資料,這種編碼方式會把檔案域指定檔案的內容也封裝到請求引數中,不會對字元編碼。
- text/plain:除了把空格轉換為 "+" 號外,其他字元都不做編碼處理,這種方式適用直接通過表單傳送郵件。
<form action="" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit">
</form>
一旦設定了enctype為multipart/form-data,瀏覽器即會採用二進位制流的方式來處理表單資料,而對於檔案上傳的處理則涉及在伺服器端解析原始的HTTP響應。在2003年,Apache Software Foundation釋出了開源的Commons FileUpload元件,其很快成為Servlet/JSP程式設計師上傳檔案的最佳選擇。
- Servlet3.0規範已經提供方法來處理檔案上傳,但這種上傳需要在Servlet中完成。
- 而Spring MVC則提供了更簡單的封裝。
- Spring MVC為檔案上傳提供了直接的支援,這種支援是用即插即用的MultipartResolver實現的。
- Spring MVC使用Apache Commons FileUpload技術實現了一個MultipartResolver實現類:
- CommonsMultipartResolver。因此,SpringMVC的檔案上傳還需要依賴Apache Commons FileUpload的元件。
檔案上傳
1、匯入檔案上傳的jar包,commons-fileupload , Maven會自動幫我們匯入他的依賴包 commons-io包;
<!--檔案上傳-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!--servlet-api匯入高版本的-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
2、配置bean:multipartResolver
【注意!!!這個bena的id必須為:multipartResolver , 否則上傳檔案會報400的錯誤!】
<!--檔案上傳配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 請求的編碼格式,必須和jSP的pageEncoding屬性一致,以便正確讀取表單的內容,預設為ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上傳檔案大小上限,單位為位元組(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
CommonsMultipartFile 的 常用方法:
-
String getOriginalFilename():獲取上傳檔案的原名
-
InputStream getInputStream():獲取檔案流
-
void transferTo(File dest):將上傳檔案儲存到一個目錄檔案中
程式碼測試
<form action="/upload" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit" value="upload">
</form>
Controller: 有兩種方式實現檔案上傳,第二種其實是對第一種方式的封裝
@RestController
public class FileController {
//@RequestParam("file") 將name=file控制元件得到的檔案封裝成CommonsMultipartFile 物件
//批量上傳CommonsMultipartFile則為陣列即可
@RequestMapping(path = "/upload",method = RequestMethod.POST)
public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {
System.out.println("fileUpload");
//獲取檔名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
//如果檔名為空,直接回到首頁!
if ("".equals(uploadFileName)){
return "redirect:/index.jsp";
}
System.out.println("上傳檔名 : "+uploadFileName);
//上傳路徑儲存設定
String path = request.getServletContext().getRealPath("/upload");
//如果路徑不存在,建立一個
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
System.out.println("上傳檔案儲存地址:"+realPath);
InputStream is = file.getInputStream(); //檔案輸入流
OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //檔案輸出流
//讀取寫出
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
os.write(buffer,0,len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}
/*
* 採用file.Transto 來儲存上傳的檔案,檔名和型別如果相同則會覆蓋原來的檔案
*/
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上傳路徑儲存設定
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
//上傳檔案地址
System.out.println("上傳檔案儲存地址:"+realPath);
//通過CommonsMultipartFile的方法直接寫檔案(注意這個時候)
file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
return "redirect:/index.jsp";
}
}
測試程式碼無誤
10.2 檔案下載
檔案下載步驟:
1、設定 response 響應頭
2、讀取檔案 -- InputStream
3、寫出檔案 -- OutputStream
4、執行操作
5、關閉流 (先開後關)
程式碼實現
@RequestMapping(value="/download")
public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception{
//要下載的圖片地址
String path = request.getServletContext().getRealPath("/upload");
String fileName = "基礎語法.jpg";
//1、設定response 響應頭
response.reset(); //設定頁面不快取,清空buffer
response.setCharacterEncoding("UTF-8"); //字元編碼
response.setContentType("multipart/form-data"); //二進位制傳輸資料
//設定響應頭
response.setHeader("Content-Disposition",
"attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
File file = new File(path,fileName);
//2、 讀取檔案--輸入流
InputStream input=new FileInputStream(file);
//3、 寫出檔案--輸出流
OutputStream out = response.getOutputStream();
byte[] buff =new byte[1024];
int index=0;
//4、執行 寫出操作
while((index= input.read(buff))!= -1){
out.write(buff, 0, index);
out.flush();
}
out.close();
input.close();
return null;
}
程式碼測試成功