SpringMVC是一種基于Java的Web框架,?用于構建Web應用程序。?
SpringMVC的核心在于其設計模式——MVC(?Model-View-Controller)?,?這種設計模式將應用程序的數據處理、?用戶接口和控制邏輯分開,?使得代碼結構更加清晰,?便于維護和擴展。?在SpringMVC中,?Model代表數據和相關的業務邏輯,?View負責顯示數據給用戶,?而Controller則是協調Model和View的橋梁,?處理用戶請求并返回相應的視圖。
MVC全稱Model View Controller,是一種設計創建Web應用程序的模式。這三個單詞分別代表Web應用程序的三個部分:
(1)前端控制器 DispatcherServlet(不需要開發,由框架提供【核心】)
DispatcherServlet 是 Spring MVC 的入口函數。接收請求,響應結果,相當于轉發器,中央處理器。有了 DispatcherServlet ,可以大大減少其它組件之間的耦合度。
用戶請求到達前端控制器,就相當于 mvc 模式中的 c,DispatcherServlet 是整個流程控制的中心,由它調用其它組件來處理用戶的請求。
(2)處理器映射器 HandlerMapping (不需要開發,由框架提供)
HandlerMapping 負責根據用戶請求(URL),找到相應的 Handler 即處理器(Controller),SpringMVC 提供了不同映射器實現的不同映射方式,例如:配置文件方式,實現接口方式,注解方式等。
(3)處理器適配器 HandlerAdapter (不需要開發,由框架提供)
按照特定規則(HandlerAdapter 要求的規則)去執行 Handler,通過 HandlerAdapter 對處理器進行執行,這是適配器模式的應用,通過擴展適配器可以對更多類型的處理器進行處理。
(4)處理器 Handler (需要工程師開發)
Handler 是繼 DispatcherServlet 前端控制器的后端控制器,在 DispatcherServlet 的控制下,Handler 對具體的用戶請求進行處理。由于 Handler 涉及到具體的用戶業務請求,所以一般情況下需要工程師根據業務需求來開發 Handler。
(5)視圖解析器 View Resolver (不需要開發,由框架提供)
作用:進行視圖解析,根據邏輯視圖名解析成真正的視圖(View),View Resolver 負責將處理結果生成 View 視圖。首先,根據邏輯視圖名解析成物理視圖名(即具體的頁面地址),再生成 View 視圖對象,最后對 View 進行渲染,將處理結果通過頁面展示給用戶。
Spring MVC 框架提供了很多的 View 視圖類型,包括:jstlView、freemarkerView、pdfView 等。 一般情況下,需要通過頁面標簽或頁面模版技術,將模型數據通過頁面展示給用戶,這需要由工程師根據業務需求開發具體的頁面。
(6)視圖 View (需要工程師開發)
View 是一個接口,實現類才可以支持不同的View類型(jsp、freemarker、pdf...)
HandlerMapping負責根據用戶請求url找到Handler即處理器,springmvc提供了不同的映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,注解方式等。
Handler 是繼DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler對具體的用戶請求進行處理。
ViewResolver通過HandlerAdapter對處理器進行執行,這是適配器模式的應用,通過擴展適配器可以對更多類型的處理器進行執行。
Spring MVC所有的請求都經過DispatcherServlet來統一分發。DispatcherServlet將請求分發給Controller之前,需要借助于Spring MVC提供的HandlerMapping定位到具體的Controller。 HandlerMapping接口負責完成客戶請求到Controller映射。 Controller接口將處理用戶請求,這和Java Servlet扮演的角色是一致的。一旦Controller處理完用戶請求,則返回ModelAndView(數據和視圖)對象給DispatcherServlet前端控制器。從宏觀角度考慮,DispatcherServlet是整個Web應用的控制器;從微觀考慮,Controller是單個Http請求處理過程中的控制器,而ModelAndView是Http請求過程中返回的模型(Model)和視圖(View)。 返回的視圖需要通過ViewResolver接口(視圖解析器)在Web應用中負責查找View對象,從從而將相應結果渲染給客戶。
<!-- 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>
<!-- jsp依賴 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<?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的核心對象
訪問mymvc地址后,報錯,文件沒有找到。找到文件是/WEB-INF/springmvc-servlet.xml或者myweb-servlet.xml(這個)
錯誤原因:在Servlet的init()方法中,創建springmvc使用的容器對象WebApplicationContext
WebApplicationContext ctx=new ClassPathXmlApplicationContext(配置文件)
配置文件的默認路徑:/WEB-INF/<servlet-name>-servlet.xml
DispatcherServlet作用:
1.在init()中創建springmvc的容器對象 WebApplicationContext,創建springmvc配置文件的所有Java對象。
java對象就是Controller對象
2.DispatcherServlet 是一個Servlet,能夠接受請求。
-->
<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>
<!-- 在服務器啟動時候創建對象,和容器的順序 在啟動時裝載對象 隨意給個值要求大于等于0 數值越小,創建的越早-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- url-pattern 作用:把一些請求交給servlet處理 就例如將/mymvc交給springmvc處理
使用中央調度器(DispatcherServlet) 1.使用擴展名方式,格式/*.xxx 例如:xxx.xml表示以xml結尾的都算
-->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>第一個springmvc</title>
</head>
<body>
<a href="some.do">發起一個som.do的請求</a>
</body>
</html>
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/** @Controller: 創建控制器(處理器)對象
* 控制器:叫做后端控制器(back controller),自定義的類處理請求的。
* 位置:在類的上面,表示創建此類的對象,對象放在springmvc的容器中
*
*/
@Controller
public class MyController {
/*
Springmvc框架使用 ,使用控制器類中的方法,處理請求
方法的特點: 1.方法的形參,表示請求中的參數 2.方法的返回值,表示本次請求的處理請求
*/
/**
* @RequestMapping :請求映射
* 屬性:value 請求中的uri地址,唯一值,以"/"開頭
* 位置:1.在方法上面(必須) 2.在類定義的上面(可選)
* 作用:指定的請求,交給指定的方法處理,等同于url-pattern(個人理解 相當于可以做doget相關的操作)
* 返回值ModelAndView:表示本次請求的處理結果(數據和視圖) model:表示數據 view:表示視圖
*/
//可以在一個類中定義多個方法使用多個@RequestMapping注解
@RequestMapping(value={"/some.do","/first.do"}) //value是一個數組,可以有多個值,相當于將該方法起一個名字
public ModelAndView doSome(){ //doGet()
//使用這個方法處理請求,能夠處理請求的方法叫做控制器方法
//調用service對象,處理請求,返回數據
ModelAndView mv=new ModelAndView();
//添加數據
mv.addObject("msg","在ModelAddView中處理了some.do的請求");
mv.addObject("fun","執行了dosome的方法");
//指定視圖,setviewName("視圖路徑") 相當于請求轉發request.getRequestDis...("/show.jsp").forward(..)
// mv.setViewName("/WEB-INF/view/show.jsp");
//當配置了視圖解析器,使用文件名稱作為視圖名使用,叫做視圖邏輯名稱
//使用了邏輯名稱,框架使用配置文件中視圖解析器的前綴和后綴,拼接為完整地視圖路徑 ,例如/WEB-INF/view/ + show + .jsp
mv.setViewName("show");
/*
當框架調用完dosome方法后,得到返回中modelandview 框架會在后續的處理邏輯值,處理mv對象里的數據和視圖
對數據執行requert,setAttribute(“msg”,“處理了some.do請求”);把數據放到request作用域中
對視圖進行轉發操作
*/
return mv;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
/show.jsp,顯示request作用域中的數據<br>
<h2>msg數據:<%=request.getAttribute("msg")%></h2>
<h2>fun數據:${fun}</h2>
</body>
</html>
<?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">
<!--spring的配置文件 聲明組件掃描器-->
<context:component-scan base-package="com.aiowang.controller"/>
<!-- 聲明視圖解析器;幫助處理視圖 主要幫助我們處理重復的多余的冗余路徑等-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前綴:指定試圖文件的路徑-->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 后綴,試圖文件的擴展名-->
<property name="suffix" value=".jsp"/> <!--表示所有的jsp文件-->
</bean>
</beans>
(1)Tomcat9.0下載 https://tomcat.apache.org/download-90.cgi
(2)IDEA配置Tomcat
打開配置選項
找到左側Tomcat圖標,新建,選擇下載好并解壓的Tomcat路徑
部署
正常運行,成功
(1) 導入依賴
junit是測試用的
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
(2)基本使用
新建一個測試類HelloLog.java
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
public class LogTest {
//日志對象
private Log log=LogFactory.getLog(LogTest.class);
@Test
public void test1(){
log.trace("hello trace!");
log.debug("hello debug");
log.info("hello info");
log.warn("hello warn");
log.error("hello error");
log.fatal("hello fatal");
}
}
(3)新建log4j配置文件
寫好了測試類運行發現沒有打印 因為配置追加器,追加器:<appender>的意思是我們的日志要輸出到哪里 寫一個log4j的配置文件 新建log4j.xml,配置信息如下
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE log4j:configuration PUBLIC "-//LOGGER"
"http://org/apache/log4j/xml/log4j.dtd">
<log4j:configuration>
<!--org.apache.log4j.ConsoleAppender 輸出到控制臺-->
<appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
<!--輸出格式-->
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n"/>
<!--%-d{yyyy-MM-dd HH:mm:ss,SSS}是當前時間
[%c]是日志出現的包和類 %p是日志的級別 %m是message也就是日志的消息,%n是換行符 -->
</layout>
</appender>
<!-- 輸出到文件H:/log/hello.log中-->
<appender name="myFile1" class="org.apache.log4j.RollingFileAppender">
<param name="File" value="D:/log/hello.log"/><!--文件位置-->
<param name="Append" value="true"/><!--是否選中追加-->
<param name="MaxFileSize" value="1kb"/><!--文件最大字節數-->
<param name="MaxBackupIndex" value="2"/>
<!--第一個文件超出上面設置的文件最大字節數后,
可以新增的新文件數量,這里只能新增2個,
當日志文件要輸出的內容超出3個1kb(第一個加上新增的兩個),則覆蓋新增的第一個文件,再覆蓋第二個文件-->
<!--日志的輸出格式-->
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n"/>
<!--%-d{yyyy-MM-dd HH:mm:ss,SSS}是輸出當前時間
[%c]是輸出日志出現的包和類 %p是日志的級別 %m是message也就是日志的消息,%n是換行符 -->
</layout>
</appender>
<!-- 輸出到文件,每天輸出一個文件-->
<appender name="myFile2" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="h:/log/world.log"/>
<param name="Append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n"/>
</layout>
</appender>
<root>
<!--優先級設置,all < trace < debug < info < warn < error < fatal < off-->
<priority value="all"/>
<appender-ref ref="myConsole"/>
<appender-ref ref="myFile1"/>
<appender-ref ref="myFile2"/>
</root>
</log4j:configuration>
(4)日志輸出格式
Spring MVC 框架中的常用注解主要包括在控制器層(Controller)、服務層(Service)、數據訪問層(Repository)、實體類(Entity)、請求參數(Request Parameters)等方面。以下是這些注解的主要含義和用例 @Autowired、@ComponentScan、@Configuration 和 @Bean 是 Spring 框架中常用的注解,用于實現依賴注入和配置管理。
1、@Controller: 含義: 標識一個類為 Spring MVC 控制器。 用例:
@Controller
public class MyController {
// Controller methods
}
2、@RequestMapping: 含義: 映射 HTTP 請求的 URL 到一個具體的處理方法。 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/hello")
public String hello() {
return "hello";
}
}
3、@RequestParam: 含義: 用于提取請求中的參數值。 客戶端發送請求 /example/greet?name=John 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/greet")
public String greet(@RequestParam("name") String name) {
return "Hello, " + name + "!";
}
}
4、@PathVariable: 含義: 用于將 URI 模板變量映射到處理方法的參數。 客戶端發送請求 /example/user/123 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/user/{id}")
public String getUserById(@PathVariable("id") Long userId) {
// Retrieve user with the specified ID
return "userDetails";
}
}
5、@PatchMapping: 含義:用于映射PATCH請求到控制器方法。@PatchMapping是一個用于映射HTTP PATCH請求到控制器方法的注解,在SpringMVC中也可以使用。它可以用于方法級別,用于指定處理PATCH請求的方法。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@PatchMapping("/{id}")
public String updateUser(@PathVariable Long id, @RequestBody User user) {
// ...
}
}
1、@GetMapping: (查詢) 含義:處理 HTTP GET 請求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable Long id) {
// ...
}
}
2、@PostMapping: (新增) 含義:處理 HTTP POST 請求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@PostMapping
public String createUser(@ModelAttribute User user) {
// ...
}
}
3、@PutMapping:(更新) 含義:處理 HTTP PUT 請求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@PutMapping("/{id}")
public String updateUser(@PathVariable Long id, @ModelAttribute User user) {
// ...
}
}
4、@DeleteMapping:(刪除) 含義:處理 HTTP DELETE 請求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
// ...
}
}
5、@PatchMapping: 含義:處理 HTTP PATCH 請求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@PatchMapping("/{id}")
public String updateUser(@PathVariable Long id, @RequestBody User user) {
// ...
}
}
1、@Service: 含義: 標識一個類為服務層的組件。 用例:
@Service
public class MyService {
// Service methods
}
@Service 是 Spring Framework 中的一個注解,用于標識一個類為服務層(Service Layer)的組件。服務層通常包含應用程序的業務邏輯,負責處理業務規則、調用數據訪問層(Repository 或 DAO)執行數據庫操作,并協調應用程序的不同部分
組件掃描: @Service 是 Spring 的組件掃描機制的一部分,標識帶有該注解的類為一個服務層組件。在應用程序啟動時,Spring 會掃描包路徑下的所有組件,并注冊為 Spring 容器中的 Bean。
依賴注入: 通過將 @Service 注解添加到類上,Spring IoC 容器會自動將該類的實例注入到其他需要依賴的組件中,例如控制器(Controller)或其他服務層組件。
事務管理: 在服務層執行的方法通常涉及數據庫操作,@Service 注解通常與 @Transactional 注解一起使用,以啟用事務管理。這確保了在業務方法中的一系列操作要么全部成功,要么全部失?。ɑ貪L)。
用例:
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
public String performBusinessLogic() {
// Business logic implementation
return "Business logic executed successfully";
}
public List<MyEntity> getAllEntities() {
return myRepository.findAll();
}
}
1、@Repository: 含義: 標識一個類為數據訪問層的組件,通常與 Spring 的數據訪問異常轉換一起使用。 用例:
@Repository
public class MyRepository {
// Repository methods
}
1、@Entity: 含義: 標識一個類為 JPA 實體類。 用例:
@Entity
public class User {
// Entity properties and methods
}
@Entity 注解是 Java Persistence API (JPA) 的一部分,用于標識一個類為 JPA 實體類。JPA 是一種規范,用于描述如何通過 Java 對象與關系型數據庫進行映射。@Entity 注解告訴 JPA,被注解的類將映射到數據庫中的一個表。
數據庫映射: @Entity 注解告訴 JPA 這個類與數據庫中的表存在映射關系。類中的字段(成員變量)通常與表中的列相對應。
主鍵標識: 實體類通常需要一個主鍵,用于唯一標識每個實體對象。通過 @Entity 注解,JPA 可以識別實體類中的主鍵。
實體類識別: 當應用程序使用 JPA 進行持久化操作時,JPA 需要知道哪些類是實體類。@Entity 注解是 JPA 識別實體類的標志。
持久性操作: 通過實體類,可以執行 CRUD(Create, Read, Update, Delete)操作。JPA 提供了 EntityManager 接口,可以用于執行這些操作。
關系映射: 實體類之間的關系可以通過 JPA 進行映射,包括一對一、一對多、多對一、多對多等關系。
示例:
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="username")
private String username;
@Column(name="email")
private String email;
// Getters and setters
}
1、@RequestBody: 含義: 用于將 HTTP 請求的正文映射到方法參數。 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/processJson")
public String processJson(@RequestBody MyJsonModel jsonModel) {
// Process JSON data
return "result";
}
}
2、@ResponseBody: 含義: 表示方法的返回值直接作為響應體,而不是視圖名稱。 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/getJson")
@ResponseBody
public MyJsonModel getJson() {
// Return JSON data directly
}
}
含義: 用于自動裝配,將指定類型的 Bean 注入到屬性、構造函數或方法參數中。 用例:
@Service
public class MyService {
private final MyRepository repository;
@Autowired
public MyService(MyRepository repository) {
this.repository=repository;
}
}
在上例中,MyService 類通過 @Autowired 注解將 MyRepository 類型的 Bean 自動注入到構造函數中。
含義: 掃描指定包路徑,尋找標有 @Component、@Service、@Repository、@Controller 注解的類,并將其注冊為 Spring Bean。 用例:
@Configuration
@ComponentScan(basePackages="com.example")
public class AppConfig {
// Configuration content
}
在上例中,@ComponentScan 注解掃描 com.example 包路徑下的所有類,將帶有相應注解的類注冊為 Spring Bean。
含義: 聲明當前類是一個配置類,通常與 @Bean 注解一起使用,用于配置 Spring 應用上下文。 用例:
@Configuration
public class AppConfig {
// Bean declarations using @Bean
}
在上例中,AppConfig 被聲明為配置類,用于定義 Spring Bean。
含義: 在配置類中使用,用于聲明一個 Bean。 用例:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService(myRepository());
}
@Bean
public MyRepository myRepository() {
return new MyRepository();
}
}
在上例中,@Bean 注解用于聲明兩個 Bean:MyService 和 MyRepository。
springmvc靜態資源配置
在javaweb項目中配置了DispatcherServlet的情況下,如果不進行額外配置的話,幾乎所有的請求都會走這個servlet來處理,默認靜態資源按路徑是訪問不到的會報404錯誤,下面講一講如何配置才能訪問到靜態資源,本文將介紹三種方法
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<async-supported>false</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
@Configuration
@EnableWebMvc
public class MyMvcConfigurer implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// tomcat默認處理靜態資源的servlet名稱為default,不指定也可以DefaultServletHttpRequestHandler.setServletContext會自動獲取
// configurer.enable("default");
configurer.enable();
}
}
上述配置完成后org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#defaultServletHandlerMapping 方法會生成一個類名為SimpleUrlHandlerMapping的bean,當其他handlerMapping無法處理請求時會接著調用SimpleUrlHandlerMapping對象進行處理。SimpleUrlHandlerMapping中有一個urlMap屬性,key為請求路徑匹配模式串,/**能匹配所有的路徑, value為handler匹配完成后會調用handler處理請求。 接著調用DefaultServletHttpRequestHandler的handleRequest方法處理請求,邏輯比較簡單,獲取請求轉發器進行請求轉發交給tomcat默認的servlet來進行處理。
@Configuration
@EnableWebMvc
public class MyMvcConfigurer implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
}
和第一種配置幾乎一樣,其實只是換了一個handler類型來處理請求罷了。
上述配置完成后org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#resourceHandlerMapping 方法會生成一個類名為SimpleUrlHandlerMapping的bean,當其他handlerMapping無法處理請求時會接著調用SimpleUrlHandlerMapping對象進行處理
ResourceHttpRequestHandler比DefaultServletHttpRequestHandler的構建稍微復雜一點。之后也是調用SimpleUrlHandlerMapping相同的邏輯先根據請求路徑匹配找到對應處理的handler,這里對應的是ResourceHttpRequestHandler之后調用handleRequest方法,原理是先根據請求的路徑找到對應的資源文件,再獲取資源文件的輸入流寫入到response響應中。
就是利用容器自身的默認Servlet, 以Tomcat為例,如下圖有一個默認的Servlet,名稱就是default(也可以在tomcat的配置文件中修改為其他名稱,是在tomcat的目錄/conf/web.xml中配置的)。
我們只需要在web項目的web.xml中配置靜態文件是由此Servlet來映射即可。 default是容器的默認servlet的名稱,示例為tomcat容器,其他容器根據實際情況來,如果tomcat配置文件修改了默認servlet名稱,則也要修改為實際的。
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/static/*</url-pattern>
</servlet-mapping>
將帶有/static/xxx 路徑的請求直接交給tomcat默認的servlet去進行處理
<mvc:resources mapping="/images/**" location="/images/" />
<mvc:resources mapping="/js/**" location="/js/" />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources location="/,classpath:/META-INF/publicResources/" mapping="/resources/**"/>
因為上面的location屬性節點是Resource資源, 因此可以使用classpath這類寫法。 <mvc:resources />更進一步,由Spring MVC框架自己處理靜態資源,并添加一些有用的附加值功能。
首先,<mvc:resources />允許靜態資源放在任何地方,如WEB-INF目錄下、類路徑下等,你甚至可以將JavaScript等靜態文件打到JAR包中。通過location屬性指定靜態資源的位置,由于location屬性是Resources類型,因此可以使用諸如"classpath:"等的資源前綴指定資源位置。傳統Web容器的靜態資源只能放在Web容器的根路徑下,<mvc:resources />完全打破了這個限制。
其次,<mvc:resources />依據當前著名的Page Speed、YSlow等瀏覽器優化原則對靜態資源提供優化。你可以通過cacheSeconds屬性指定靜態資源在瀏覽器端的緩存時間,一般可將該時間設置為一年,以充分利用瀏覽器端的緩存。在輸出靜態資源時,會根據配置設置好響應報文頭的Expires 和 Cache-Control值。
在接收到靜態資源的獲取請求時,會檢查請求頭的Last-Modified值,如果靜態資源沒有發生變化,則直接返回303相應狀態碼,提示客戶端使用瀏覽器緩存的數據,而非將靜態資源的內容輸出到客戶端,以充分節省帶寬,提高程序性能。
第一種方式是要定義的Interceptor類要實現Spring的HandlerInterceptor 接口
第二種方式是繼承實現了抽象類HandlerInterceptorAdapter
public class UserInterceptor implements HandlerInterceptor{
/**
* 該方法在整個請求完成后執行,主要用來清理資源
* 該方法只能在當前interceptor的preHandler方法的返回值是true時才會執行
*/
@Override
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
}
/**
* 該方法在Controller的方法調用后執行,在視圖被渲染以前被調用,所以可以用來對ModelAndView對象進行操作
* 該方法只能在當前interceptor的preHandler方法的返回值是true時才會執行
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2, ModelAndView arg3) throws Exception {
}
/**
* 該方法在請求之前被調用
* 該方法返回為true時攔截器才會繼續往下執行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
//用于判斷用戶是否登錄
boolean flag=false;
User user=(User) request.getSession().getAttribute("user");
if(user==null){
request.setAttribute("message", "請先登錄");
request.getRequestDispatcher("loginPage.jsp").forward(request, response);
}else{
flag=true;
}
return flag;
}
}
<mvc:mapping path=""/>配置攔截路徑
<mvc:exclude-mapping path=""/>配置不進行攔截的路徑。
<!-- 配置攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 攔截路徑 -->
<mvc:mapping path="/*"/>
<!-- 不攔截的路徑 -->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/loginPage"/>
<bean class="com.dj.interceptor.UserInterceptor"></bean>
</mvc:interceptor>
<!-- 當設置多個攔截器時,先按**順序調用preHandle方法**,然后**逆序調用**每個攔截器的postHandle和afterCompletion方法 -->
</mvc:interceptors>
@Controller
public class UserController {
@RequestMapping(value="/{pagename}")
public String pageName(@PathVariable String pagename){
return pagename;
}
@RequestMapping("login")
public ModelAndView login(String username,String password
,ModelAndView mv,HttpSession session){
if(username!=null&&username.equals("aaa")&&password!=null&&password.equals("111")){
User user=new User();
user.setUsername(username);
user.setPassword(password);
session.setAttribute("user", user);
mv.setViewName("success");
}else{
mv.addObject("message", "賬號或密碼錯誤");
mv.setViewName("loginPage");
}
return mv;
}
@RequestMapping("success")
public String success(){
return "success";
}
}
<form action="login" method="post">
<!-- 提示信息 -->
<font color="red">${requestScope.message }</font><br>
用戶名:<input type="text" name="username" /><br>
密碼:<input type="password" name="password"/>
<input type="submit" value="登錄"/>
</form>
<body> 登陸成功! </body>
直接訪問success頁面被攔截
訪問登錄頁面,因為配置了不進行攔截的路徑,所以顯示如下
輸入賬號密碼登錄成功
對于@ControllerAdvice,我們比較熟知的用法是結合@ExceptionHandler用于全局異常的處理,但其作用不僅限于此。ControllerAdvice拆分開來就是Controller Advice,關于Advice,前面我們講解Spring Aop時講到,其是用于封裝一個切面所有屬性的,包括切入點和需要織入的切面邏輯。這里ContrllerAdvice也可以這么理解,其抽象級別應該是用于對Controller進行“切面”環繞的,而具體的業務織入方式則是通過結合其他的注解來實現的。@ControllerAdvice是在類上聲明的注解,其用法主要有三點:
在Spring MVC進行調用的過程中,會有很多的特殊的需求。比如全局異常,分頁信息和分頁搜索條件,請求時帶來返回時還得回顯頁面。
Spring提供@ControllerAdvice對需要處理的范圍進行配置。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
// 控制的掃描包范圍
@AliasFor("basePackages")
String[] value() default {};
// 控制的掃描包范圍
@AliasFor("value")
String[] basePackages() default {};
// 控制的包類
Class<?>[] basePackageClasses() default {};
// @Controller或者@RestController的類 的數據
Class<?>[] assignableTypes() default {};
// 控制范圍可以用注解進行配置
Class<? extends Annotation>[] annotations() default {};
}
從上面的講解可以看出,@ControllerAdvice的用法基本是將其聲明在某個bean上,然后在該bean的方法上使用其他的注解來指定不同的織入邏輯。不過這里@ControllerAdvice并不是使用AOP的方式來織入業務邏輯的,而是Spring內置對其各個邏輯的織入方式進行了內置支持。本文將對@ControllerAdvice的這三種使用方式分別進行講解。
系統比較龐大時很多的異常是不能控制,或者未知的,不能將所有的sql異常,反射異常,類不存在等拋到頁面上展示給用戶。
則需要一個全局的攔截器處理,Spring 提供了@ExceptionHandler處理方式。
1)、全局異常處理定義
@ControllerAdvice(basePackages="com.kevin.tool")
public class ExceptionHandlerController {
/**
* 錯誤后返回json
* 如果想跳轉到專門的異常界面,則可以返回{@link org.springframework.web.servlet.ModelAndView}
*
* @return 標準異常json
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public Map<String, String> handler() {
Map<String, String> errorMap=new HashMap<String, String>(16);
errorMap.put("code", "500");
errorMap.put("msg", "系統異常,請稍后重試");
return errorMap;
}
}
2)、控制器方法調用異常
@RestController
public class ControllerAdviceDemoController {
@ResponseBody
@RequestMapping("bindException")
public String bindException() {
getMessage();
return "ok";
}
private void getMessage() {
throw new RuntimeException("未知異常!");
}
}
3)、訪問效果
@InitBinder從字面意思可以看出這個的作用是給Binder做初始化的,@InitBinder主要用在@Controller中標注于方法上(@RestController也算),表示初始化當前控制器的數據綁定器(或者屬性綁定器),只對當前的Controller有效。@InitBinder標注的方法必須有一個參數WebDataBinder。所謂的屬性編輯器可以理解就是幫助我們完成參數綁定,然后是在請求到達controller要執行方法前執行!
數據綁定有很多的場景,當前比如前端傳入的日期為字符串類型,后端按照Format進解析為日期。
1)、全局日期綁定定義
@ControllerAdvice(basePackages="com.kevin.tool")
public class ExceptionHandlerController {
@InitBinder("date")
public void globalInitBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
}
2)、控制器方法調用日期轉換
@RestController
public class ControllerAdviceDemoController {
@ResponseBody
@RequestMapping(value="/initBind", method=RequestMethod.GET)
public String detail(@RequestParam("id") long id, Date date) {
System.out.println(date);
System.out.println(id);
return "ok";
}
}
3)、收到的日期類型效果
訪問地址為://127.0.0.1:9999/initBind?id=123&date=2019-12-30
先看看@ModelAttribute的注解信息,元注解@Target指定可以修飾方法參數和方法(全局)。當前模擬一種常見,就是將所有輸出的信息都加上當前的平臺信息(比如版本等公共信息,這種需求還是比較多的)。
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean binding() default true;
}
1)、全局返回屬性添加
@ControllerAdvice(basePackages="com.kevin.tool")
public class ExceptionHandlerController {
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("msg", "hello");
HashMap<String, String> map=new HashMap<>(16);
map.put("version", "1.0.0");
map.put("name", "XXX平臺");
model.addAttribute("platform", map);
}
}
2)、控制器方法訪問
@RestController
public class ControllerAdviceDemoController {
@GetMapping("/modelAttributeTest")
private String modelAttributeTest(@ModelAttribute("msg") String msg,
@ModelAttribute("platform") Map<String, String> platform) {
String result="msg:" + msg + "<br>" + "info:" + platform;
return result;
}
}
3)、輸出效果
在控制器方法中只需要實現頁面跳轉(只設置頁面視圖名稱)功能而沒有其他業務,此時可以在SpringMvc的配置文件中使用view-controller標簽表示控制器方法
在SpringMvC的核心配置文件中使用視圖控制器標簽跳轉到首頁
<?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.atguigu.mvc.controller"></context:component-scan>
<!--配置Thymeleaf視圖解析器-->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
</bean>
<!--視圖控制器,設置請求對應的視圖名稱實現頁面的跳轉-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--開啟MVC的注解驅動,保證視圖控制器設置的請求和控制器方法設置的請求全部都會被前端控制器處理-->
<mvc:annotation-driven />
</beans>
請求路徑path對應的視圖名稱是view-name,即請求路徑/對應的視圖名稱是index。 此時,可以不需要控制器方法。
通過@RequestMapping注解: 匹配路徑與處理器
@RequestMapping注解用于建立請求URL路徑和處理器之間的對應關系.
出現位置: 可以出現在類上,也可以出現在方法上.
當它既出現在類上也出現在方法上時,類上注解值為請求URL的一級目錄,方法上注解值為請求URL的二級目錄 當它只出現在方法上時,該注解值為請求URL的一級目錄 其屬性如下:
path: value屬性的別名,指定請求的URL,支持Ant風格表達式,通配符如下:
通配符 | 說明 |
? | 匹配文件(路徑)名中的一個字符 |
* | 匹配文件(路徑)名中的任意數量(包括0個)的字符 |
** | 匹配任意數量(包括0個)的路徑 |
例如
路徑/project/*.a匹配項目根路徑下所有在/project路徑下的.a文件
路徑/project/p?ttern匹配項目根路徑下的/project/pattern和/project/pXttern,但不能匹配/project/pttern
路徑/**/example匹配項目根路徑下的/project/example,/project/foo/example,和/example
路徑/project/**/dir/file.*匹配項目根路徑下的/project/dir/file.jsp,/project/foo/dir/file.html,/project/foo/bar/dir/file.pdf
路徑/**/*.jsp匹配項目根路徑下的所有jsp文件
另外,遵循最長匹配原則,若URL請求了/project/dir/file.jsp,現在存在兩個匹配模式:/**/*.jsp和/project/dir/*.jsp,那么會根據/project/dir/*.jsp來匹配.
@RequestMapping(params={"param1"}),表示請求參數中param1必須出現
@RequestMapping(params={"!param1"}),表示請求參數中param1不能出現
@RequestMapping(params={"param1=value1"}),表示請求參數中param1必須出現且為value1
@RequestMapping(params={"param1!value1"}),表示請求參數中param1必須出現且不為value1
多個值之間是與的關系
WebMvcConfigurerAdapter配置類是spring提供的一種配置方式,采用JavaBean的方式替代傳統的基于xml的配置來對spring框架進行自定義的配置。因此,在spring boot提倡的基于注解的配置,采用“約定大于配置”的風格下,當需要進行自定義的配置時,便可以繼承WebMvcConfigurerAdapter這個抽象類,通過JavaBean來實現需要的配置。
WebMvcConfigurerAdapter是一個抽象類,它只提供了一些空的接口讓用戶去重寫,比如如果想添加攔截器的時候,需要去重寫一下addInterceptors()這個方法,去配置自定義的攔截器。我們可以看一下WebMvcConfigurerAdapter提供了哪些接口來供我們使用。
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
/*配置路徑匹配參數*/
public void configurePathMatch(PathMatchConfigurer configurer) {}
/*配置Web Service或REST API設計中內容協商,即根據客戶端的支持內容格式情況來封裝響應消息體,如xml,json*/
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
/*配置路徑匹配參數*/
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
/* 使得springmvc在接口層支持異步*/
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
/* 注冊參數轉換和格式化器*/
public void addFormatters(FormatterRegistry registry) {}
/* 注冊配置的攔截器*/
public void addInterceptors(InterceptorRegistry registry) {}
/* 自定義靜態資源映射*/
public void addResourceHandlers(ResourceHandlerRegistry registry) {}
/* cors跨域訪問*/
public void addCorsMappings(CorsRegistry registry) {}
/* 配置頁面直接訪問,不走接口*/
public void addViewControllers(ViewControllerRegistry registry) {}
/* 注冊自定義的視圖解析器*/
public void configureViewResolvers(ViewResolverRegistry registry) {}
/* 注冊自定義控制器(controller)方法參數類型*/
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {}
/* 注冊自定義控制器(controller)方法返回類型*/
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {}
/* 重載會覆蓋掉spring mvc默認注冊的多個HttpMessageConverter*/
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
/* 僅添加一個自定義的HttpMessageConverter,不覆蓋默認注冊的HttpMessageConverter*/
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
/* 注冊異常處理*/
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
/* 多個異常處理,可以重寫次方法指定處理順序等*/
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
}
WebMvcConfigurerAdapter提供了很多的接口供用戶去實現自定義的配置項。下面挑幾個比較重要的介紹一下如何使用這些接口來自定義配置。
(1)注冊攔截器
首先,編寫攔截器的代碼:
public class LoginInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger=LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("-----------------------------");
logger.info(request.getRequestedSessionId());
logger.info("-----------------------------");
return true;
}
}
這里只打印相關信息,然后,需要寫一個config類去配置這個攔截器:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/*
* 攔截器配置*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
}
配置類繼承了WebMvcConfigurerAdapter這個類,并且重寫了addInterceptors這個方法,在方法中,注冊了上面編寫的攔截器,并且為此攔截器配置了攔截路徑,這樣一來就算是配置好了這個攔截器。
(2)配置CORS跨域
只需要在上面的webConfig里重寫WebMvcConfigurerAdapter的addCorsMappings方法就可以獲得基于spring的跨域支持。
/**
* 跨域CORS配置
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
super.addCorsMappings(registry);
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("POST","GET")
.allowedOrigins("http://...")
.allowCredentials(true);
}
(3)配置ViewController
當首頁或者登陸頁的頁面對外暴露,不需要加載任何的配置的時候,這些頁面將不通過接口層,而是直接訪問,這時,就需要配置ViewController指定請求路徑直接到頁面。
/**
* 視圖控制器配置
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
super.addViewControllers(registry);
registry.addViewController("/").setViewName("forward:/index.html");
}
(4)配置ViewResolver
通常在使用jsp的項目中,會基于spring mvc配置的文件去配置視圖解析器,通過重寫WebMvcConfigurerAdapter里的configureViewResolvers也可以將自己定義的InternalResourceViewResolver配置整合進spring中。
/**
* 配置請求視圖映射
*
* @return
*/
@Bean
public InternalResourceViewResolver resourceViewResolver() {
InternalResourceViewResolver internalResourceViewResolver=new InternalResourceViewResolver();
//請求視圖文件的前綴地址
internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
//請求視圖文件的后綴
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
super.configureViewResolvers(registry);
registry.viewResolver(resourceViewResolver());
}
可以看一下ViewResolverRegistry中的代碼:
public UrlBasedViewResolverRegistration jsp() {
return this.jsp("/WEB-INF/", ".jsp");
}
public UrlBasedViewResolverRegistration jsp(String prefix, String suffix) {
InternalResourceViewResolver resolver=new InternalResourceViewResolver();
resolver.setPrefix(prefix);
resolver.setSuffix(suffix);
this.viewResolvers.add(resolver);
return new UrlBasedViewResolverRegistration(resolver);
}
可以看到,即使不去配置,spring也會新建一個默認的視圖解析器。十分方便。
(5)配置Formatter
當請求的參數中帶有日期的參數的時候,可以在此配置formatter使得接收到日期參數格式統一。
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new Formatter<Date>() {
@Override
public Date parse(String date, Locale locale) {
return new Date(Long.parseLong(date));
}
@Override
public String print(Date date, Locale locale) {
return Long.valueOf(date.getTime()).toString();
}
});
}
WebMvcConfigurer配置類其實是Spring內部的一種配置方式,采用JavaBean的形式來代替傳統的xml配置文件形式進行針對框架個性化定制,可以自定義一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要創建一個配置類并實現WebMvcConfigurer 接口;
在Spring Boot 1.5版本都是靠重寫WebMvcConfigurerAdapter的方法來添加自定義攔截器,消息轉換器等。SpringBoot 2.0 后,該類被標記為@Deprecated(棄用)。官方推薦直接實現WebMvcConfigurer或者直接繼承WebMvcConfigurationSupport,方式一實現WebMvcConfigurer接口(推薦),方式二繼承WebMvcConfigurationSupport類,
Spring MVC 為文件上傳提供了直接支持,這種支持是通過即插即用的 MultipartResolver 實現的。Spring 使用 Jakarta Commons FileUpload 技術實現了一個 MultipartResolver 實現類:CommonsMultipartResolver。
在 Spring MVC 上下文中默認沒有裝配 MultipartResolver,因此默認情況下不能處理文件的上傳工作。如果想使用 Spring 的文件上傳功能,則需要先在上下文中配置 MultipartResolver。
下面使用 CommonsMultipartResolver 配置一個 MultipartResolver 解析器。
<!-- 文件上傳 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
p:defaultEncoding="UTF-8"//①請求的編碼格式,默認為ISO-8859-1
p:maxUploadSize="5000000"//②上傳文件的大小上限,單位為字節(5MB)
p:uploadTempDir="file://d:/temp"/>//③上傳文件的臨時路徑
defaultEncoding 必須和用戶 JSP 的 pageEncoding 屬性一致,以便正確讀取表單的內容。uploadTempDir 是文件上傳過程中所使用的臨時目錄,文件上傳完成后,臨時目錄中的臨時文件會被自動清除。
為了讓 CommonsMultipartResolver 正常工作,必須先將 Jakarta Commons FileUpload 及 Jakarta Commons io 的類包添加到類路徑下。
在 UserController 中添加一個用于處理用戶頭像上傳的方法,如下面代碼所示。
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value="/uploadPage")//①
public String updatePage() {
return "uploadPage";
}
@RequestMapping(value="/upload")
public String updateThumb(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) throws Exception{
//②上傳的文件自動綁定到MultipartFile中
if (!file.isEmpty()) {
file.transferTo(new File("d:/temp/"+file.getOriginalFilename()));
return "redirect:success.html";
}else{
return "redirect:fail.html";
}
}
}
Spring MVC 會將上傳文件綁定到 MultipartFile 對象中。MultipartFile 提供了獲取上傳文件內容、文件名等方法,通過其 transferTo() 方法還可將文件存儲到硬件中,具體說明如下。
負責上傳文件的表單和一般表單有一些區別,表單的編碼類型必須是 multipart/form-data 類型。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>請上傳用戶頭像</title>
</head>
<body>
<h1>
請選擇上傳的頭像文件
</h1>
<form method="post" action="<c:url value="/user/upload.html"/>" enctype="multipart/form-data">//指定表單內容類型,以便支持文件上傳
<input type="text" name="name" />
<input type="file" name="file" />//②上傳文件的組件名
<input type="submit" />
</form>
</body>
</html>
在Spring MVC中,HttpMessageConverter主要用于將HTTP請求的輸入內容轉換為指定的Java對象,以及將Java對象轉換為HTTP響應的輸出內容。這種靈活的消息轉換機制就是利用HttpMessageConverter來實現的。
Spring MVC提供了多個默認的HttpMessageConverter實現,包括處理JSON、XML、文本等格式的Converter。另外,我們也可以自定義HttpMessageConverter來處理其他格式的數據。
Spring MVC提供了兩個注解:@RequestBody和@ResponseBody,分別用于完成請求報文到對象和對象到響應報文的轉換。
然而,有時候默認的HttpMessageConverter無法滿足特定的需求,例如,當我們需要處理的數據格式沒有默認的Converter時,或者我們需要對現有的Converter進行擴展時,就需要自定義HttpMessageConverter。
自定義HttpMessageConverter可以讓我們更加靈活地控制數據轉換的過程,例如我們可以自定義轉換規則、異常處理等。
接下來我們通過一個實例講解如何自定義HttpMessageConverter。
需求
接口請求數據格式:
xxx|yyy|zzz|...
接口返回JSON數據格式
{
"xxx": xxx,
"yyy": yyy,
"zzz": zzz,
...
}
其實就上面的數據格式,我們完全可以不用自定義HttpMessageConverter也是完全可以實現的。我們這里主要就是教大家如何在特殊的需求下實現特定的數據轉換處理。
(1)自定義HttpMessageConverter轉換器
public class PackHttpMessageConverter implements HttpMessageConverter<Object> {
// 設置自定義的Content-Type類型,這樣就限定了只有請求的內容類型是該類型才會使用該轉換器進行處理
private static final MediaType PACK=new MediaType("application", "pack", StandardCharsets.UTF_8) ;
// 判斷當前轉換器是否能夠讀取數據
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return PACK.equals(mediaType) ;
}
// 判斷當前轉換器是否可以將結果數據進行輸出到客戶端
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return true ;
}
// 返回當前轉換器只支持application/pack類型的數據格式
@Override
public List<MediaType> getSupportedMediaTypes() {
return Arrays.asList(PACK) ;
}
// 從請求中讀取數據
@Override
public Object read(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
InputStream is=inputMessage.getBody() ;
String res=IOUtils.toString(is, StandardCharsets.UTF_8) ;
// 這里簡單處理只針對Users類型的對象處理
if (clazz==Users.class) {
try {
// 創建實例
Users target=(Users) clazz.newInstance() ;
String[] s=res.split("\\|");
target.setId(Long.valueOf(s[0])) ;
target.setName(s[1]) ;
target.setAge(Integer.valueOf(s[2])) ;
target.setIdNo(s[3]) ;
return target ;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace() ;
}
}
return null ;
}
// 將Controller方法返回值寫到客戶端
@Override
public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// 設置響應頭為json格式
outputMessage.getHeaders().add("Content-Type", "application/json;charset=UTF-8") ;
ObjectMapper mapper=new ObjectMapper() ;
OutputStream os=outputMessage.getBody();
// 輸出結果內容
os.write(mapper.writeValueAsString(t).getBytes(StandardCharsets.UTF_8)) ;
os.flush();
}
}
(2)將PackHttpMessageConverter注冊到容器中
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new PackHttpMessageConverter()) ;
}
}
到這里自定義HttpMessageConverter及注冊到容器中就全部完成了,開發還是比較簡單,接下來做測試
(3)接口
// 方法非常簡單還是用的那些常用的類,@RequestBody接收請求body中的內容
@PostMapping("/i")
public Object i(@RequestBody Users user) {
System.out.println(handlerAdapter) ;
return user ;
}
(4)通過Postman測試接口
設置請求的header
似乎沒有任何的問題,其實你只要在寫的方法中打印下日志,或者調試下,你會發現你的write方法根本就沒有被調用,也就是說寫數據并沒有使用到我們自定義的實現,這是因為有優先級比我們自定義的轉換器高,所以要想讓寫消息也調用自定義的。我們需要如下修改注冊方式:
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new PackHttpMessageConverter()) ;
}
這樣我們自定義的轉換器就排到了第一的位置,這樣就會調用我們自定義的write方法。
請求參數由于添加了@RequestBody,所以方法的參數解析器使用的是RequestResponseBodyMethodProcessor。
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// ...
// 讀取請求數據;調用父類方法
Object arg=readWithMessageConverters(inputMessage, parameter, paramType);
// ...
}
}
AbstractMessageConverterMethodArgumentResolver
public abstract class AbstractMessageConverterMethodArgumentResolver {
protected <T> Object readWithMessageConverters(...) {
// ...
// 遍歷所有的消息轉換器
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType=(Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter=(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
// 判斷當前轉換器是否讀,也就上面我們自定義中實現的canRead方法
if (genericConverter !=null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass !=null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse=getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// 讀取具體的數據內容
body=(genericConverter !=null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body=getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body=getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
}
原理也比較的簡單。
自定義HttpMessageConverter是Spring MVC中一個強大的工具,它可以幫助開發者更加靈活地控制數據轉換的過程,滿足特定的需求。
MappingJackson2HttpMessageConverter是springboot中默認的Json消息轉換器。這個類的繼承圖如下:
這個類的主要實現邏輯是在AbstractJackson2HttpMessageConverter抽象類中實現的。這個列實現序列化與反序列化的最核心組件是ObjectMapper這個類。
MappingJackson2HttpMessageConverter是一個Spring消息轉換器,用于在Web應用程序中處理請求和響應內容。它的工作原理如下:
MappingJackson2HttpMessageConverter通過實現HttpMessageConverter接口并重寫相關方法,完成請求和響應內容的轉換。當Spring處理請求或生成響應時,它會自動選擇合適的消息轉換器,并使用它來處理請求和響應內容。
MappingJackson2HttpMessageConverter如何將請求內容轉換為Java對象和響應內容轉換為JSON
(1)簡單使用:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
}
@Bean
public ObjectMapper objectMapper() {
return new Jackson2ObjectMapperBuilder()
.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
.featuresToEnable(SerializationFeature.INDENT_OUTPUT)
.build();
}
}
這樣,在控制器方法中使用@RequestBody或@ResponseBody注解時,就可以通過MappingJackson2HttpMessageConverter進行序列化/反序列化操作了。
(2)自定義MappingJackson2HttpMessageConverter
將時間戳序列化為LocalDateTime,將LocalDateTime反序列化為時間戳
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
ObjectMapper mapper=new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 時間對象自定義格式化
JavaTimeModule javaTimeModule=new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
long timestamp=Long.parseLong(jsonParser.getText());
Instant instant=Instant.ofEpochMilli(timestamp);
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}
});
javaTimeModule.addSerializer(LocalDateTime.class, new JsonSerializer<LocalDateTime>() {
@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumber(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
}
});
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
// Long轉換為String傳輸
javaTimeModule.addSerializer(Long.class, ToStringSerializer.instance);
mapper.registerModule(javaTimeModule);
converter.setObjectMapper(mapper);
return converter;
}
如果不生效:
StringHttpMessageConverter是Spring MVC中用于讀寫HTTP消息的字符串轉換器。它可以將請求的輸入流轉換為字符串,同樣也可以將字符串寫入HTTP響應中
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public StringHttpMessageConverter stringHttpMessageConverter() {
StringHttpMessageConverter converter=new StringHttpMessageConverter();
converter.setWriteAcceptCharset(false); // 設置是否在寫入響應時發送AcceptedCharset
return converter;
}
}
SSE技術是基于單工通信模式,只是單純的客戶端向服務端發送請求,服務端不會主動發送給客戶端。服務端采取的策略是抓住這個請求不放,等數據更新的時候才返回給客戶端,當客戶端接收到消息后,再向服務端發送請求,周而復始。
注意:因為EventSource對象是SSE的客戶端,可能會有瀏覽器對其不支持,但谷歌、火狐、360是可以的,IE不可以。
另外WebSocket技術是雙工模式。
服務端代碼如下:
//本文使用的是Spring4.x,無需其他類庫,;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
@RequestMapping(value="/", method=RequestMethod.GET)
public String home(Locale locale, Model model) {
return "sse";
}
@RequestMapping(value="push",produces="text/event-stream")
public @ResponseBody String push(){
System.out.println("push msg..");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//注意:返回數據的格式要嚴格按照這樣寫,‘\n\n’不可少
return "data:current time: "+new SimpleDateFormat("YYYY-MM-dd hh:mm:ss").format(new Date())+"\n\n";
}
}
客戶端代碼如下,sse.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>SSE方式消息推送</title>
</head>
<body>
<div id="msgFromPush"></div>
<!--這里的jquery僅僅用于數據的展示,不影響消息推送-->
<script type="text/javascript" src="<c:url value='resources/jquery-1.10.2.js'/>"></script>
<script type="text/javascript">
if(!!window.EventSource){
var source=new EventSource('push');
s='';
source.addEventListener('message',function(e){
console.log("get message"+e.data);
s+=e.data+"<br/>";
$("#msgFromPush").html(s);
});
source.addEventListener('open',function(e){
console.log("connect is open");
},false);
source.addEventListener('error',function(e){
if(e.readyState==EventSource.CLOSE){
console.log("connect is close");
}else{
console.log(e.readyState);
}
},false);
}else{
console.log("web is not support");
}
</script>
</body>
</html>
運行結果:
Servlet3.0+ 異步處理方法通過設置動態Servlet(即Dynamic)支持異步處理,在客戶端(瀏覽器)以ajax形式不斷發送請求,從而獲得信息。
(1)動態Servlet支持異步處理
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import com.config.MvcConfig;
/** Use class that implements WebApplicationInitializer interface to substitute web.xml
* @author apple
*
*/
public class WebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext applicationContext= new AnnotationConfigWebApplicationContext();
applicationContext.register(MvcConfig.class); // process annotated class MvcConfig
applicationContext.setServletContext(servletContext); // make applicationContext and servletContext related
applicationContext.refresh();
Dynamic servlet=servletContext.addServlet("dispatcher", new DispatcherServlet(applicationContext));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
servlet.setAsyncSupported(true);
}
}
(2)Service Bean
該類僅僅是業務邏輯,與異步實現關系無關。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;
/** 用于異步Servlet 3.0+的服務器端推送測試,
* 為Controller提供一個異步的定時更新的DeferredResult<String>
* @author apple
*
*/
@Service
public class PushService {
private DeferredResult<String> deferredResult ;
public DeferredResult<String> getAyncUpdateDeferredResult() {
this.deferredResult=new DeferredResult<>();
return deferredResult;
}
/**以下說明通過查看@Scheduled注解獲得。
* 由于@Scheduled注解的處理是通過注冊一個ScheduledAnnotationBeanPostProcessor完成的,
* 而后者是對方法被@Scheduled注解了的Bean,按照該注解的要求,
* 通過調用一個TaskScheduler進行post process。
* 因此對于實例化的Bean,必須完成@Scheduled注解的方法后才能被調用。
*/
@Scheduled(fixedDelay=5000)
public void refresh() {
if (this.deferredResult !=null) {
this.deferredResult.setResult(String.valueOf(System.currentTimeMillis()));
}
}
}
(3) Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import com.service.PushService;
@Controller
public class PushController {
@Autowired
PushService pushService;
@RequestMapping(value="/defer")
@ResponseBody
public DeferredResult<String> deferredCall() {
// 通過Service Bean獲取異步更新的DeferredReuslt<String>
return pushService.getAyncUpdateDeferredResult();
}
}
(4) 測試頁面(jsp)
測試頁面采用ajax不斷發送請求,這些請求構成了并發請求。由于前面Servlet支持異步處理,請求到達服務端后,直到Service Bean實例化,并按照@Scheduled要求延時至設定的時間(例中5000ms)進行了設置,才通過Controller中以@RequestMapping注解的方法發送response到瀏覽器。 測試結果: 瀏覽器頁面顯示(數據為:System.currentTimeMillis()) 1528273203260 1528273208265 1528273213271 1528273218278 1528273223282 1528273228285 1528273233290 1528273238296 1528273243298 1528273248302
由結果可知,請求確實是并發的,Servlet 3.0+對請求進行了異步處理。
1、什么是 mock 測試
在測試過程中,對于某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法,就是 mock 測試在測試過程中,對于某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法,就是mock測試。
2、為什么使用 mock 測試
3、MockMVC 介紹
基于 RESTful 風格的 SpringMVC 的測試,我們可以測試完整的 Spring MVC 流程,即從 URL請求到控制器處理,再到視圖渲染都可以測試。
1)MockMvcBuilder
MockMvcBuilder 是用來構造 MockMvc 的構造器,其主要有兩個實現:StandaloneMockMvcBuilder 和 DefaultMockMvcBuilder,對于我們來說直接使用靜態工廠 MockMvcBuilders 創建即可。 MockMvcBuilder 是用來構造 MockMvc 的構造器,其主要有兩個實現:StandaloneMockMvcBuilder 和 DefaultMockMvcBuilder,對于我們來說直接使用靜態工廠 MockMvcBuilders 創建即可。
2)MockMvcBuilders
負責創建 MockMvcBuilder 對象,有兩種創建方式:
standaloneSetup(Object... controllers):通過參數指定一組控制器,這樣就不需要從上下文獲取了。
webAppContextSetup(WebApplicationContext wac):指定 WebApplicationContext,將會從該上下文獲取相應的控制器并得到相應的 MockMvc,本章節下面測試用例均使用這種方式創建 MockMvcBuilder 對象。
3)MockMvc
對于服務器端的 SpringMVC 測試支持主入口點。通過 MockMvcBuilder 構造MockMvcBuilder 由 MockMvcBuilders 建造者的靜態方法去建造。
核心方法:perform(RequestBuilder rb) -- 執行一個 RequestBuilder 請求,會自動執行SpringMVC 的流程并映射到相應的控制器執行處理,該方法的返回值是一個 ResultActions。
4)ResultActions
(1)andExpect:添加 ResultMatcher 驗證規則,驗證控制器執行完成后結果是否正確;
(2)andDo:添加 ResultHandler 結果處理器,比如調試時打印結果到控制臺;
(3)andReturn:最后返回相應的 MvcResult;然后進行自定義驗證/進行下一步的異步處理;
5)MockMvcRequestBuilders
用來構建請求的,其主要有兩個子類 MockHttpServletRequestBuilder 和MockMultipartHttpServletRequestBuilder(如文件上傳使用),即用來 Mock 客戶端請求需要的所有數據。
6)MockMvcResultMatchers
(1)用來匹配執行完請求后的結果驗證
(2)如果匹配失敗將拋出相應的異常
(3)包含了很多驗證 API 方法
7)MockMvcResultHandlers
(1)結果處理器,表示要對結果做點什么事情
(2)比如此處使用 MockMvcResultHandlers.print() 輸出整個響應結果信息
8)MvcResult
(1)單元測試執行結果,可以針對執行結果進行自定義驗證邏輯。
1、添加依賴
<!-- spring 單元測試組件包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!-- 單元測試Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- Mock測試使用的json-path依賴 -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.2.0</version>
</dependency>
前兩個 jar 依賴我們都已經接觸過了,對于返回視圖方法的測試這兩個 jar 依賴已經足夠了,第三個 jar 依賴是用于處理返回 Json 數據方法的,這里要明白每個 jar 的具體作用。
2、被測試的方法
@RequestMapping(value="editItem")
public String editItem(Integer id, Model model) {
Item item=itemService.getItemById(id);
model.addAttribute("item", item);
return "itemEdit";
}
@RequestMapping(value="getItem")
@ResponseBody
public Item getItem(Integer id) {
Item item=itemService.getItemById(id);
return item;
}
這里我們提供了兩個方法,一個是返回視圖的方法,另一個是返回 Json 數據的方法,下面我們會給出測試類,分別對這兩個方法進行測試。
3、測試類:ItemMockTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/*.xml")
@WebAppConfiguration
public class ItemMockTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void init() {
mockMvc=MockMvcBuilders.webAppContextSetup(context).build();
}
}
這里前兩個注解就不再解釋了,我們在學習 Spring 與 Junit 整合的時候已經講解過了,這里說一下第三個注解: @WebAppConfiguration:可以在單元測試的時候,不用啟動 Servlet 容器,就可以獲取一個 Web 應用上下文。
1)返回視圖方法測試
@Test
public void test() throws Exception {
MvcResult result=mockMvc.perform(MockMvcRequestBuilders.get("/editItem").param("id", "1"))
.andExpect(MockMvcResultMatchers.view().name("itemEdit"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
Assert.assertNotNull(result.getModelAndView().getModel().get("item"));
}
這三句代碼是我們對結果的期望,最后打印出了結果,說明執行成功,所有期望都達到了,否則會直接報錯。從結果中我們就可以看到這個請求測試的情況。
2、返回 Json 數據方法
@Test
public void test1() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/getItem")
.param("id", "1")
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("IPhone X"))
.andDo(MockMvcResultHandlers.print())
.andReturn();
}
在這個方法中比較特殊的就是設置 MediaType 類型,因為都是使用 Json 格式,所以設置了 MediaType.APPLICATION_JSON,jsonPath 用于比對期望的數據是否與返回的結果一致,這里需要注意的是 "$.id" 這 key 的種形式。
隨著異步 I/O 和 Netty 等框架的流行,響應式編程逐漸走入大眾的視野。但是,響應式編程本身并不是太新的概念,這個術語最早出現在 1985 年 David Harel 和 Amir Pnueli 的論文“響應式系統的開發”之中,他們對復雜計算機系統的特征進行了歸納,提出了一種新穎的二分方式:轉換式(Transformative)與響應式(Reactive)系統。轉換式系統接收已知的一組輸入,轉換這些輸入并產生輸出,而響應式系統則會持續受到外部環境的刺激,它們的角色就是持續響應刺激。在構建響應式 Web 服務上,Spring 5 中引入了全新的編程框架,那就是 Spring WebFlux。作為一款新型的 Web 服務開發框架,它與傳統的 WebMVC 相比具體有哪些優勢呢?
介紹
Spring WebFlux 作為一個響應式 (reactive-stack) web 框架補充,在 5.0 的版本開始加入到 Spring 全家桶。這是一個完全非阻塞的,支持 Reactive Streams, 運行在諸如 Netty, Undertow, 以及 Servlet 3.1+ 容器上的,Spring WebFlux 構建在 Reactor 框架之上,提供了基于注解和函數式兩種方式來配置和運行。Spring WebFlux 可以讓你使用更少的線程去處理并發請求,同時能夠讓你使用更少的硬件資源來拓展你的應用。WebFlux 使用Netty作為默認的web服務器,其依賴于非阻塞IO,并且每次寫入都不需要額外的線程進行支持。也可以使用Tomcat、Jetty容器,不同與SpringMVC依賴于Servlet阻塞IO,并允許應用程序在需要時直接使用Servlet API,WebFlux依賴于Servlet 3.1非阻塞IO。使用Undertow作為服務器時,WebFlux直接使用Undertow API而不使用Servlet API。
特點
場景
WebFlux 用于構建響應式 Web 服務。微服務架構的興起為 WebFlux 的應用提供了一個很好的場景。我們知道在一個微服務系統中,存在數十乃至數百個獨立的微服務,它們相互通信以完成復雜的業務流程。這個過程勢必會涉及大量的 I/O 操作,尤其是阻塞式 I/O 操作會整體增加系統的延遲并降低吞吐量。如果能夠在復雜的流程中集成非阻塞、異步通信機制,我們就可以高效處理跨服務之間的網絡請求。針對這種場景,WebFlux 是一種非常有效的解決方案。控制層一旦使用 Spring WebFlux,它下面的安全認證層、數據訪問層都必須使用 Reactive API。其次,Spring Data Reactive Repositories 目前只支持 MongoDB、Redis 和 Couchbase 等幾種不支持事務管理的 NOSQL。技術選型時一定要權衡這些弊端和風險。
響應式編程
響應式編程是一種面向數據流和變化傳播的編程范式,這意味著可以在編程語言中很方便地表達靜態或動態的數據流,而相關的計算模型會自動將變化的值通過數據流進行傳播,電子表格程序就是響應式編程的。
響應式應用應該具備如下的四個特點:
并發模型
WebFlux模型主要依賴響應式編程庫Reactor,Reactor 有兩種模型,Flux 和 Mono,提供了非阻塞、支持回壓機制的異步流處理能力。WebFlux API接收普通Publisher作為輸入,在內部使其適配Reactor類型,使用它并返回Flux或Mono作為輸出。
介紹
SpringMvc是一種基于java的實現Mvc設計模式的請求驅動類型的輕量級web框架,屬于SpringFrameWork的后續產品,已經融合在Spring Web Flow中,SpringMvc已經成為目前最主流的MVC框架之一,并且隨著Spring3.0的發布,全面超越Struts2,成為最優秀的mvc框架,他通過一套注解,讓一個簡單的java類成為處理請求的控制器,他無需實現任何接口,同時他還支持RESTful編程風格的請求。
特點
MVC
Spring web MVC框架提供了MVC(模型 - 視圖 - 控制器)架構和用于開發靈活和松散耦合的Web應用程序的組件。 MVC模式導致應用程序的不同方面(輸入邏輯,業務邏輯和UI邏輯)分離,同時提供這些元素之間的松散耦合。
并發模型
servlet由servlet container進行生命周期管理。container啟動時構造servlet對象并調用servlet init()進行初始化;container關閉時調用servlet destory()銷毀servlet;container運行時接受請求,并為每個請求分配一個線程(一般從線程池中獲取空閑線程)然后調用service()。
處理請求的時候同步操作,一個請求對應一個線程來處理,并發上升,線程數量就會上漲(上線文切換,內存消耗大)影響請求的處理時間?,F代系統多數都是IO密集的,同步處理讓線程大部分時間都浪費在了IO等待上面。雖然Servlet3.0后提供了異步請求處理與非阻塞IO支持,但是使用它會遠離Servlet API的其余部分,比如其規范是同步的(Filter, Servlet)或阻塞的(getParameter,getPart),而且其對響應的寫入仍然是阻塞的。
Spring WebFlux 不是 Spring MVC 的替代方案,Spring WebFlux 是 Spring Framework 5.0中引入的新的響應式web框架。與Spring MVC不同,它不需要Servlet API,是完全異步且非阻塞的,并且通過Reactor項目實現了Reactive Streams規范。Spring MVC依然構建在 Servlet API 以及 Servlet 容器之上;Spring Security 為兩種不同的技術棧提供了安全性的支持,Spring Data 分別為兩種不同的技術棧實現了 Repository;在數據訪問方面,響應式 Repository 已經涵蓋了 Mongo、Cassandra、Redis 以及 Couchbase。但是在關系型數據庫方面,因為 JBDC 規范本身就是阻塞式的,所以進展并不明顯。但是,像 PostgreSQL 和 MySQL 已經有了異步驅動。異步非阻塞并不會使程序運行得更快。WebFlux 并不能使接口的響應時間縮短,它僅僅能夠提升吞吐量和伸縮性。Spring WebFlux 是一個異步非阻塞的 Web 框架,所以,它特別適合應用在 IO 密集型的服務中,比如微服務網關這樣的應用中。
言
做 Java Web 開發的你,一定聽說過SpringMVC的大名,作為現在運用最廣泛的Java框架,它到目前為止依然保持著強大的活力和廣泛的用戶群。
本文介紹如何用eclipse一步一步搭建SpringMVC的最小系統,所謂最小系統,就是足以使項目在SpringMVC框架下成功跑起來,并且能夠做一些簡單的事情(比如訪問頁面)的系統。
話不多說,讓我們開始吧。所有的源代碼和jar包都會在最后給出。
其他環境:
操作系統:Windos 10
Tomcat : v7.0
JDK : 1.7
正文
1. 新建一個項目
Paste_Image.png
我們用eclipse新建項目,選擇Dynamic Web Project(動態的Web項目)。
點擊Next
Paste_Image.png
Project name里面寫上 springmvc,這就是我們項目的名稱,其他不用改,直接點擊Finish 。
Paste_Image.png
OK,項目就建好了。
接下來一定要將項目的字符集改為UTF-8
右鍵項目——properties
Paste_Image.png
改為UTF-8,點擊OK。
2. 編寫 web.xml
當我們打開WebContent/WEB-INF目錄的時候,發現里面只有一個lib目錄,這是存放各種jar包的地方。我們知道一個web項目必須要有一個web.xml文件才行。
既然沒有,我們自己寫一個咯。
右鍵WEB-INF——new——file,新建一個web.xml文件。
點擊Finish
將以下內容填進去即可。
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
這樣就完成了基本的配置,我的意思是說,現在這個項目就已經是一個標準的web項目了。
3. 驗證web項目是否搭建成功
為了驗證到目前為止的正確性,我們在WebContent目錄下面新建一個jsp文件。
名字就叫index.jsp
Paste_Image.png
內容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><html>
我們現在就將這個項目部署到Tomcat,來驗證是否可以跑起來。
在項目上右鍵——Debug As——Debug on Server
直接點擊Finish
經過一段時間,控制臺開始打印日志信息,當我們看到這些信息的時候,說明Tomcat已經啟動完畢了。
Paste_Image.png
讓我們打開瀏覽器,在地址欄輸入以下信息
http://localhost:8088/springmvc/index.jsp
我電腦上Tomcat配置的端口號是8088,具體情況視你自己的Tomcat決定,可能是8080等。
Paste_Image.png
可見,能夠成功訪問頁面了,這說明我們到目前為止的操作是正確的。
3. 集成SpringMVC
我們在web.xml文件里面添加下面的配置
3.1 配置監聽器
<listener>
3.2 配置過濾器,解決POST亂碼問題
<filter>
3.3 配置SpringMVC分發器,攔截所有請求
<servlet>
在這個配置中,我們規定了 DispatcherServlet 的關聯 XML 文件名稱叫做 dispatcher-servlet。
注意,這里的路徑是相對于web.xml來說的,也就是說,這個文件也在WEB-INF的根目錄下。
所以,我們需要在WEB-INF的根目錄下新建一個dispatcher-servlet.xml文件。
Paste_Image.png
至此,web.xml文件的編寫就告一段落了。
3.4 編寫dispatcher-servlet.xml
dispatcher-servlet.xml 的作用就是配置SpringMVC分發器。
配置如下:
<?xml version="1.0" encoding="UTF-8" ?><beans xmlns="http://www.springframework.org/schema/beans"
根據配置,有三個需要注意的地方。
它會掃描 com.springmvc 包下所有的Java類,但凡是遇到有注解的,比如@Controller , @Service , @Autowired ,就會將它們加入到Spring的bean工廠里面去。
所有的靜態資源文件,比如說 js , css , images 都需要放在/resources目錄下,這個目錄現在我們還沒有建。
所有的展示頁面,比如jsp文件,都需要放置在/WEB-INF/pages目錄下,這個目錄現在我們也沒有建。
OK,我們把對應的目錄加上。
首先是Java文件的目錄。
Paste_Image.png
我們在這個地方右鍵,新建一個 com 包,再在里面建一個 springmvc 包,或者用 . 的方式一起建。
Paste_Image.png
點擊Finish
Paste_Image.png
根據SpringMVC的分層,我們在springmvc 包下面建三個包,分別是controller , service , dao
Paste_Image.png
這樣的話, 當我們項目一旦啟動,springmvc就會掃描這三個包,將里面但凡是有注解的類都提取起來,放進Spring容器(或者說Spring的bean工廠),借由Spring容器來統一管理。這也就是你從來沒有去new一個Controller的原因。
接下來,我們來建靜態資源的目錄。
在WebContent目錄下新建一個resources文件夾。
然后順便把js,css,img的文件夾都建一下,這里就存放我們的靜態資源文件。
Paste_Image.png
最后,我們在WEB-INF目錄下建一個pages文件夾,作為展示頁面的存放目錄。
Paste_Image.png
將之前的index.jsp拷貝進來。
Paste_Image.png
這樣就配置的差不多了。
5. 導包和驗證
我們將jar包放到lib目錄:
Paste_Image.png
然后啟動項目,驗證一下到目前為止的構建是否正確。
打開Servers視圖,點擊如圖像是甲蟲一樣的圖標。
Paste_Image.png
發現報錯了,錯誤信息如下:
Paste_Image.png
錯誤:
Could not open ServletContext resource [/WEB-INF/applicationContext.xml]
它說我們在WEB-INF下面少了一個applicationContext.xml 這個文件,原來,我們少了對SpringBean工廠的配置,它的意思就是說,我們要規定一下,在Spring容器啟動的時候,需要自動加載哪些東西?
于是,我們把 applicationContext.xml 加上。
Paste_Image.png
<?xml version="1.0" encoding="UTF-8"?>
里面我們啥也不配置,再次啟動Tomcat。
Paste_Image.png
這回不報錯了。
5. 配置ViewController
我們知道,WEB-INF目錄下的任何資源都是無法直接通過瀏覽器的url地址去訪問的,保證了安全性。這也是我們為什么把頁面都放在該目錄下的原因。
為了有所區分,我們還單獨建立了一個pages文件夾,將這些頁面保存起來。
Paste_Image.png
現在,為了訪問這個頁面,我們需要用到SpringMVC的頁面跳轉機制。
我們在Controller包下新建一個ViewController
Paste_Image.png
點擊Finish
Paste_Image.png
ViewController 代碼:
package com.springmvc.controller;import javax.servlet.http.HttpServletRequest;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.servlet.ModelAndView;@Controllerpublic class ViewController { @RequestMapping("/view")
我只需要將想要訪問的頁面放在path里面,通過url傳進來就行了。
因為添加了java類,因此我們重新啟動Tomcat。
啟動完成后,在地址欄輸入:
http://localhost:8088/springmvc/view?path=index
結果:
Paste_Image.png
沒關系,我們看他報什么錯。
message /springmvc/WEB-INF/pagesindex.jsp
pagesindex.jsp是什么鬼??
原來,在dispatcher-servlet.xml中,我們少寫了一個 "/"
Paste_Image.png
添上去就行了。
Paste_Image.png
保存后,因為修改了XML配置文件,因此我們還是需要重新啟動Tomcat。
啟動完成后,繼續!
Paste_Image.png
成功了。
6. 引入靜態資源
比如,我在resources/img目錄下放了一張圖片,怎么引入到index.jsp呢?
Paste_Image.png
background : url(http://localhost:8088/springmvc/resources/img/bg.jpg);background-size : 100% 100%;
的確,這是一種方式。可是,它有一個缺點就是根路徑寫死了,我們肯定不希望這樣的。
其實,我們可以在viewController里面拿到項目根路徑,然后傳遞到jsp頁面就OK了。
Paste_Image.png
我們把調試信息 “恭喜,web項目已經成功搭建!” 刪掉。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><html>
${contextPath} 可以取到Controller傳過來的contextPath值。
成功了!
大家可以點擊加入群:606187239【JAVA大牛學習交流】
里面有Java高級大牛直播講解知識點 走的就是高端路線
(如果你想跳槽換工作 但是技術又不夠 或者工作上遇到了
瓶頸 我這里有一個JAVA的免費直播課程 講的是高端的知識點
基礎不好的誤入喲 只要你有1-5年的開發經驗
可以加群找我要課堂鏈接 注意:是免費的 沒有開發經驗誤入哦)
1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,
需要突破技術瓶頸的可以加。2、在公司待久了,過得很安逸,
但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。
3、如果沒有工作經驗,但基礎非常扎實,對java工作機制,
常用設計思想,常用java開發框架掌握熟練的,可以加。
4、覺得自己很牛B,一般需求都能搞定。
但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。
5. 群號:高級架構群 606187239備注好信息!
6.阿里Java高級大牛直播講解知識點,分享知識,
多年工作經驗的梳理和總結,帶著大家全面、
科學地建立自己的技術體系和技術認知!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。