前言
??本篇文章主要簡單了解下Spring中一些JSR規范所提供的注解,所謂JSR規范,是Java Specification Requests的縮寫,意為Java規范提案,它是JCP,Java標準化組織(Java Community Process)提交給Sun/Oracle的標準化技術規范的請求,目的是為了提供一種規范,并且補充和完善JAVA相關的功能。而目前,JSR規范已經成為了Java界的一個重要標準。JSR所有規范的地址是:https://jcp.org/en/jsr/all
一、JSR-250規范相關
1. Resource注解
??和@Autowired功能類似,@Resource注解也是用來注入bean的,但它并不屬于Spring的注解,而是JSR-250規范中提供的注解,位于javax.annotation包下,它和@Autowired不同的一點主要就是,默認情況下它是根據名稱(byName)來注入的,而@Autowired是根據類型來注入的(byType)。我們先來看下它的實現:
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
從上面我們可以看到,該注解可以用于類,方法與屬性上。接下來,我們來看一下它的一些參數:
name
,用于綁定JNDI context的對象的名稱(不太明白),默認情況下,用于屬性上時,該名稱是字段名;用于方法上時,名稱是與方法相對應的JavaBean的屬性名;而用于類上時,沒有默認值,必須要指定一個。
@Resource
private DataSource dataSource; // bean的名稱就是dataSource
// 同理
private DataSource dataSource;
@Resource
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
lookup
,要注入的bean的名稱(不太明白);type
,要注入的bean的類型,和name的默認情況類似。用于屬性上時,該類型是屬性字段的類型;用于方法上時,類型是與方法相對應的javaBean的屬性類型;而用于類上時,沒有默認值,必須要指定一個;authenticationType
, 枚舉類型,不太明白;shareable
,表示該組件和其他組件之間是否可以共享該資源,默認true類型;mappedName
,不太明白;description
,要注入的bean的描述信息。
由于Resource注解不但但用于bean的注入,更多的情況下還可以用于解析JNDI相關的對象,所以我們不用過多關注它的屬性,等用到JNDI的時候再來查看文檔不晚。
@Resource在通過byName 進行自動注入對象時,如果查詢不到,會回退到byType,也就是說如果通過byName查詢不到對象,那么就會通過byType來進行注入。
有關@Resource的解釋,除了看下Spring官方文檔,可以看下Oracle 的JAVA EE 6的官方文檔:https://docs.oracle.com/javaee/6/tutorial/doc/bncjk.html
2. PostContruct 和 PreDestroy 注解
??首先,我們知道,Spring 容器中的 Bean 是有生命周期的,Spring 允許在 Bean 在初始化完成后以及 Bean 銷毀前執行特定的操作,而常用的設定方式有以下三種:
1.通過實現 InitializingBean/DisposableBean 接口來定制初始化之后或者銷毀之前的操作方法;
- 通過 <bean> 元素的 init-method/destroy-method屬性指定初始化之后 或者銷毀之前調用的操作方法;
- 在指定方法上加上@PostConstruct 或@PreDestroy注解來制定該方法是在初始化之后還是銷毀之前調用。
??和@Resource注解一樣,這兩個注解也不屬于Spring本身的注解,也是屬于JSR-250規范所提供的注解,而且這兩個注解都是用于方法級別的。
這里有關它們的順序,我們簡單說下,感興趣的童鞋可以自己測試下:
在Spring的bean進行注入的時候,首先會初始化構造方法,然后會執行@PostContruct注解修飾的方法,其次會執行InitializingBean方法,最后,才會執行init-method方法。也就是
Constructor > @PostConstruct > InitializingBean > init-method
;而對應的PreDestroy
修飾的方法則會在destroy
方法執行之后執行;
這里參考自:源碼解析:init-method、@PostConstruct、afterPropertiesSet孰先孰后
- 這兩個注解所修飾的方法一般是無參的非靜態方法,
- 由于@PostConstruct注解是在對象構造完成后調用init-method之前執行的,所以如果我們配置了延遲初始化的話(
default-lazy-init="true"
),只有當這個bean被調用了的情況下,@PostConstruct注解所修飾的方法才會執行。
二、JSR-330 規范
1. Inject 注解
??另一個可以實現注入功能的注解是@Inject,該注解也不是Spring注解,而是JSR-330規范中提供的注解,而JSR-330規范則是為了統一Java依賴注入的規范,所以其核心注解@Inject基本上可以完全替換@Autowired注解。該規范一共6個注解,分別對應為:Inject
,Named
,Provider
,Qualifier
,Scope
,Singleton
。該注解所在jar包對應的maven地址為:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
- @Inject 和 @Autowired一樣,也是通過byType來自動注入,都是通過
AutowiredAnnotationBeanPostProcessor
類來實現的依賴注入。@Inject 注解可以用于方法,屬性,構造器上,與@Autowired不同的是,@Inject沒有 required屬性,因此@Inject注解所依賴的關系必須存在,如果不存在,則會拋出一個異常。- 相對于@Autowired注解使用@Qualifier注解,@Inject使用@Named注解來解決@Autowired的這個問題。而@Named注解與@Qualifier注解又是特別相似,兩者的區別僅僅在于語義層面。@Qualifier注解幫助我們縮小所匹配Bean的選擇范圍(默認使用Bean的ID),而@Named通過Bean的ID來標志可選擇的bean。
@Inject
@Named("userService")
private UserService userService;
- JSR-330在 javax.inject包里有自己的@Qualifier注解,但JSR-330不建議使用該注解,而是鼓勵我們將此注解做為元注解來自定義我們的注解,另外,這兩個@Qualifier注解功能基本上是相同的。
JSR-330的這幾個注解我們就不仔細說了,等用到了再來細說。對應的官網文檔地址:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/beans.html#beans-standard-annotations
三、JSR-303 相關注解
1. Valid注解
??首先,Valid不是Spring本身提供的注解,而是由JSR-303用于校驗傳入參數合法性的一個注解。做Java后臺開發的都知道,前端參數合法性校驗是我們開發過程中必不可少的步驟,但是驗證參數本身算是一個體力活,而且還會造成代碼冗余,影響代碼的美觀性,而JSR-303則正是為了解決這個問題而出現的一種比較優雅的解決方式。
JSR-303作為JAVA-EE 6 的一項規范,又被稱為Bean Validation,而Hibernate Validator則是官方的參考實現,并且Hibernate Validator還在JSR-303的基礎上進行了擴展,以支持更多的校驗規則。
??第一步,首先,我們需要引入validation-api
jar包和hibernate-validator
包,我們可以去Maven倉庫下載最新版的jar包,截至目前,最新版的包分別是:
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.9.Final</version>
</dependency>
第二步,編寫我們的實體對象User:
public class User implements Serializable{
@NotNull(message = "名字不能為空")
private String name;
@NotNull(message = "id不能為空")
private String id;
@Min(value = 14, message = "數字不能小于14")
private Double aDouble;
......
}
第三步,編寫我們的控制器方法,使用@Valid注解來進行校驗:
@RequestMapping(value = "/test.html", method = RequestMethod.GET)
public JsonResult findPet(@Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 錯誤提示信息
List<ObjectError> errorList = bindingResult.getAllErrors();
for(ObjectError error : errorList){
System.out.println(error.getDefaultMessage());
}
FieldError fieldError = bindingResult.getFieldError();
String validMess = fieldError.getDefaultMessage();
jsonResult = new JsonResult(false, validMess);
return jsonResult;
}
}
??一般情況下,我們可以通過借助BindingResult對象來判斷錯誤信息,然后根據獲取到的錯誤信息,拋給全局異常處理,或者把錯誤信息封裝成 JsonResult 對象返回給我們的前端。
??無需過多配置,就可以實現校驗的功能,并且省去了許多冗余的代碼,讓代碼看上去更美觀些。而如果要查看對應的校驗類型的話,我們可以參考JSR303規范來進行查看。而如果Hibernate Validtor滿足不了我們的需求的時候,我們還可以自定義校驗注解。
這里簡單說幾點:
- 一般情況下,我們都可以通過BindingResult來檢查錯誤信息,如果不適用該對象的話,如果校驗不通過,Spring將直接會拋出異常;
- JSR-303和Hibernate Validtor都是用于校驗對象,而針對單個參數的校驗,我們可以使用Spring Validator來實現,Spring Validator是在JSR-303規范基礎上進行了擴展,添加了MethodValidationPostProcessor攔截器,可以使用@Validated注解實現對方法參數的校驗,并且還可以實現分組校驗,而@Valid是不支持分組校驗的(比如同一個實現在不同的controller中,而我們想分別對不同Controller中的實體進行校驗,這就是分組),后面看下簡單的例子。
- 這里只列舉了JSR-303規范,并且只學習了最基礎也是最常用的校驗功能,其實關于校驗的東西還有很多,比如國際化,錯誤信息封裝,組合參數校驗等,這些等我們后續有時間了再仔細研究。
針對方法參數驗證,我們看一個簡單的實現:
- 添加MethodValidationPostProcessor的bean配置:
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
- Controller添加@Validated注解:
@RestController
@RequestMapping("/world")
@Validated
public class WorldController {
}
- Controller中方法接著使用JSR-303注解:
@RequestMapping(value = "/test.html", method = RequestMethod.GET)
public String findPet(@NotNull(message = "不能為空") String user) {
}
- 可以看到我們的配置已經生效,如果我們的傳入參數不滿足條件,將會拋出ConstraintViolationException異常,我們可以通過它的getConstraintViolations獲得所有沒有通過校驗的ConstraintViolation集合,然后來得到它們對應的錯誤信息;也可以使用全局異常來處理:
@ExceptionHandler
@ResponseBody
public JsonResult exceptions(ConstraintViolationException exception) {
JsonResult jsonResult = new JsonResult();
jsonResult.setSuccess(false);
jsonResult.setMessage(exception.getMessage());
return jsonResult;
}
- JSR-303不但可以用于后端接收前端參數的校驗,也可以用于dubbo接口參數的校驗;
- 這里大致說下相關版本對應的規范,Bean Validation 1.0對應 JSR-303,屬于JAVA EE 6的規范;Bean Validation 1.1 對應 JSR-349,屬于JAVA EE 7的規范;Bean Validation 2.0對應JSR-380,屬于JAVA EE 8的規范。大家可以根據需要,了解下新版本的內容。