Spring MVC 資料繫結流程分析
1. 資料繫結流程原理★
① Spring MVC 主框架將 ServletRequest 物件及目標方法的入參例項傳遞給 WebDataBinderFactory 例項,以建立 DataBinder 例項物件
② DataBinder 呼叫裝配在 Spring MVC 上下文中的 ConversionService 元件進行資料型別轉換、資料格式化工作。將 Servlet 中的請求資訊填充到入參物件中
③ 呼叫 Validator
④ Spring MVC 抽取 BindingResult 中的入參物件和校驗錯誤物件,將它們賦給處理方法的響應入參
Spring MVC 通過反射機制對目標處理方法進行解析,將請求訊息繫結到處理方法的入參中。資料繫結的核心部件是 DataBinder,執行機制如下:
原理:SpringMVC如何確定POJO引數的值,以及將頁面的值正確的賦值給POJO 1、使用資料繫結器來負責將頁面帶來的引數繫結到pojo中
binderFactory //繫結器工廠根據當前請求,其他資訊;創建出資料繫結器; //資料繫結器負責將請求中的資料繫結到pojo中 WebDataBinder binder = binderFactory.createBinder(request, attribute, name); if (binder.getTarget() != null) { //資料繫結期間進行型別轉換以及格式化工作 bindRequestParameters(binder, request); //資料校驗:email;birth;BindingResult元件中會封裝錯誤資訊; validateIfApplicable(binder, parameter); //資料校驗錯誤資訊處理 if (binder.getBindingResult().hasErrors()) { //如果出錯有處理 if (isBindExceptionRequired(binder, parameter)) { \\parameter指目標方法正在處理的當前引數 saveEmp(Employee emp) protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) { int i = parameter.getParameterIndex();//獲取引數索引 Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();//獲取當前方法所有引數的引數型別 //如果下一個引數是Errors旗下的,就返回成功;否則就是沒人處理; boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1])); return !hasBindingResult; } //如果沒人處理就拋異常 throw new BindException(binder.getBindingResult()); } } }
SpringMVC預設使用的是DefaultFormattingCOnversionService ConversionService converters = 負責資料型別轉換及格式化;都是裡面的每一個轉換器負責工作; @org.springframework.format.annotation.DateTimeFormat java.lang.Long -> java.lang.String: org.springfra[email protected]7c515e0,@org.springframework.format.annotation.NumberFormat java.lang.Long -> java.lang.String: org.sprin[email protected]592e7da0 @org.springframework.format.annotation.DateTimeFormat java.time.LocalDate -> java.lang.String: org.springframework.format.d[email protected]7bfa429f,java.time.LocalDate -> java.lang.String : org.s[email protected]34d07c25 @org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime -> java.lang.String: org.springframework.format.d[email protected]7bfa429f,java.time.LocalDateTime -> java.lang.String : org.s[email protected]2fbd9c99 @org.springframework.format.annotation.DateTimeFormat java.time.LocalTime -> java.lang.String: org.springframework.format.d[email protected]7bfa429f,java.time.LocalTime -> java.lang.String : org.s[email protected]317cb8e5 @org.springframework.format.annotation.DateTimeFormat java.time.OffsetDateTime -> java.lang.String: org.springframework.format.d[email protected]7bfa429f,java.time.OffsetDateTime -> java.lang.String : org.springframework.form[email protected] @org.springframework.format.annotation.DateTimeFormat java.time.OffsetTime -> java.lang.String: org.springframework.format.d[email protected]7bfa429f,java.time.OffsetTime -> java.lang.String : org.s[email protected]616a3ae3 @org.springframework.format.annotation.DateTimeFormat java.time.ZonedDateTime -> java.lang.String: org.springframework.format.d[email protected]7bfa429f,java.time.ZonedDateTime -> java.lang.String : org.s[email protected]48de4b92 @org.springframework.format.annotation.DateTimeFormat java.util.Calendar -> java.lang.String: org.springfra[email protected]7c515e0 @org.springframework.format.annotation.DateTimeFormat java.util.Date -> java.lang.String: org.springfra[email protected]7c515e0 @org.springframework.format.annotation.NumberFormat java.lang.Double -> java.lang.String: org.sprin[email protected]592e7da0 @org.springframework.format.annotation.NumberFormat java.lang.Float -> java.lang.String: org.sprin[email protected]592e7da0 @org.springframework.format.annotation.NumberFormat java.lang.Integer -> java.lang.String: org.sprin[email protected]592e7da0 @org.springframework.format.annotation.NumberFormat java.lang.Short -> java.lang.String: org.sprin[email protected]592e7da0 @org.springframework.format.annotation.NumberFormat java.math.BigDecimal -> java.lang.String: org.sprin[email protected]592e7da0 @org.springframework.format.annotation.NumberFormat java.math.BigInteger -> java.lang.String: org.sprin[email protected]592e7da0 java.lang.Boolean -> java.lang.String : o[email protected]1ae88e8d java.lang.Character -> java.lang.Number : or[email protected]15499bcc java.lang.Character -> java.lang.String : o[email protected]314796e3 java.lang.Enum -> java.lang.String : [email protected]7a9e5f5 java.lang.Long -> java.util.Calendar : org.springframework[email protected]2425e96f java.lang.Long -> java.util.Date : org.springframe[email protected]37be4c53 java.lang.Number -> java.lang.Character : org.[email protected]64e80876 java.lang.Number -> java.lang.Number : org.spri[email protected]24d24a7d java.lang.Number -> java.lang.String : o[email protected]2d2b9f78 java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.lang.Long: org.springfra[email protected]7c515e0,java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Long: org.sprin[email protected]592e7da0 java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.LocalDate: org.springframework.format.d[email protected]7bfa429f,java.lang.String -> java.time.LocalDate: org.[email protected]1b95054a java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime: org.springframework.format.d[email protected]7bfa429f,java.lang.String -> java.time.LocalDateTime: org.[email protected]1cb17371 java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.LocalTime: org.springframework.format.d[email protected]7bfa429f,java.lang.String -> java.time.LocalTime: org.[email protected]183beaef java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.OffsetDateTime: org.springframework.format.d[email protected]7bfa429f,java.lang.String -> java.time.OffsetDateTime: org.[email protected]5b5278c0 java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.OffsetTime: org.springframework.format.d[email protected]7bfa429f,java.lang.String -> java.time.OffsetTime: org.[email protected]43da3826 java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.ZonedDateTime: org.springframework.format.d[email protected]7bfa429f,java.lang.String -> java.time.ZonedDateTime: org.[email protected]ff5bcf4 java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.util.Calendar: org.springfra[email protected]7c515e0 java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.util.Date: org.springfra[email protected]7c515e0 java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Double: org.sprin[email protected]592e7da0 java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Float: org.sprin[email protected]592e7da0 java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Integer: org.sprin[email protected]592e7da0 java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Short: org.sprin[email protected]592e7da0 java.lang.String -> @org.springframework.format.annotation.NumberFormat java.math.BigDecimal: org.sprin[email protected]592e7da0 java.lang.String -> @org.springframework.format.annotation.NumberFormat java.math.BigInteger: org.sprin[email protected]592e7da0 java.lang.String -> java.lang.Boolean : or[email protected]1795e212 java.lang.String -> java.lang.Character : org.[email protected]7f0a5c2 java.lang.String -> java.lang.Enum : org.sp[email protected]3ee79e1e java.lang.String -> java.lang.Number : org.spri[email protected]35ac0acf java.lang.String -> java.time.Instant: [email protected]824112 java.lang.String -> java.util.Locale : o[email protected]5f141e60 java.lang.String -> java.util.Properties : org.s[email protected]34316a3 java.lang.String -> java.util.UUID : [email protected]4f52170 java.time.Instant -> java.lang.String : org.springframework.format.datetime.standard.InstantFormatte[email protected] java.time.ZoneId -> java.util.TimeZone : org[email protected]309ed382 java.util.Calendar -> java.lang.Long : org.springframework[email protected]783071dc java.util.Calendar -> java.util.Date : org.springframework[email protected]18da2904 java.util.Date -> java.lang.Long : org.springframe[email protected]3a5ea3fa java.uti...4、如果是資料校驗使用校驗器進行 validators 5、處理校驗成功還是失敗資訊?BindingResult負責處理校驗錯誤的資訊
應用: 1)、自定義的型別轉換器;
ConversionService 是 Spring 型別轉換體系的核心介面。
可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定義一個 ConversionService.
Spring 將自動識別出 IOC 容器中的 ConversionService,並在 Bean 屬性配置及
Spring MVC 處理方法入參繫結等場合使用它進行資料的轉換
可通過 ConversionServiceFactoryBean 的 converters 屬性註冊自定義的型別轉換器
</bean> <bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean"> <!-- 我們的型別轉換器要賦值給他 --> <property name="converters"> <!-- 給set賦值 --> <set> <ref bean="myStringToEmployeeConvertor"/> </set> </property> </bean> <!-- 靜態資源被SpringMVC攔截到了,所以jquery 404 --> <!-- SpringMVC處理不了的資源,現在交給tomcat的DefaultServlet; default-servlet-name: default-servlet-name="default";如果只有它,動態資源就完蛋了; --> <!--靜態資源訪問ok --> <mvc:default-servlet-handler/> <!--動態資源就能訪問;開掛版的mvc模式 --> <!-- 轉換器採用自定義的 --> <mvc:annotation-driven conversion-service="conversionServiceFactoryBean"></mvc:annotation-driven>
1) Spring 支援的轉換器型別
Spring 定義了 3 種類型的轉換器介面,實現任意一個轉換器介面都可以作為自定義轉換器註冊到 ConversionServiceFactoryBean 中:
Converter<S,T>:將 S 型別物件轉為 T 型別物件
ConverterFactory:將相同系列多個 “同質” Converter 封裝在一起。如果希望將一種型別的物件轉換為另一種型別及其子類的物件(例如將 String 轉換為 Number 及 Number 子類(Integer、Long、Double 等)物件)可使用該轉換器工廠類
GenericConverter:會根據源類物件及目標類物件所在的宿主類中的上下文資訊進行型別轉換
public class MyStringToEmployeeConvertor implements Converter<String, Employee>{ @Autowired DepartmentDao dao; @Override public Employee convert(String source) { // TODO Auto-generated method stub Employee employee = new Employee(null, "haha", "hahah", 0,null); System.out.println("MyStringToEmployeeConvertor開始工作啦......."+source); //[email protected] if(!"".equals(source)&&source!=null){ //把字串按照"-"進行分割,得到的資料儲存在陣列中 String[] split = source.split("-"); employee.setLastName(split[0]); employee.setEmail(split[1]); employee.setGender(Integer.parseInt(split[2])); Department department = dao.getDepartment(Integer.parseInt(split[3])); employee.setDepartment(department); } return employee; }JSR303資料校驗; 以前沒有資料校驗; 資料輸錯:400頁面; 我們希望:回到本頁面,提示哪一項出錯即可; 以後校驗,在關鍵位置,我們都應該使用雙端校驗;前端+後端; JSR303資料校驗流程: 1、導包 匯入校驗框架的jar包; Hibernate Validator標準5個包
hibernate-validator-5.0.0.CR2.jar hibernate-validator-annotation-processor-5.0.0.CR2.jar classmate-0.8.0.jar jboss-logging-3.1.1.GA.jar validation-api-1.1.0.CR1.jarTomcat7.0以下(不包括7)需要把下面el相關的三個包的包匯入到tomcat的lib裡面,覆蓋之前的el表示式
2、加註解
給需要校驗的javaBean屬性上加註解;給javaBean加校驗註解
private Integer id; @NotEmpty @Length(min=5,max=15) private String lastName; @Email private String email; //1 male, 0 female private Integer gender=1; /** * pattern:指定日期格式 */ @DateTimeFormat(pattern="yyyy-MM-dd") @Past private Date birthDay;
3、告訴SpringMVC這個javaBean需要校驗;@Valid
一定注意:所有的ModelAttribute要全部統一;
* 在員工更新操作之前;在點選提交之前,提前執行查出資料
* 校驗成功失敗的資訊,以及校驗失敗後怎麼做?
* 給引數後面緊跟一個Errors、或者BindingResult
public String empUpdate(@Valid @ModelAttribute("employee") Employee employee,Errors errors){
//後端把錯誤顯示出來 if(errors.getFieldErrorCount()>0){ System.out.println("型別轉換出錯誤了"); //1、獲取產生的所有的錯誤 List<FieldError> fieldErrors = errors.getFieldErrors(); for(FieldError fieldError:fieldErrors){ System.out.println(fieldError.getField()+"-"+fieldError.getDefaultMessage()); } return "/edit"; } System.out.println(employee); employeeDao.save(employee); return "redirect:/emps"; }
4 在頁面上顯示錯誤
Spring MVC 除了會將表單/命令物件的校驗結果儲存到對應的 BindingResult 或 Errors 物件中外,還會將所有校驗結果儲存到“隱含模型”
即使處理方法的簽名中沒有對應於表單/命令物件的結果入參,校驗結果也會儲存在 “隱含物件” 中。
隱含模型中的所有資料最終將通過 HttpServletRequest 的屬性列表暴露給 JSP 檢視物件,因此在 JSP 中可以獲取錯誤資訊
在 JSP 頁面上可通過 <form:errors path=“userName”> 顯示錯誤訊息
<form:form action="${ ctp}/emp/${employee.id }" method="post" modelAttribute="employee" > birthDay:<form:input path="birthDay"/><form:errors path="birthDay"/><br/> email:<form:input path="email"/><form:errors path="email"/><br> 男:<form:radiobutton path="gender" value="1"/> 女:<form:radiobutton path="gender" value="0"/><br/> <!-- SpringMvc習慣於直接用引數,而不是通過類。引數獲取 --> department:<form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id" ></form:select><br/> <!-- 瀏覽器 form 表單只支援 GET 與 POST 請求,而DELETE、PUT 等 method 並不支援, Spring3.0 添加了一個過濾器,可以將這些請求轉換為標準的 http 方法,使得支援 GET、POST、PUT 與 DELETE 請求。 所以要新增name="_method" value="PUT",過濾器根據這個進行區分 --> <input type="hidden" name="_method" value="PUT" > <input type="hidden" name="id" value="${employee.id}"> <input type="submit" value="更新"> </form:form>
5. 提示訊息的國際化
每個屬性在資料繫結和資料校驗發生錯誤時,都會生成一個對應的 FieldError 物件。
當一個屬性校驗失敗後,校驗框架會為該屬性生成 4 個訊息程式碼,這些程式碼以校驗註解類名為字首,結合 modleAttribute、屬性名及屬性型別名生成多個對應的訊息程式碼:例如 User 類中的 password 屬性標註了一個 @Pattern 註解,當該屬性值不滿足 @Pattern 所定義的規則時, 就會產生以下 4 個錯誤程式碼:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
當使用 Spring MVC 標籤顯示錯誤訊息時, Spring MVC 會檢視 WEB 上下文是否裝配了對應的國際化訊息,如果沒有,則顯示預設的錯誤訊息,否則使用國際化訊息。
若資料型別轉換或資料格式轉換時發生錯誤,或該有的引數不存在,或呼叫處理方法時發生錯誤,都會在隱含模型中建立錯誤訊息。其錯誤程式碼字首說明如下:
required:必要的引數不存在。如 @RequiredParam(“param1”) 標註了一個入參,但是該引數不存在
typeMismatch:在資料繫結時,發生資料型別不匹配的問題
methodInvocation:Spring MVC 在呼叫處理方法時發生了錯誤
Field error in object 'employee' on field 'lastName': rejected value [E-AA];
codes
[
Length.employee.lastName,//校驗註解類名.modleAttribute(儲存隱含模型)的key.屬性
Length.lastName,//校驗註解類名.屬性
Length.java.lang.String,//校驗註解類名.屬性型別
Length//校驗註解類名
];//4種錯誤程式碼;任何欄位錯誤都會有他的錯誤程式碼;國際化檔案中的key就是要寫這個錯誤程式碼;
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [employee.lastName,lastName]; arguments []; default message [lastName],15,5]; default message [length must be between 5 and 15]
2)、配置使用SpringMVC管理國際化資原始檔
<!-- SpringMVC管理國際化資原始檔 --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="error"></property> </bean>
3)、SpringMVC:<form:errors path="fieldName">自動的按照國際化要求取出錯誤資訊;
https://www.cnblogs.com/limingxian537423/p/7277538.html
https://www.jianshu.com/p/a707acd7eb3a
http://www.imooc.com/article/263093