精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

@ControllerAdvice注解使用及原理探究

開發 前端
@initBinder和@ModelAttribute都是請求過程中的處理,我們知道springMvc通過HandlerApapter定位到具體的方法進行請求處理,因此查看HandlerHaper的實現類,發現RequestMappingHandlerAdapter比較符合我們的目標。

最近在新項目的開發過程中,遇到了個問題,需要將一些異常的業務流程返回給前端,需要提供給前端不同的響應碼,前端再在次基礎上做提示語言的國際化適配。這些異常流程涉及業務層和控制層的各個地方,如果每個地方都寫一些重復代碼顯得很冗余。

然后查詢解決方案時發現了@ControllerAdvice這個注解,可以對業務異常進行統一處理。經過仔細了解后,發現這個注解還有更多的用處,都很實用。

1 ControllerAdvice介紹

@ControllerAdvice一般和三個以下注解一塊使用,起到不同的作用,

  • @ExceptionHandler: 該注解作用于方法上,,可以捕獲到controller中拋出的一些自定義異常,統一進行處理,一般用于進行一些特定的異常處理。
  • @InitBinder:該注解作用于方法上,用于將前端請求的特定類型的參數在到達controller之前進行處理,從而達到轉換請求參數格式的目的。
  • @ModelAttribute:該注解作用于方法和請求參數上,在方法上時設置一個值,可以直接在進入controller后傳入該參數。

2 ControllerAdvice應用場景

2.1@ExceptionHandler統一處理業務異常

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 這里就是對各個層返回的異常進行統一捕獲處理
@ExceptionHandler(value = BusinessException.class)
public ResponseData<Void> bizException(BusinessException e){
        log.error("業務異常記錄",e);
        return ResponseData.error(e.getCode(),e.getMessage());
}
}
//業務異常處代碼示例:
if(CollectionUtil.isNotEmpty(companies)){
// 通過BusinessExceptionEnum枚舉對業務異常進行統一管理
throw new BusinessException(BusinessExceptionEnum.ERROR_10003);
}

需要注意的是,如果這里有多個ExceptionHandler,按照異常類的層次體系,越高層的異常,優先級越低。

2.2@InitBinder做日期格式的統一處理

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 將前端傳入的字符串時間格式轉換為LocalDate時間  
@InitBinder
    protected void initBinder(WebDataBinder binder) {
//將前端傳入的字符串格式時間數據轉為LocalDate格式的數據
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
            }
        });
//將前端傳入的字符串格式時間數據轉為LocalDateTime格式的數據
        binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
            }
        });
//將前端傳入的字符串格式時間數據轉為LocalTim格式的數據
        binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
            }
        });
    }
}
// controller進行參數綁定
public ResponseData<List<WorkCalendarVo>> listWorkCalendar(@RequestParam LocalDate  date){}

2.3 ModelAttribute提前綁定全局user對象

// 這里@ModelAttribute("loginUser")標注的modelAttribute()方法表示會在Controller方法之前將user設置到contoller里的已綁定參數里
    @ModelAttribute("loginUser")
    public User setLoginUser(HttpServletRequest request) {
        return LoginContextUtils.getLoginUser(request);
    }
// 使用
    @PostMapping("/list")
    public ResponseData<IPage<EmployeeVo>> listEmployee(@ModelAttribute("loginUser") User user, @RequestBody EmployeeSearch employeeSearch){
        return ResponseData.success(employeeService.listEmployee(user, employeeSearch));
    }

3 ControllerAdvice作用原理探究

在探究ControllerAdvice如何生效時,不得不提到springMvc繞不過的DispatcherServlet,這個類是SpringMVC統一的入口,所有的請求都通過它,里面的一些初始化方法如下。

public class DispatcherServlet extends FrameworkServlet {
    // ......
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
//請求處理的adapter
        initHandlerAdapters(context);
// 異常響應處理的resolver
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
    // ......
}

3.1@initBinder和@ModelAttribute的作用原理

@initBinder和@ModelAttribute都是請求過程中的處理,我們知道springMvc通過HandlerApapter定位到具體的方法進行請求處理,因此查看HandlerHaper的實現類,發現RequestMappingHandlerAdapter比較符合我們的目標

點進去RequestMappingHandlerAdapter后發現里面的一個方法如下

@Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
// 這里會添加ResponseBody advice beans
        initControllerAdviceCache();

        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }
// 這里找到contollerAdvice注解的類,緩存里面的方法
private void initControllerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }
// 找到@ControllerAdvice注解標注的類
        List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

        List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

        for (ControllerAdviceBean adviceBean : adviceBeans) {
            Class<?> beanType = adviceBean.getBeanType();
            if (beanType == null) {
                throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
            }
// 找到所有ModelAttribute標注的方法進行緩存,就可以使用了
            Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
            if (!attrMethods.isEmpty()) {
                this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
            }
// 找到所有initBinder注解標注的方法進行緩存,就可以使用了
            Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
            if (!binderMethods.isEmpty()) {
                this.initBinderAdviceCache.put(adviceBean, binderMethods);
            }
            if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                requestResponseBodyAdviceBeans.add(adviceBean);
            }
        }

        if (!requestResponseBodyAdviceBeans.isEmpty()) {
            this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
        }
// ......日志處理
    }

3.2@ExceptionHandler注解的作用原理

相同的思路,@ExceptionHandler是響應時的處理,因此需要找到對應的Resolver,進入initHandlerExceptionResolvers(context)方法,

屬性填充后會進行afterPropertiesSet方法,這個方法可以用在一些特殊情況中,也就是某個對象的某個屬性需要經過外界得到,比如說查詢數據庫等方式,這時候可以用到spring的該特性,只需要實現InitializingBean。

@Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBodyAdvice beans
        initExceptionHandlerAdviceCache();

        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

private void initExceptionHandlerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }

        List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
        for (ControllerAdviceBean adviceBean : adviceBeans) {
            Class<?> beanType = adviceBean.getBeanType();
            if (beanType == null) {
                throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
            }
// 這里找到ExceptionHandler注解標注的方法進行緩存,后面就可以使用了
            ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
            if (resolver.hasExceptionMappings()) {
                this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
            }
            if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                this.responseBodyAdvice.add(adviceBean);
            }
        }
// ......日志處理
    }

在啟動spring時debug發現最終也會走到這里對@ExceptionHander注解的方法已經緩存

當Controller拋出異常時,DispatcherServlet通過ExceptionHandlerExceptionResolver來解析異常,而ExceptionHandlerExceptionResolver又通過ExceptionHandlerMethodResolver 來解析異常, ExceptionHandlerMethodResolver 最終解析異常找到適用的@ExceptionHandler標注的方法是這里:

@Nullable
    public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
        Method method = this.exceptionLookupCache.get(exceptionType);
        if (method == null) {
            method = getMappedMethod(exceptionType);
            this.exceptionLookupCache.put(exceptionType, method);
        }
        return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
    }

4 用具體的調用過程,驗證上面的推測

本部分通過對DispatcherServlet的調用過程跟蹤,梳理出ControllerAdvice的作用原理,以@InitBinder主節點生效過程為例。

首選是dispathServlet在初始化過程中,初始化RequestMappingHandlerAdapter過程中打斷點發現,initBinder已經緩存進來了。

然后是dispatcherServlet的調用流程圖,驗證下是initBinder注解是否生效。

DispatcherServlet 通過doService()方法開始調用,主要邏輯包括 設置 request ,通過doDispatch() 進行請求分發處理。

doDispatch() 的主要過程是通過 HandlerMapping 獲取 Handler,再找到用于執行它的 HandlerAdapter,執行 Handler 后得到 ModelAndView ,ModelAndView 是連接“業務邏輯層”與“視圖展示層”的橋梁。

4.1 DispathcerServlet的doDispatch方法

