<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # SpringMVC數據格式化——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC ## 7.3、數據格式化 在如Web /客戶端項目中,通常需要將數據轉換為具有某種格式的字符串進行展示,因此上節我們學習的數據類型轉換系統核心作用不是完成這個需求,因此Spring3引入了格式化轉換器(Formatter SPI) 和格式化服務API(FormattingConversionService)從而支持這種需求。在Spring中它和PropertyEditor功能類似,可以替代PropertyEditor來進行對象的解析和格式化,而且支持細粒度的字段級別的格式化/解析。 Formatter SPI核心是完成解析和格式化轉換邏輯,在如Web應用/客戶端項目中,需要解析、打印/展示本地化的對象值時使用,如根據Locale信息將java.util.Date----&gt;java.lang.String打印/展示、java.lang.String----&gt;java.util.Date等。 該格式化轉換系統是Spring通用的,其定義在org.springframework.format包中,不僅僅在Spring Web MVC場景下。 ### 7.3.1、架構 **1、格式化轉換器:**提供格式化轉換的實現支持。 ![](https://box.kancloud.cn/2016-05-15_5737eddb95eab.jpg) 一共有如下兩組四個接口: **(1、Printer接口:**格式化顯示接口,將T類型的對象根據Locale信息以某種格式進行打印顯示(即返回字符串形式); ``` package org.springframework.format; public interface Printer<T> { String print(T object, Locale locale); } ``` **(2、Parser接口:**解析接口,根據Locale信息解析字符串到T類型的對象; ``` package org.springframework.format; public interface Parser<T> { T parse(String text, Locale locale) throws ParseException; } ``` 解析失敗可以拋出java.text.ParseException或IllegalArgumentException異常即可。 **(3、Formatter接口:**格式化SPI接口,繼承Printer和Parser接口,完成T類型對象的格式化和解析功能; ``` package org.springframework.format; public interface Formatter<T> extends Printer<T>, Parser<T> { } ``` **(4、AnnotationFormatterFactory接口:**注解驅動的字段格式化工廠,用于創建帶注解的對象字段的Printer和Parser,即用于格式化和解析帶注解的對象字段。 ``` package org.springframework.format; public interface AnnotationFormatterFactory<A extends Annotation> {//①可以識別的注解類型 Set<Class<?>> getFieldTypes();//②可以被A注解類型注解的字段類型集合 Printer<?> getPrinter(A annotation, Class<?> fieldType);//③根據A注解類型和fieldType類型獲取Printer Parser<?> getParser(A annotation, Class<?> fieldType);//④根據A注解類型和fieldType類型獲取Parser } ``` 返回用于格式化和解析被A注解類型注解的字段值的Printer和Parser。如JodaDateTimeFormatAnnotationFormatterFactory可以為帶有@DateTimeFormat注解的java.util.Date字段類型創建相應的Printer和Parser進行格式化和解析。 **2、格式化轉換器注冊器、格式化服務:**提供類型轉換器注冊支持,運行時類型轉換API支持。 ![](https://box.kancloud.cn/2016-05-15_5737eddbaacd3.jpg) 一個有如下兩種接口: (1、FormatterRegistry:格式化轉換器注冊器,用于注冊格式化轉換器(Formatter、Printer和Parser、AnnotationFormatterFactory); ``` package org.springframework.format; public interface FormatterRegistry extends ConverterRegistry { //①添加格式化轉換器(Spring3.1 新增API) void addFormatter(Formatter<?> formatter); //②為指定的字段類型添加格式化轉換器 void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); //③為指定的字段類型添加Printer和Parser void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); //④添加注解驅動的字段格式化工廠AnnotationFormatterFactory void addFormatterForFieldAnnotation( AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory); }? ``` **(2、FormattingConversionService:**繼承自ConversionService,運行時類型轉換和格式化服務接口,提供運行期類型轉換和格式化的支持。 FormattingConversionService內部實現如下圖所示: ![](https://box.kancloud.cn/2016-05-15_5737eddbbe2bf.jpg) 我們可以看到FormattingConversionService內部實現如上所示,當你調用convert方法時: ⑴若是S類型-----&gt;String:調用私有的靜態內部類PrinterConverter,其又調用相應的Printer的實現進行格式化; ⑵若是String-----&gt;T類型:調用私有的靜態內部類ParserConverter,其又調用相應的Parser的實現進行解析; ⑶若是A注解類型注解的S類型-----&gt;String:調用私有的靜態內部類AnnotationPrinterConverter,其又調用相應的AnnotationFormatterFactory的getPrinter獲取Printer的實現進行格式化; ⑷若是String-----&gt;A注解類型注解的T類型:調用私有的靜態內部類AnnotationParserConverter,其又調用相應的AnnotationFormatterFactory的getParser獲取Parser的實現進行解析。 注:S類型表示源類型,T類型表示目標類型,A表示注解類型。 此處可以可以看出之前的Converter SPI完成任意Object與Object之間的類型轉換,而Formatter SPI完成任意Object與String之間的類型轉換(即格式化和解析,與PropertyEditor類似)。 ### 7.3.2、Spring內建的格式化轉換器如下所示: | **類名** | **說明** | | --- | --- | --- | | DateFormatter | java.util.Date&lt;----&gt;String實現日期的格式化/解析 | | NumberFormatter | java.lang.Number&lt;----&gt;String實現通用樣式的格式化/解析 | | CurrencyFormatter | java.lang.BigDecimal&lt;----&gt;String實現貨幣樣式的格式化/解析 | | PercentFormatter | java.lang.Number&lt;----&gt;String實現百分數樣式的格式化/解析 | | NumberFormatAnnotationFormatterFactory | @NumberFormat注解類型的數字字段類型&lt;----&gt;String①通過@NumberFormat指定格式化/解析格式②可以格式化/解析的數字類型:Short、Integer、Long、Float、Double、BigDecimal、BigInteger | | JodaDateTimeFormatAnnotationFormatterFactory | @DateTimeFormat注解類型的日期字段類型&lt;----&gt;String①通過@DateTimeFormat指定格式化/解析格式②可以格式化/解析的日期類型:joda中的日期類型(org.joda.time包中的):LocalDate、LocalDateTime、LocalTime、ReadableInstantjava內置的日期類型:Date、Calendar、Longclasspath中必須有Joda-Time類庫,否則無法格式化日期類型 | NumberFormatAnnotationFormatterFactory和JodaDateTimeFormatAnnotationFormatterFactory(如果classpath提供了Joda-Time類庫)在使用格式化服務實現DefaultFormattingConversionService時會自動注冊。 ### 7.3.3、示例 在示例之前,我們需要到[http://joda-time.sourceforge.net/](http://joda-time.sourceforge.net/)下載Joda-Time類庫,本書使用的是joda-time-2.1版本,將如下jar包添加到classpath: ``` joda-time-2.1.jar ``` #### 7.3.3.1、類型級別的解析/格式化 **一、直接使用Formatter SPI進行解析/格式化** ``` //二、CurrencyFormatter:實現貨幣樣式的格式化/解析 CurrencyFormatter currencyFormatter = new CurrencyFormatter(); currencyFormatter.setFractionDigits(2);//保留小數點后幾位 currencyFormatter.setRoundingMode(RoundingMode.CEILING);//舍入模式(ceilling表示四舍五入) //1、將帶貨幣符號的字符串“$123.125”轉換為BigDecimal("123.00") Assert.assertEquals(new BigDecimal("123.13"), currencyFormatter.parse("$123.125", Locale.US)); //2、將BigDecimal("123")格式化為字符串“$123.00”展示 Assert.assertEquals("$123.00", currencyFormatter.print(new BigDecimal("123"), Locale.US)); Assert.assertEquals("¥123.00", currencyFormatter.print(new BigDecimal("123"), Locale.CHINA)); Assert.assertEquals("¥123.00", currencyFormatter.print(new BigDecimal("123"), Locale.JAPAN)); ``` parse方法:將帶格式的字符串根據Locale信息解析為相應的BigDecimal類型數據; print方法:將BigDecimal類型數據根據Locale信息格式化為字符串數據進行展示。 不同于Convert SPI,Formatter SPI可以根據本地化(Locale)信息進行解析/格式化。 其他測試用例請參考cn.javass.chapter7.web.controller.support.formatter.InnerFormatterTest的testNumber測試方法和testDate測試方法。 ``` @Test public void testWithDefaultFormattingConversionService() { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); //默認不自動注冊任何Formatter CurrencyFormatter currencyFormatter = new CurrencyFormatter(); currencyFormatter.setFractionDigits(2);//保留小數點后幾位 currencyFormatter.setRoundingMode(RoundingMode.CEILING);//舍入模式(ceilling表示四舍五入) //注冊Formatter SPI實現 conversionService.addFormatter(currencyFormatter); //綁定Locale信息到ThreadLocal //FormattingConversionService內部自動獲取作為Locale信息,如果不設值默認是 Locale.getDefault() LocaleContextHolder.setLocale(Locale.US); Assert.assertEquals("$1,234.13", conversionService.convert(new BigDecimal("1234.128"), String.class)); LocaleContextHolder.setLocale(null); LocaleContextHolder.setLocale(Locale.CHINA); Assert.assertEquals("¥1,234.13", conversionService.convert(new BigDecimal("1234.128"), String.class)); Assert.assertEquals(new BigDecimal("1234.13"), conversionService.convert("¥1,234.13", BigDecimal.class)); LocaleContextHolder.setLocale(null);}? ``` DefaultFormattingConversionService:帶數據格式化功能的類型轉換服務實現; conversionService.addFormatter():注冊Formatter SPI實現; conversionService.convert(**new**?BigDecimal("1234.128"), String.**class**):用于將BigDecimal類型數據格式化為字符串類型,此處根據“LocaleContextHolder._setLocale_(locale)”設置的本地化信息進行格式化; conversionService.convert("¥1,234.13", BigDecimal.**class**):用于將字符串類型數據解析為BigDecimal類型數據,此處也是根據“LocaleContextHolder._setLocale_(locale)”設置的本地化信息進行解; LocaleContextHolder._setLocale_(locale):設置本地化信息到ThreadLocal,以便Formatter SPI根據本地化信息進行解析/格式化; 具體測試代碼請參考cn.javass.chapter7.web.controller.support.formatter.InnerFormatterTest的testWithDefaultFormattingConversionService測試方法。 **三、自定義Formatter進行解析/格式化** 此處以解析/格式化PhoneNumberModel為例。 **(1、定義Formatter SPI實現**? ``` package cn.javass.chapter7.web.controller.support.formatter; //省略import public class PhoneNumberFormatter implements Formatter<PhoneNumberModel> { Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$"); @Override public String print(PhoneNumberModel phoneNumber, Locale locale) {//①格式化 if(phoneNumber == null) { return ""; } return new StringBuilder().append(phoneNumber.getAreaCode()).append("-") .append(phoneNumber.getPhoneNumber()).toString(); } @Override public PhoneNumberModel parse(String text, Locale locale) throws ParseException {//②解析 if(!StringUtils.hasLength(text)) { //①如果source為空 返回null return null; } Matcher matcher = pattern.matcher(text); if(matcher.matches()) { //②如果匹配 進行轉換 PhoneNumberModel phoneNumber = new PhoneNumberModel(); phoneNumber.setAreaCode(matcher.group(1)); phoneNumber.setPhoneNumber(matcher.group(2)); return phoneNumber; } else { //③如果不匹配 轉換失敗 throw new IllegalArgumentException(String.format("類型轉換失敗,需要格式[010-12345678],但格式是[%s]", text)); } } } ``` 類似于Convert SPI實現,只是此處的相應方法會傳入Locale本地化信息,這樣可以為不同地區進行解析/格式化數據。 **(2、測試用例:** ``` package cn.javass.chapter7.web.controller.support.formatter; //省略import public class CustomerFormatterTest { @Test public void test() { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); conversionService.addFormatter(new PhoneNumberFormatter()); PhoneNumberModel phoneNumber = new PhoneNumberModel("010", "12345678"); Assert.assertEquals("010-12345678", conversionService.convert(phoneNumber, String.class)); Assert.assertEquals("010", conversionService.convert("010-12345678", PhoneNumberModel.class).getAreaCode()); } } ``` 通過PhoneNumberFormatter可以解析String---&gt;PhoneNumberModel和格式化PhoneNumberModel---&gt;String。 到此,類型級別的解析/格式化我們就介紹完了,從測試用例可以看出類型級別的是對項目中的整個類型實施相同的解析/格式化邏輯。 有的同學可能需要在不同的類的字段實施不同的解析/格式化邏輯,如用戶模型類的注冊日期字段只需要如“2012-05-02”格式進行解析/格式化即可,而訂單模型類的下訂單日期字段可能需要如“2012-05-02 20:13:13”格式進行展示。 接下來我們學習一下如何進行字段級別的解析/格式化吧。 #### 7.3.3.2、字段級別的解析/格式化 **一、使用內置的注解進行字段級別的解析/格式化:** **(1、測試模型類準備:** ``` package cn.javass.chapter7.model; public class FormatterModel { @NumberFormat(style=Style.NUMBER, pattern="#,###") private int totalCount; @NumberFormat(style=Style.PERCENT) private double discount; @NumberFormat(style=Style.CURRENCY) private double sumMoney; @DateTimeFormat(iso=ISO.DATE) private Date registerDate; @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") private Date orderDate; //省略getter/setter }? ``` 此處我們使用了Spring字段級別解析/格式化的兩個內置注解: @Number:定義數字相關的解析/格式化元數據(通用樣式、貨幣樣式、百分數樣式),參數如下: style:用于指定樣式類型,包括三種:Style.NUMBER(通用樣式) Style.CURRENCY(貨幣樣式) Style.PERCENT(百分數樣式),默認Style.NUMBER; pattern:自定義樣式,如patter="#,###"; @DateTimeFormat:定義日期相關的解析/格式化元數據,參數如下: pattern:指定解析/格式化字段數據的模式,如”yyyy-MM-dd HH:mm:ss” iso:指定解析/格式化字段數據的ISO模式,包括四種:ISO.NONE(不使用) ?ISO.DATE(yyyy-MM-dd) ISO.TIME(hh:mm:ss.SSSZ) ?ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ),默認ISO.NONE; style:指定用于格式化的樣式模式,默認“SS”,具體使用請參考Joda-Time類庫的org.joda.time.format.DateTimeFormat的forStyle的javadoc; 優先級:?pattern 大于 iso 大于?style。 **(2、測試用例:** ``` @Test public void test() throws SecurityException, NoSuchFieldException { //默認自動注冊對@NumberFormat和@DateTimeFormat的支持 DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); //準備測試模型對象 FormatterModel model = new FormatterModel(); model.setTotalCount(10000); model.setDiscount(0.51); model.setSumMoney(10000.13); model.setRegisterDate(new Date(2012-1900, 4, 1)); model.setOrderDate(new Date(2012-1900, 4, 1, 20, 18, 18)); //獲取類型信息 TypeDescriptor descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("totalCount")); TypeDescriptor stringDescriptor = TypeDescriptor.valueOf(String.class); Assert.assertEquals("10,000", conversionService.convert(model.getTotalCount(), descriptor, stringDescriptor)); Assert.assertEquals(model.getTotalCount(), conversionService.convert("10,000", stringDescriptor, descriptor)); } ``` TypeDescriptor:擁有類型信息的上下文,用于Spring3類型轉換系統獲取類型信息的(可以包含類、字段、方法參數、屬性信息);通過TypeDescriptor,我們就可以獲取(類、字段、方法參數、屬性)的各種信息,如注解類型信息; conversionService.convert(model.getTotalCount(), descriptor, stringDescriptor):將totalCount格式化為字符串類型,此處會根據totalCount字段的注解信息(通過descriptor對象獲取)來進行格式化; conversionService.convert("10,000", stringDescriptor, descriptor):將字符串“10,000”解析為totalCount字段類型,此處會根據totalCount字段的注解信息(通過descriptor對象獲取)來進行解析。 **(3、通過為不同的字段指定不同的注解信息進行字段級別的細粒度數據解析/格式化** ``` descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("registerDate")); Assert.assertEquals("2012-05-01", conversionService.convert(model.getRegisterDate(), descriptor, stringDescriptor)); Assert.assertEquals(model.getRegisterDate(), conversionService.convert("2012-05-01", stringDescriptor, descriptor)); descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("orderDate")); Assert.assertEquals("2012-05-01 20:18:18", conversionService.convert(model.getOrderDate(), descriptor, stringDescriptor)); Assert.assertEquals(model.getOrderDate(), conversionService.convert("2012-05-01 20:18:18", stringDescriptor, descriptor)); ``` 通過如上測試可以看出,我們可以通過字段注解方式實現細粒度的數據解析/格式化控制,但是必須使用TypeDescriptor來指定類型的上下文信息,即編程實現字段的數據解析/格式化比較麻煩。 其他測試用例請參考cn.javass.chapter7.web.controller.support.formatter.InnerFieldFormatterTest的test測試方法。 **二、自定義注解進行字段級別的解析/格式化:** 此處以解析/格式化PhoneNumberModel字段為例。 **(1、定義解析/格式化字段的注解類型:** ``` package cn.javass.chapter7.web.controller.support.formatter; //省略import @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface PhoneNumber { } ``` **(2、實現AnnotationFormatterFactory注解格式化工廠:** ``` package cn.javass.chapter7.web.controller.support.formatter; //省略import public class PhoneNumberFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<PhoneNumber> {//①指定可以解析/格式化的字段注解類型 private final Set<Class<?>> fieldTypes; private final PhoneNumberFormatter formatter; public PhoneNumberFormatAnnotationFormatterFactory() { Set<Class<?>> set = new HashSet<Class<?>>(); set.add(PhoneNumberModel.class); this.fieldTypes = set; this.formatter = new PhoneNumberFormatter();//此處使用之前定義的Formatter實現 } //②指定可以被解析/格式化的字段類型集合 @Override public Set<Class<?>> getFieldTypes() { return fieldTypes; } //③根據注解信息和字段類型獲取解析器 @Override public Parser<?> getParser(PhoneNumber annotation, Class<?> fieldType) { return formatter; } //④根據注解信息和字段類型獲取格式化器 @Override public Printer<?> getPrinter(PhoneNumber annotation, Class<?> fieldType) { return formatter; } }? ``` AnnotationFormatterFactory實現會根據注解信息和字段類型獲取相應的解析器/格式化器。 **(3、修改FormatterModel添加如下代碼:** ``` @PhoneNumber private PhoneNumberModel phoneNumber;? ``` **(4、測試用例** ``` @Test public void test() throws SecurityException, NoSuchFieldException { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();//創建格式化服務 conversionService.addFormatterForFieldAnnotation( new PhoneNumberFormatAnnotationFormatterFactory());//添加自定義的注解格式化工廠 FormatterModel model = new FormatterModel(); TypeDescriptor descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("phoneNumber")); TypeDescriptor stringDescriptor = TypeDescriptor.valueOf(String.class); PhoneNumberModel value = (PhoneNumberModel) conversionService.convert("010-12345678", stringDescriptor, descriptor); //解析字符串"010-12345678"--> PhoneNumberModel model.setPhoneNumber(value); Assert.assertEquals("010-12345678", conversionService.convert(model.getPhoneNumber(), descriptor, stringDescriptor));//格式化PhoneNumberModel-->"010-12345678" } ``` 此處使用DefaultFormattingConversionService的addFormatterForFieldAnnotation注冊自定義的注解格式化工廠PhoneNumberFormatAnnotationFormatterFactory。 到此,編程進行數據的格式化/解析我們就完成了,使用起來還是比較麻煩,接下來我們將其集成到Spring Web MVC環境中。 ### 7.3.4、集成到Spring Web MVC環境 **一、注冊FormattingConversionService實現和自定義格式化轉換器:** ``` <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <!—此處省略之前注冊的自定義類型轉換器--> <property name="formatters"> <list> <bean class="cn.javass.chapter7.web.controller.support.formatter. PhoneNumberFormatAnnotationFormatterFactory"/> </list> </property> </bean> ``` 其他配置和之前學習7.2.2.4一節一樣。 **二、示例:** **(1、模型對象字段的數據解析/格式化:** ``` @RequestMapping(value = "/format1") public String test1(@ModelAttribute("model") FormatterModel formatModel) { return "format/success"; }? ``` ``` totalCount:<spring:bind path="model.totalCount">${status.value}</spring:bind><br/> discount:<spring:bind path="model.discount">${status.value}</spring:bind><br/> sumMoney:<spring:bind path="model.sumMoney">${status.value}</spring:bind><br/> phoneNumber:<spring:bind path="model.phoneNumber">${status.value}</spring:bind><br/> <!-- 如果沒有配置org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor將會報錯 --> phoneNumber:<spring:eval expression="model.phoneNumber"></spring:eval><br/> <br/><br/> <form:form commandName="model"> <form:input path="phoneNumber"/><br/> <form:input path="sumMoney"/> </form:form> ``` 在瀏覽器輸入測試URL: http://localhost:9080/springmvc-chapter7/format1?totalCount=100000&discount=0.51&sumMoney=100000.128&phoneNumber=010-12345678 數據會正確綁定到我們的formatModel,即請求參數能被正確的解析并綁定到我們的命令對象上,而且在JSP頁面也能正確的顯示格式化后的數據(即正確的被格式化顯示)。 **(2、功能處理方法參數級別的數據解析:** ``` @RequestMapping(value = "/format2") public String test2( @PhoneNumber @RequestParam("phoneNumber") PhoneNumberModel phoneNumber, @DateTimeFormat(pattern="yyyy-MM-dd") @RequestParam("date") Date date) { System.out.println(phoneNumber); System.out.println(date); return "format/success2"; }? ``` 此處我們可以直接在功能處理方法的參數上使用格式化注解類型進行注解,Spring Web MVC能根據此注解信息對請求參數進行解析并正確的綁定。 在瀏覽器輸入測試URL: http://localhost:9080/springmvc-chapter7/format2?phoneNumber=010-12345678&date=2012-05-01 數據會正確的綁定到我們的phoneNumber和date上,即請求的參數能被正確的解析并綁定到我們的參數上。 控制器代碼位于cn.javass.chapter7.web.controller.DataFormatTestController中。 如果我們請求參數數據不能被正確解析并綁定或輸入的數據不合法等該怎么處理呢?接下來的一節我們來學習下綁定失敗處理和數據驗證相關知識。??
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看