spring boot 學習筆記 (3) Spring Boot 對web的支援
JSON 的支援
JSON (JavaScript Object Notation) 是一種輕量級的資料交換格式,易於閱讀和編寫,同時也易於機器解析和生成。JSON 採用完全獨立於語言的文字格式,但是也使用了類似於 C 語言家族的習慣(包括 C、C++、C#、Java、JavaScript、Perl、Python 等),這些特性使 JSON 成為理想的資料交換語言。
早期人們習慣使用 XML 進行資訊互動,後來 JSON 的使用更加簡單,到了現在資訊互動大部分都以 JSON 為主。早期在 Spring 體系中使用 JSON 還比較複雜,需要配置多項 XML 和註解,現在在 Spring Boot 體系中,對 JSON 支援簡單而又完善,在 Web 層使用僅僅只需要一個註解即可完成。接下來使用案例說明。
新建一個 spring-boot-web 專案,在 pom.xml 中新增 Web 依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
啟動類:
@SpringBootApplication public class WebApplication { public static void main(String[] args) { SpringApplication.run(WebApplication.class, args); } }
在專案根路徑下新建 model 包,在包下新建一個實體類 User,User 資訊如下:
public class User {
private String name;
private int age;
private String pass;
//setter、getter省略
}
在專案中新建 Web 包,並在 Web 包下新建一個類 WebController,在類中建立一個方法返回 User,如下:
@RestController public class WebController { @RequestMapping(name="/getUser", method= RequestMethod.POST) public User getUser() { User user=new User(); user.setName("小明"); user.setAge(12); user.setPass("123456"); return user; } }
- @RestController 註解相當於 @ResponseBody + @Controller 合在一起的作用,如果 Web 層的類上使用了 @RestController 註解,就代表這個類中所有的方法都會以 JSON 的形式返回結果,也相當於 JSON 的一種快捷使用方式;
- @RequestMapping(name="/getUser", method= RequestMethod.POST),以 /getUser 的方式去請求,method= RequestMethod.POST 是指只可以使用 Post 的方式去請求,如果使用 Get 的方式去請求的話,則會報 405 不允許訪問的錯誤。
在 test 包下新建 WebControllerTest 測試類,對 getUser() 方法進行測試。
@SpringBootTest
public class WebControllerTest {
//省略部分程式碼
@Test
public void getUser() throws Exception {
String responseString = mockMvc.perform(MockMvcRequestBuilders.post("/getUser"))
.andReturn().getResponse().getContentAsString();
System.out.println("result : "+responseString);
}
}
這裡的測試程式碼和上面的稍有區別,“.andReturn().getResponse().getContentAsString(); ”的意思是獲取請求的返回資訊,並將返回資訊轉換為字串,最後將請求的響應結果打印出來。
執行完 Test,返回結果如下:
result : {"name":"小明","age":12,"pass":"123456"}
說明 Spring Boot 自動將 User 物件轉成了 JSON 進行返回。那麼如果返回的是一個 list 呢,在 WebController 新增方法 getUsers(),程式碼如下:
@RequestMapping("/getUsers")
public List<User> getUsers() {
List<User> users=new ArrayList<User>();
User user1=new User();
user1.setName("neo");
user1.setAge(30);
user1.setPass("neo123");
users.add(user1);
User user2=new User();
user2.setName("小明");
user2.setAge(12);
user2.setPass("123456");
users.add(user2);
return users;
}
新增測試方法:
@Test
public void getUsers() throws Exception {
String responseString = mockMvc.perform(MockMvcRequestBuilders.get("/getUsers"))
.andReturn().getResponse().getContentAsString();
System.out.println("result : "+responseString);
}
執行測試方法,返回內容如下:
result : [{"name":"neo","age":30,"pass":"neo123"},{"name":"小明","age":12,"pass":"123456"}]
說明不管是物件還是集合或者物件巢狀,Spring Boot 均可以將其轉換為 JSON 字串,這種方式特別適合系統提供介面時使用。
請求傳參
請求傳參是 Web 開發最基礎的內容,前端瀏覽器和後端伺服器正是依賴互動過程中的引數,來判斷使用者是否登入、使用者正在執行時哪些動作,因此引數的傳遞和接收是一個 Web 系統最基礎的功能。Spring Boot 內建了很多種引數接收方式,提供一些註解來幫助限制請求的型別、接收不同格式的引數等,接下來我們舉例介紹。
使用 Spring Boot 可以輕鬆的對請求做一些限制,比如為了安全我們只允許 POST 請求的訪問,只需要在方法上新增一個配置既可:
@RequestMapping(name="/getUser", method= RequestMethod.POST)
public User getUser() {
...
}
這時候再以 GET 請求去訪問,就會返回如下結果:
Request method 'GET' not supported
如果要求其請求只支援 GET 請求,method 設定為:method= RequestMethod.GET;如果不進行設定預設兩種方式的請求都支援。
Spring Web 層支援多種方法傳參,在上一課中我們傳入一個屬性 name,直接使用物件接收也可以支援。
@RequestMapping(name="/getUser", method= RequestMethod.POST)
public String getUser(User user) {
...
}
這樣的寫法,只要是 User 的屬性都會被自動填充到 User 物件中;還有另外一種傳參的方式,使用 URL 進行傳參,這種形式的傳參位址列會更加美觀一些。
@RequestMapping(value="get/{name}", method=RequestMethod.GET)
public String get(@PathVariable String name) {
return name;
}
在瀏覽器中輸入網址:http://localhost:8080/get/neo,返回:neo,說明 name 值已經成功傳入。
資料校驗
在很多時候,當我們要處理一個應用程式的業務邏輯時,資料校驗是必須要考慮和麵對的事情。應用程式必須通過某種手段來確保輸入進來的資料從語義上來講是正確的。在 Java 應用程式中,必須要對輸入進來的資料從語義上分析是有效的,也就是資料校驗。
輸入驗證是最重要的 Web 開發任務之一,在 Spring MVC 中有兩種方式可以驗證輸入:一種是 Spring 自帶的驗證框架,另外一種是利用 JSR 實現。
JSR 是一個規範文件,指定了一整套 API,通過標註給物件屬性新增約束。Hibernate Validator 就是 JSR 規範的具體實現,Hibernate Validator 提供了 JSR 規範中所有內建約束註解的實現,以及一些附加的約束註解,除此之外使用者還可以自定義約束註解。
Spring Boot 的引數校驗依賴於 hibernate-validator 來進行。使用 Hibernate Validator 校驗資料,需要定義一個接收的資料模型,使用註解的形式描述欄位校驗的規則,我們以 User 物件為例為大家演示如何使用。
首先在 WebController 新增一個儲存的方法 saveUser,引數為 User。
@RequestMapping("/saveUser")
public void saveUser(@Valid User user,BindingResult result) {
System.out.println("user:"+user);
if(result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
for (ObjectError error : list) {
System.out.println(error.getCode()+ "-" + error.getDefaultMessage());
}
}
}
- @Valid 引數前面新增 @Valid 註解,代表此物件使用了引數校驗;
- BindingResult 引數校驗的結果會儲存在此物件中,可以根據屬性判斷是否校驗通過,校驗不通過可以將錯誤資訊打印出來。
接下來在 User 中給需要校驗的引數新增對應的註解,對不同的屬性,按照規則新增不同的校驗內容。
public class User {
@NotEmpty(message="姓名不能為空")
private String name;
@Max(value = 100, message = "年齡不能大於100歲")
@Min(value= 18 ,message= "必須年滿18歲!" )
private int age;
@NotEmpty(message="密碼不能為空")
@Length(min=6,message="密碼長度不能小於6位")
private String pass;
//...
}
其中,message="密碼不能為空"
,為自定義返回的錯誤資訊。
Hibernate Validator 基本上包含了常用的資料校驗,包括校驗屬性是否為空、長度、大小、特定格式等,完整的註解可以看下錶:
註解 | 應用目標 | 執行時檢查 | Hibernate 元資料影響 |
---|---|---|---|
@Length(min=, max=) | 屬性(String) | 檢查字串長度是否符合範圍 | 列長度會被設到最大值 |
@Max(value=) | 屬性(以 numeric 或者 string 型別來表示一個數字) | 檢查值是否小於或等於最大值 | 對列增加一個檢查約束 |
@Min(value=) | 屬性(以 numeric 或者 string 型別來表示一個數字) | 檢查值是否大於或等於最小值 | 對列增加一個檢查約束 |
@NotNull | 屬性 | 檢查值是否非空(not null) | 列不為空 |
@Past | 屬性(date 或 calendar) | 檢查日期是否是過去時 | 對列增加一個檢查約束 |
@Future | 屬性(date 或 calendar) | 檢查日期是否是將來時 | 無 |
@Pattern(regex="regexp", flag=) | 屬性(string) | 檢查屬性是否與給定匹配標誌的正則表示式相匹配(見 java.util.regex.Pattern ) | 無 |
@Range(min=, max=) | 屬性(以 numeric 或者 string 型別來表示一個數字) | 檢查值是否在最小和最大值之間(包括臨界值) | 對列增加一個檢查約束 |
@Size(min=, max=) | 屬性(array,collection,map) | 檢查元素大小是否在最小和最大值之間(包括臨界值) | 無 |
@AssertFalse | 屬性 | 檢查方法的演算結果是否為 false(對以程式碼方式而不是註解表示的約束很有用) | 無 |
@AssertTrue | 屬性 | 檢查方法的演算結果是否為 true(對以程式碼方式而不是註解表示的約束很有用) | 無 |
@Valid | 屬性(object) | 對關聯物件遞迴進行驗證。如果物件是集合或陣列,就遞迴地驗證其元素;如果物件是 Map,則遞迴驗證其值元素 | 無 |
屬性(String) | 檢查字串是否符合有效的 email 地址規範 | 無 |
新增測試方法,對屬性校驗進行測試:
@Test
public void saveUsers() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/saveUser")
.param("name","")
.param("age","666")
.param("pass","test")
);
}
結果返回:
user:name=,age=666,pass=test
Max-年齡不能大於100歲
Length-密碼長度不能小於6位
NotEmpty-姓名不能為空
結果顯示均已經觸發了校驗規則,返回了錯誤資訊,在實際使用過程中可以對錯誤資訊進行包裝,最後返回到前端進行展示。
自定義 Filter
Filter 也稱之為過濾器,可以在前端攔截所有使用者的請求,可以認為是 Servlet 的一種加強版,Web 開發人員通過 Filter 技術,對 Web 伺服器管理的所有 Web 資源,例如 JSP、Servlet、靜態圖片檔案或靜態 HTML 檔案等進行攔截,從而實現一些特殊的功能。例如,實現 URL 級別的許可權訪問控制、過濾敏感詞彙、排除有 XSS 威脅的字元、記錄請求日誌等一些高階功能。
Spring Boot 內建了一些 Filter,比如,處理編碼的 OrderedCharacterEncodingFilter 和請求轉化的 HiddenHttpMethodFilter,也支援根據我們的需求來可以自定義 Filter。
自定義 Filter 有兩種實現方式,第一種是使用 @WebFilter,第二種是使用 FilterRegistrationBean,經過實踐之後發現使用 @WebFilter 自定義的過濾器優先順序順序不能生效,因此推薦使用第二個方案,接下來我們詳細介紹第二種方案。
自定義 Filter 兩個步驟:
- 實現 Filter 介面,實現其中的 doFilter() 方法;
- 新增 @Configuration 註解,將自定義 Filter 加入過濾鏈。
新建 MyFilter 類,重寫 doFilter() 方法:
public class MyFilter implements Filter {
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest srequest, ServletResponse sresponse, FilterChain filterChain)
throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletRequest request = (HttpServletRequest) srequest;
System.out.println("this is MyFilter,url :"+request.getRequestURI());
filterChain.doFilter(srequest, sresponse);
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
將自定義 Filter 加入過濾鏈:
@Configuration
public class WebConfiguration {
@Bean
public FilterRegistrationBean testFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new MyFilter());
registration.addUrlPatterns("/*");
registration.setName("MyFilter");
registration.setOrder(6);
return registration;
}
}
新增完後啟動專案,在瀏覽器中輸入地址:http://localhost:8080/getUsers,就會看到控制檯列印如下資訊:
this is MyFilter,url :/xxx
說明 MyFilter 已經對所有的 URL 進行了監控。當有多個過濾器時可以通過設定 Order 屬性來決定它們的執行順序,Order 值越小優先順序越高。我們複製上面的 MyFilter 重新命名為 MyFilter2,在 WebConfiguration 中新增 MyFilter2 的配置:
public FilterRegistrationBean test2FilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new MyFilter2());
registration.addUrlPatterns("/*");
registration.setName("MyFilter2");
registration.setOrder(1);
return registration;
}
將 MyFilter 的 Order 屬性設定為 6,將 MyFilter2 的 Order 屬性設定為 1,重新啟動專案,在瀏覽器中輸入地址:http://localhost:8080/getUsers,就會看到控制檯列印如下資訊:
this is MyFilter2,url :/getUsers
this is MyFilter,url :/getUsers
可以發現過濾器 MyFilter2 因為 Order 值設定得低,會優先被執行。
配置檔案
在 Web 開發的過程中,經常需要自定義一些配置檔案,如何使用呢?
在 application.properties 中配置:
neo.title=純潔的微笑
neo.description=分享技術,品味生活
Spring Boot 也支援 Yaml 語法書寫,比如上面的配置內容可以寫到 application.yml 檔案中,格式如下:
neo:
title: 純潔的微笑
description: 分享技術,品味生活
在選擇兩種語法時,Yaml 語法更加簡潔,properties 使用更加廣泛,團隊中保持一致即可。內容配置完成後,需要自定義一個物件來接收配置內容。
注:同時存在 application.yml 和 application.properties,並且裡面配置相同,application.properties 的配置會覆蓋 application.yml。
讀取單個配置項
當需要從配置檔案載入單個配置內容時,只需要使用 @Value 屬性即可,新建 PropertiesTest 測試類進行測試。
@RunWith(SpringRunner.class)
@SpringBootTest
public class PropertiesTest {
@Value("${neo.title}")
private String title;
@Test
public void testSingle() {
Assert.assertEquals(title,"純潔的微笑");
}
}
- @Value("${neo.title}") 會預設讀取 application.properties 或者 application.yml 檔案中的 neo.title 配置屬性值,並賦值給 title。
- Assert.assertEquals 是判斷屬性值是否和目標值一致。
執行測試用例執行正常,說明屬性值載入成功。
讀取多個配置
通常在專案中使用配置檔案時,往往需要載入多個配置項,比如資料庫連線引數等,通常會定義一個物件來接收多個配置項,方便在專案中使用。比如定義一個 NeoProperties 物件,來接收所有以 neo 開頭的配置內容。
@Component
@ConfigurationProperties(prefix="neo")
public class NeoProperties {
private String title;
private String description;
//省略getter settet方法
}
- @Component 的定義為例項,方便在 Spring Boot 專案中引用;
- @ConfigurationProperties(prefix="neo"),表示以 neo 開頭的屬性會自動賦值到物件的屬性中,比如,neo.title 會自動賦值到物件屬性 title 中。
寫單元測試進行驗證,使用屬性時直接將 NeoProperties 物件注入即可。
@Resource
private NeoProperties properties;
@Test
public void testMore() throws Exception {
System.out.println("title:"+properties.getTitle());
System.out.println("description:"+properties.getDescription());
}
執行 test 後輸出結果:
title:純潔的微笑
description:分享技術,品味生活
自定義配置檔案
有時候需要自定義配置檔案,以便和系統使用的 application.properties 檔案區分開,避免混淆。Spring Boot 對這種情況也有很好的支援。
在 resources 目錄下建立一個 other.properties 檔案,內容如下:
other.title=keep smile
other.blog=www.ityouknow.com
和上面一樣定義一個物件來接收配置檔案的內容:
@Component
@ConfigurationProperties(prefix="other")
@PropertySource("classpath:other.properties")
public class OtherProperties {
private String title;
private String blog;
//省略getter settet方法
}
對比上面讀取多個配置示例,多了一個註解來指明配置檔案地址:@PropertySource("classpath:other.properties"),同樣建立一個測試方法,檢測是否正確載入了外部配置檔案。
@Test
public void testOther() throws Exception {
System.out.println("title:"+otherProperties.getTitle());
System.out.println("blog:"+otherProperties.getBlog());
}
執行 test 後輸出結果:
title:my title
blog:www.ityouknow.com
說明自定義配置檔案載入成功。
更多自定義配置
https://www.cnblogs.com/magicalSam/p/7189421.html
如果測試中出現中文亂碼,可按照以下方法進行設定:
依次單擊 File | Settings | Editor | File Encodings 命令,將 Properties Files (*.properties) 下的 Default encoding for properties files 設定為 UTF-8,勾選 Transparent native-to-ascii conversion 複選框。
總結
Spring Boot 集成了引數校驗、內嵌容器、Restful、JSON 等內容,這些技術在我們進行 Web 開發中必不可少。Spring Boot 對這些技術進行了包裝,讓我們在 Web 專案開發過程中非常容易地使用這些技術,降低了開發 Web 專案的技術難度。