在入口處找到要執行的HandlerAdapter,調用handle方法繼續

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
// 找到執行鏈,根據請求路徑匹配到controller的方法
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
// 找到對應的HandlerAdapter,執行鏈中的handler類型為HandlerMethod的.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = HttpMethod.GET.matches(method);
                if (isGet || HttpMethod.HEAD.matches(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler. 真正進行處理的地方
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            ..........
    }

4.2 RequestmappingHanderApapter對@initBInder注解緩存方法進行處理

找到對應的handlerAdapter后進入invokeHandlerMethod()方法,在這里通過構建WebDataBinderFactory對initBinder注解進行構建,供后續使用,具體邏輯如下。
通過getDataBinderFactory()方法從之前緩存的Map> initBinderAdviceCache中生成binderFactory

@Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
//根據initBinder注解,獲取對應的factory,主要成員是InvocableHandlerMethod,就包括之前緩存的。
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 創建可調用的對象,進行調用邏輯處理
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
// binderFactory設置進invocableMethod,
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);

            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                LogFormatUtils.traceDebug(logger, traceOn -> {
                    String formatted = LogFormatUtils.formatValue(result, !traceOn);
                    return "Resume with async result [" + formatted + "]";
                });
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }
// 繼續進行處理
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }
// 生成WebDataBinderFactory的具體邏輯
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
        Class<?> handlerType = handlerMethod.getBeanType();
        Set<Method> methods = this.initBinderCache.get(handlerType);
        if (methods == null) {
            methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
            this.initBinderCache.put(handlerType, methods);
        }
        List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
        // Global methods first 獲取之前項目啟動緩存的initMethod
        this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
            if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
                Object bean = controllerAdviceBean.resolveBean();
                for (Method method : methodSet) {
                    initBinderMethods.add(createInitBinderMethod(bean, method));
                }
            }
        });
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            initBinderMethods.add(createInitBinderMethod(bean, method));
        }
        return createDataBinderFactory(initBinderMethods);
    }

經過上面的處理,發現initBinder標注的注解方法已經成功緩存進bindFactory。

4.3 繼續調用getMethodArgumentValues進行后續處理

繼續往下跟蹤,進入InvocableHandlerMethod的invokeForRequest方法,里面有getMethodArgumentValues方法,會對請求參數進行處理。
最終使用AbstractNamedValueMethodArgumentResolver的resolveArgument()方法對請求字符串格式數據進行處理

// 請求Controller方法如下    
public ResponseData<IPage<CompanyVo>> listCompany(HttpServletRequest servletRequest, @RequestBody CompanySearch companySearch, @RequestParam LocalDate localDate){
       getLoginUser(servletRequest);
        return ResponseData.success(companyService.listCompany(companySearch));
    }

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
// 得到方法的參數列表
        MethodParameter[] parameters = getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        }

        Object[] args = new Object[parameters.length];
// 循環如處理請求參數
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
            try {
// 真正進行參數處理的地方
                args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            }
            catch (Exception ex) {
                // Leave stack trace for later, exception may actually be resolved and handled...
                if (logger.isDebugEnabled()) {
                    String exMsg = ex.getMessage();
                    if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                        logger.debug(formatArgumentError(parameter, exMsg));
                    }
                }
                throw ex;
            }
        }
        return args;
    }

// 最終會使用AbstractNamedValueMethodArgumentResolver來進行處理
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();
// 得到請求參數名稱為"localdate"
        Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                    "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }
// 獲取請求的locadate的值,此時為字符串格式"yyyy-mm-dd"
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
            }
            else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        }
        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
        }
// 這里就會使用bindFactory進行處理
        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            try {
// 經過這里進行處理,輸入的string類型就會轉為LocalDate了
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            }
            catch (ConversionNotSupportedException ex) {
                throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());
            }
            catch (TypeMismatchException ex) {
                throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());
            }
            // Check for null value after conversion of incoming argument value
            if (arg == null && namedValueInfo.defaultValue == null &&
                    namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
            }
        }

        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

        return arg;
    }

最后附上上面調用過程中一些類的介紹

以上就是ControllerAdivce的全介紹。通過對源碼的學習,加深了對HTTP請求過程的理解。

參考:https://blog.csdn.net/zmm__1377445292/article/details/116158554

作者:京東物流 付鵬嘎

來源:京東云開發者社區 自猿其說Tech

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2010-09-15 15:59:11

CSS hack

2019-01-10 08:24:06

2010-08-27 09:29:40

CSSbehavior

2023-05-05 07:39:04

Spring事務面試

2017-05-18 15:02:36

AndroidGC原理JVM內存回收

2022-10-30 15:00:50

2010-09-16 14:42:44

JVM

2014-04-02 17:10:00

虛擬應用工作原理

2021-10-27 16:52:37

LayoutInfl源碼解析

2014-04-15 15:45:22

Java8Java8教程

