Spring Boot 對基礎 Web 開發的支持(下)

數據校驗

在很多時候,當我們要處理一個應用程序的業務邏輯時,數據校驗是必須要考慮和麪對的事情。應用程序必須通過某種手段來確保輸入進來的數據從語義上來講是正確的。在 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,則遞歸驗證其值元素
@Email 屬性(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

說明自定義配置文件加載成功。

如果測試中出現中文亂碼,可按照以下方法進行設置:

依次單擊 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 項目的技術難度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章