2011-12-22 14:27:11

2022-03-17 08:55:43

本地線程變量共享全局變量

2024-05-21 09:01:00

2023-02-07 09:17:19

Java注解原理

2014-04-15 09:53:54

Java8類型注解

2021-12-30 12:30:01

Java注解編譯器

2010-09-29 14:54:34

J2MEHashtable

2024-08-28 08:00:00

2024-12-17 08:28:30

2020-09-04 13:30:43

Java自定義代碼
點贊
收藏

51CTO技術棧公眾號

性一交一乱一区二区洋洋av| 日韩一级特黄| 久久久精品黄色| 国产精品白丝jk喷水视频一区 | 在线观看视频在线观看| 国产鲁鲁视频在线观看特色| 国产九色精品成人porny| 久久亚洲国产成人| 国产免费a级片| 欧美a级在线观看| 国产精品人成在线观看免费| 亚洲一区二区三区xxx视频| 精品深夜av无码一区二区老年| 国内自拍欧美| 色狠狠一区二区| 小说区视频区图片区| 蜜桃av中文字幕| 在线亚洲欧美| www.久久久久久.com| 亚洲精品一区二区18漫画 | 欧美激情二区三区| 久久人人爽人人爽人人片| 播放一区二区| 亚洲一二三区不卡| 精品久久久久久一区| 中文字幕 欧美激情| 欧美视频官网| 一区二区三区视频在线| 久草免费资源站| 美女100%一区| 亚洲一卡二卡三卡四卡| 亚洲mv在线看| 人成免费电影一二三区在线观看| 久久激情五月婷婷| 91产国在线观看动作片喷水| 懂色av粉嫩av浪潮av| 99亚洲乱人伦aⅴ精品| 色噜噜狠狠色综合中国| 黄网站色视频免费观看| 成人午夜影视| 99国产欧美另类久久久精品| 成人国产精品av| 在线免费黄色av| 黄色国产精品| 美女扒开尿口让男人操亚洲视频网站| 欧美狂猛xxxxx乱大交3| 成人性生交大片免费看96| 黄网动漫久久久| 国产精品美女在线播放| 国产黄色免费在线观看| 91亚洲国产成人精品一区二三| 91精品视频免费| 五月激情丁香网| 亚洲久久视频| 久久久久久久久久久人体| 黑人狂躁日本娇小| 不卡中文一二三区| 亚洲男人天堂2019| 老司机av网站| 欧美高清免费| 欧美自拍偷拍一区| 777米奇影视第四色| 国产白浆在线免费观看| 亚洲一区二区不卡免费| 300部国产真实乱| 麻豆视频网站在线观看| 国产精品成人在线观看| 亚洲精品二区| fc2在线中文字幕| 国产色爱av资源综合区| 欧美成熟毛茸茸复古| 特黄aaaaaaaaa真人毛片| 国产成人精品www牛牛影视| 成人免费在线视频网址| 国产精品午夜福利| 韩国av一区二区三区在线观看| 国产欧美一区二区| 91尤物国产福利在线观看| 精品制服美女久久| 国产成人一区三区| 成年人视频免费| 日韩一区欧美二区| 国产精品免费在线免费 | 欧美性生活影院| 污色网站在线观看| 国产精品1区| 日韩一区和二区| 久久无码专区国产精品s| 综合欧美亚洲| 亚洲精品理论电影| 91网站免费视频| 国产一区二区三区四区| 中文字幕亚洲无线码a| 性色国产成人久久久精品| 午夜精品一区二区三区国产 | 欧洲精品一区二区三区在线观看| 国产天堂在线播放| www.成人在线.com| 日韩免费电影一区| 日韩aaaaa| 精品视频免费| 欧美猛少妇色xxxxx| 国产在线观看你懂的| 午夜在线播放视频欧美| 国产精品欧美一区二区| 91成人国产综合久久精品| 国产一区激情在线| 国产欧美综合精品一区二区| 三级视频在线| 综合av第一页| 男人日女人逼逼| 久久99久久久精品欧美| 精品成人一区二区三区四区| 一区二区三区久久久久| 66国产精品| 日产精品99久久久久久| 国产乱码一区二区| 久久综合av免费| 日本丰满少妇黄大片在线观看| 国产99在线| 91精品在线麻豆| 黑丝av在线播放| 四虎成人av| 97视频在线观看网址| 国产永久免费视频| 久久久国产精华| 国产制服91一区二区三区制服| www.色在线| 欧美另类一区二区三区| 捆绑裸体绳奴bdsm亚洲| 亚洲一区二区三区| 日本欧美黄网站| 亚洲国产精品一| 国产精品久久久久永久免费观看 | 日本激情在线观看| 欧美午夜精品久久久久久久| 在线免费看v片| 精品久久影视| 国模吧一区二区三区| 91高潮大合集爽到抽搐| 久久亚区不卡日本| 亚洲一区二区四区| 亚洲风情在线资源| 日韩欧美色综合网站| 一区二区三区在线播放视频| 免费亚洲一区| 国产在线欧美日韩| 色噜噜狠狠狠综合欧洲色8| 欧美性做爰猛烈叫床潮| www.久久国产| 亚洲欧美一区在线| 国产精品日韩在线播放| 免费在线黄色网址| 欧美日韩日本国产| 中文字幕精品久久久| 欧美日韩mv| 成人做爽爽免费视频| 77导航福利在线| 欧美午夜电影网| 最新中文字幕av| 久久夜色精品| 欧美精彩一区二区三区| 极品视频在线| 亚洲精品福利在线观看| 日韩特黄一级片| 成人视屏免费看| 日本男女交配视频| 日韩一区二区三区精品| 欧美美女操人视频| 中文字幕一区二区三区波野结| 国产女主播在线一区二区| 久久人妻精品白浆国产| 亚洲国产国产| 国产不卡av在线免费观看| 青草久久伊人| 欧美亚男人的天堂| eeuss中文字幕| 狠狠色2019综合网| 久久久久久久香蕉| 欧美大胆a级| 欧美亚洲日本网站| 国产精品麻豆一区二区三区| 欧美伊人久久大香线蕉综合69| 色www亚洲国产阿娇yao| 另类欧美日韩国产在线| 正在播放亚洲| 成人午夜网址| 欧美一级电影免费在线观看| 国产二区在线播放| 欧美理论片在线| 在线免费日韩av| 9色porny自拍视频一区二区| 欧美国产亚洲一区| 亚洲动漫在线观看| 成人免费高清完整版在线观看| 69成人在线| 国产丝袜一区二区| 一本色道久久综合熟妇| 一区二区三区在线观看欧美| 亚洲精品乱码久久久久久不卡| 蜜桃一区二区三区四区| www.激情网| 国内精品伊人久久久| 国产精品久久一区| 亚洲小说区图片| 亚洲欧美另类国产| 国产女人高潮的av毛片| 精品女厕一区二区三区| www.黄色com| 91网站在线播放| 爱爱爱爱免费视频| 狠狠入ady亚洲精品| 欧美亚洲精品日韩| 日韩成人在线看| 国产极品jizzhd欧美| 最新黄网在线观看| 亚洲天堂精品在线| 精品久久人妻av中文字幕| 欧美视频13p| 香蕉成人在线视频| www.日韩av| 一级黄色片国产| 久久久噜噜噜| 日韩av新片网| 日本不卡二三区| 精品日韩美女| 日韩精品一区二区三区中文| 国产xxx69麻豆国语对白| 色呦呦在线视频| 色婷婷综合久久久久| 四虎在线视频| 精品乱人伦小说| 国产精品羞羞答答在线| 91黄视频在线| 精品成人免费视频| 成人欧美一区二区三区白人 | 2018天天弄| 欧美国产日产图区| 久久福利小视频| 国产福利一区二区| 日韩在线不卡一区| 久久综合伊人| 激情综合在线观看| 亚洲网站视频| 热久久最新地址| 午夜久久免费观看| 伊人婷婷久久| 成人午夜国产| 日本欧美精品久久久| 亚洲美女15p| 久久国产精品高清| 草草视频在线一区二区| 亚洲最大福利网| 国产精品视频一区视频二区| 国产精品美女久久久免费 | 一区二区三区午夜视频| 中国成人在线视频| 久久综合影院| 精品久久中出| 日韩动漫一区| 久久久久久一区| 日韩中出av| 鲁片一区二区三区| 亚洲精品推荐| 日本一区网站| jizzjizz欧美69巨大| 日韩精品大片| 日本黄色精品| 小说区视频区图片区| 中文字幕亚洲综合久久五月天色无吗''| 一区二区三区视频在线播放| 五月开心六月丁香综合色啪| 丝袜美腿玉足3d专区一区| av中文一区| 一区二区国产日产| 伊人情人综合网| 日韩在线观看a| 国产亚洲精品v| 另类小说第一页| 久久成人精品无人区| 亚洲国产日韩欧美在线观看| 极品美女销魂一区二区三区| 人妻精品久久久久中文字幕69| 成人在线综合网| 成人免费av片| 99久久久精品| 亚洲熟女乱综合一区二区三区| 久久午夜国产精品| 殴美一级黄色片| 亚洲欧洲综合另类在线| 午夜国产福利一区二区| 亚洲成人午夜电影| 日本中文字幕第一页| 欧美日韩午夜在线| 国产哺乳奶水91在线播放| 亚洲第一页在线| 免费黄色在线视频网站| 久久天天躁夜夜躁狠狠躁2022| 日韩激情美女| 国产v综合ⅴ日韩v欧美大片| av国产精品| 久久av一区二区| 激情五月色综合国产精品| 最新视频 - x88av| 亚洲一区欧美激情| 中文字幕久久av| 91小视频免费看| 99鲁鲁精品一区二区三区| 亚洲不卡av一区二区三区| 久久久久久无码精品大片| 制服丝袜av成人在线看| 亚洲欧美日本在线观看| 日韩在线www| av电影免费在线看| 国产精品视频999| 国产精品调教视频| 神马影院我不卡| 激情欧美日韩一区| 亚洲制服中文字幕| 国产精品嫩草99a| 中文字幕xxxx| 精品视频久久久久久| 欧美卡一卡二| 99在线免费观看视频| 夜间精品视频| 女同激情久久av久久| 国产欧美一区二区精品性| 国产在线观看黄色| 亚洲精品国精品久久99热一| 久久久123| 翡翠波斯猫1977年美国| 一区二区三区在线| 91亚洲一区二区| 亚洲欧美另类综合偷拍| 一级淫片免费看| xxx成人少妇69| 欧美日韩尤物久久| 天天综合色天天综合色hd| 日日夜夜免费精品| 蜜桃av免费看| 91国在线观看| av在线播放网| 国产精品老女人视频| 四虎8848精品成人免费网站| 亚洲欧美日韩精品一区| 中文字幕一区二区三区四区不卡| 亚洲无码精品在线播放| 久久精品视频免费播放| 成人乱码手机视频| 日韩在线视频在线| 成人精品视频一区二区三区尤物| 精品一区二区三区人妻| 日韩免费一区二区三区在线播放| 欧美xxxx黑人又粗又长| 国产亚洲精品自在久久| 国产亚洲精品v| 久久久久亚洲av无码a片| 欧美手机在线视频| 精产国品自在线www| 4444kk亚洲人成电影在线| 在线欧美三区| 91精彩刺激对白露脸偷拍| 欧美亚洲国产一卡| 国产精品刘玥久久一区| 国产富婆一区二区三区 | 久久久久久久久久国产精品| 国产在线播放精品| 欧美一级黄色影院| 中文字幕一区二区三区不卡| www.精品视频| 欧美诱惑福利视频| 首页国产精品| 亚洲激情 欧美| 91成人免费在线| 中文在线字幕免费观看| 精品日韩美女| 精品一区在线看| 日本一区二区不卡在线| 亚洲欧洲午夜一线一品| 91麻豆精品国产91久久久更新资源速度超快 | 亚洲欧美激情另类| 欧美中文在线字幕| 国产国产精品| 欧美性xxxx图片| 日韩一级完整毛片| 天堂8中文在线最新版在线| 亚洲欧洲精品一区| 成人国产精品免费观看| 成人免费一级片| 欧美日韩不卡合集视频| 国产成人高清| 男人女人拔萝卜视频| 色999日韩国产欧美一区二区| 国产成人高清精品| 欧美日韩亚洲免费| 国产成人超碰人人澡人人澡| 国产一级片av| 久久久久久久av| 国产精品伦理久久久久久| 偷拍女澡堂一区二区三区|