<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文讀懂Spring中的AOP機制

          共 21559字,需瀏覽 44分鐘

           ·

          2021-03-31 12:27

          點擊上方老周聊架構關注我



          一、前言

          上一篇我們說了注解的底層原理,請戳:一文讀懂注解的底層原理

          這一篇我們來說一下 Spring 中的 AOP 機制,為啥說完注解的原理然后又要說 AOP 機制呢?

          不妨看一下前面的這一篇,請戳:一文讀懂Annotation,這一篇我們實現(xiàn)了如何自定義注解的案例。

          1、標記日志打印的自定義注解

          @Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface PrintLog {}


          2、定義一個切面,在切面中對使用了 @PrintLog 自定義注解的方法進行環(huán)繞增強通知

          @Component@Aspect@Slf4jpublic class PrintLogAspect {    @Around(value = "@annotation(com.riemann.core.annotation.PrintLog)")    public Object handlerPrintLog(ProceedingJoinPoint joinPoint) throws Throwable {        String clazzName = joinPoint.getSignature().getDeclaringTypeName();        String methodName = joinPoint.getSignature().getName();        Object[] args = joinPoint.getArgs();
          Map<String, Object> nameAndArgs = getFieldsName(this.getClass(), clazzName, methodName, args); log.info("Enter class[{}] method[{}] params[{}]", clazzName, methodName, nameAndArgs);
          Object object = null; try { object = joinPoint.proceed(); } catch (Throwable throwable) { log.error("Process class[{}] method[{}] error", clazzName, methodName, throwable); } log.info("End class[{}] method[{}]", clazzName, methodName); return object; }
          private Map<String, Object> getFieldsName(Class clazz, String clazzName, String methodName, Object[] args) throws NotFoundException { Map<String, Object > map = new HashMap<>(); ClassPool pool = ClassPool.getDefault(); ClassClassPath classPath = new ClassClassPath(clazz); pool.insertClassPath(classPath);
          CtClass cc = pool.get(clazzName); CtMethod cm = cc.getDeclaredMethod(methodName); MethodInfo methodInfo = cm.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); if (attr == null) { // exception } int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1; for (int i = 0; i < cm.getParameterTypes().length; i++) { map.put( attr.variableName(i + pos), args[i]); }
          return map; }}


          3、最后,在 Controller 中的方法上使用 @PrintLog 自定義注解即可;當某個方法上使用了自定義注解,那么這個方法就相當于一個切點,那么就會對這個方法做環(huán)繞(方法執(zhí)行前和方法執(zhí)行后)增強處理。

          @RestControllerpublic class Controller {  @PrintLog  @GetMapping(value = "/user/findUserNameById/{id}", produces = "application/json;charset=utf-8")  public String findUserNameById(@PathVariable("id") int id) {      // 模擬根據(jù)id查詢用戶名      String userName = "公眾號【老周聊架構】";      return userName;  }}


          了解完自定義注解的底層機制以后,我們來想一下,為啥在 Controller 類里的方法上添加 @PrintLog 就可以做到在這個方法前后打印相應的日志呢?這就是我們熟悉的 AOP 底層幫我們做完了這個事情。很多人知道是 AOP 完成的,但對于怎么完成的很多人還是不是很清楚。本文就來分析分析它背后的機制。

          我們來看下 PrintLogAspect 這個增強類,它有 @Component、@Aspect、@Around 幾個核心注解。

          下面我們就可以來想一想,這幾個核心注解是如何被 Spring 讀取的,Spring 又是如何應用這些注解生成代理類,又是如何讓它起到增強的作用。

          二、Spring中的AOP機制

          這里用一張時序圖說一下整體機制

          spring 在整個 Bean 初始化完成后,會執(zhí)行后置處理器方法,調(diào)用各個 BeanPostProcessor,在各個 BeanPostProcessor 里,有一個 AnnotationAwareAspectJAutoProxyCreator,spring 會在該類的 postProcessBeforeInitialization 里進行 Advisor 的初始化。

          可以這樣理解,spring 在創(chuàng)建一個類之前,會看下有沒有配置 AOP,如果有的話,會把配置給轉(zhuǎn)換成一個個 advisor,然后緩存起來(這樣后面需要生成代理類時候,就可以直接使用了)。

          findCandidateAdvisors 的方式有兩種,一種是上圖第 7 步的 findAdvisorBeans 還有一種是第 8 步的 buildAspectJAdvisors。

          1、findAdvisorBeans 方式

          public List<Advisor> findAdvisorBeans() {    String[] advisorNames = this.cachedAdvisorBeanNames;    if (advisorNames == null) {        advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);        this.cachedAdvisorBeanNames = advisorNames;    }
          if (advisorNames.length == 0) { return new ArrayList(); } else { List<Advisor> advisors = new ArrayList(); String[] var3 = advisorNames; int var4 = advisorNames.length;
          for(int var5 = 0; var5 < var4; ++var5) { String name = var3[var5]; if (this.isEligibleBean(name)) { if (this.beanFactory.isCurrentlyInCreation(name)) { if (logger.isTraceEnabled()) { logger.trace("Skipping currently created advisor '" + name + "'"); } } else { try { // 核心方法 advisors.add(this.beanFactory.getBean(name, Advisor.class)); } catch (BeanCreationException var11) { Throwable rootCause = var11.getMostSpecificCause(); if (rootCause instanceof BeanCurrentlyInCreationException) { BeanCreationException bce = (BeanCreationException)rootCause; String bceBeanName = bce.getBeanName(); if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) { if (logger.isTraceEnabled()) { logger.trace("Skipping advisor '" + name + "' with dependency on currently created bean: " + var11.getMessage()); } continue; } }
          throw var11; } } } }
          return advisors; }}


          其實去掉那些讀緩存的代碼,就一句話:

          advisors.add(this.beanFactory.getBean(name, Advisor.class));

          找到實現(xiàn)了 Advisor 接口的類,并返回。

          2、buildAspectJAdvisors 方式

          public List<Advisor> buildAspectJAdvisors() {    List<String> aspectNames = this.aspectBeanNames;    if (aspectNames == null) {        synchronized(this) {            aspectNames = this.aspectBeanNames;            if (aspectNames == null) {                List<Advisor> advisors = new ArrayList();                List<String> aspectNames = new ArrayList();                // 找到所有的類(因為是Object所以基本上就是所有被spring管理的類)                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);                String[] var18 = beanNames;                int var19 = beanNames.length;
          for(int var7 = 0; var7 < var19; ++var7) { String beanName = var18[var7]; // 是否是Aspect(比如含有@Aspect注解) if (this.isEligibleBean(beanName)) { Class<?> beanType = this.beanFactory.getType(beanName); if (beanType != null && this.advisorFactory.isAspect(beanType)) { aspectNames.add(beanName); AspectMetadata amd = new AspectMetadata(beanType, beanName); if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); // 生成Advisor List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory); if (this.beanFactory.isSingleton(beanName)) { this.advisorsCache.put(beanName, classAdvisors); } else { this.aspectFactoryCache.put(beanName, factory); }
          advisors.addAll(classAdvisors); } else { if (this.beanFactory.isSingleton(beanName)) { throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton"); }
          MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName); this.aspectFactoryCache.put(beanName, factory); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } } }
          this.aspectBeanNames = aspectNames; return advisors; } } }
          if (aspectNames.isEmpty()) { return Collections.emptyList(); } else { List<Advisor> advisors = new ArrayList(); Iterator var3 = aspectNames.iterator();
          while(var3.hasNext()) { String aspectName = (String)var3.next(); List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName); if (cachedAdvisors != null) { advisors.addAll(cachedAdvisors); } else { MetadataAwareAspectInstanceFactory factory = (MetadataAwareAspectInstanceFactory)this.aspectFactoryCache.get(aspectName); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } }
          return advisors; }}


          我們的 PrintLogAspect 例子里,并沒有實現(xiàn)任何接口,只是使用了一個 @Aspect 注解。因此使用 buildAspectJAdvisors 方式,spring 會通過我們的 AspectJ 注解(比如@Around、@Pointcut、@Before、@After) 動態(tài)的生成各個 Advisor。

          小結如下:

          • 找到所有被 spring 管理的類(父類是 Object 的類)

          • 如果類含有 @Aspect 注解,調(diào)用 advisorFactory.getAdvisors 方法生成對應的 advisor

          • 返回advisors


          我們繼續(xù)來看下最核心的,Advisor 的創(chuàng)建。

          3、Advisor 的創(chuàng)建

          org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisors
          public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {    Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();    String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();    this.validate(aspectClass);    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);    List<Advisor> advisors = new ArrayList();    Iterator var6 = this.getAdvisorMethods(aspectClass).iterator();    // 遍歷所有沒有 @Pointcut 注解的方法    while(var6.hasNext()) {        Method method = (Method)var6.next();        Advisor advisor = this.getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);        if (advisor != null) {            advisors.add(advisor);        }    }
          if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { Advisor instantiationAdvisor = new ReflectiveAspectJAdvisorFactory.SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory); advisors.add(0, instantiationAdvisor); }
          Field[] var12 = aspectClass.getDeclaredFields(); int var13 = var12.length;
          for(int var14 = 0; var14 < var13; ++var14) { Field field = var12[var14]; Advisor advisor = this.getDeclareParentsAdvisor(field); if (advisor != null) { advisors.add(advisor); } }
          return advisors;}


          最核心的,就是遍歷所有沒有 Pointcut 注解的方法,調(diào)用 getAdvisor 生成對應的 Advisor。

          @Nullablepublic Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {    this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());    AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());    return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);}


          也就是,生成的 Advisor 的實現(xiàn)類,其實是 InstantiationModelAwarePointcutAdvisorImpl。

          4、不同類型的通知

          使用過 AOP 的都知道,不同的注解,比如 @Before、@After、@Around 都是不一樣的。InstantiationModelAwarePointcutAdvisorImpl 這個類,實際上,是對底層 Advisor 的包裝,它記錄了所對應 @AspectJ 的類、配置的方法、對應的切入點、以及最重要的通知,這個通知會在 InstantiationModelAwarePointcutAdvisorImpl 的構造函數(shù)中被初始化。

          private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {    Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut, this.aspectInstanceFactory, this.declarationOrder, this.aspectName);    return advice != null ? advice : EMPTY_ADVICE;}

          org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvice

          public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();    this.validate(candidateAspectClass);    AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);    if (aspectJAnnotation == null) {        return null;    } else if (!this.isAspect(candidateAspectClass)) {        throw new AopConfigException("Advice must be declared inside an aspect type: Offending method '" + candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]");    } else {        if (this.logger.isDebugEnabled()) {            this.logger.debug("Found AspectJ method: " + candidateAdviceMethod);        }
          Object springAdvice; switch(aspectJAnnotation.getAnnotationType()) { case AtPointcut: if (this.logger.isDebugEnabled()) { this.logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); }
          return null; case AtAround: springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; case AtBefore: springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; case AtAfter: springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; case AtAfterReturning: springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); AfterReturning afterReturningAnnotation = (AfterReturning)aspectJAnnotation.getAnnotation(); if (StringUtils.hasText(afterReturningAnnotation.returning())) { ((AbstractAspectJAdvice)springAdvice).setReturningName(afterReturningAnnotation.returning()); } break; case AtAfterThrowing: springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); AfterThrowing afterThrowingAnnotation = (AfterThrowing)aspectJAnnotation.getAnnotation(); if (StringUtils.hasText(afterThrowingAnnotation.throwing())) { ((AbstractAspectJAdvice)springAdvice).setThrowingName(afterThrowingAnnotation.throwing()); } break; default: throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod); }
          ((AbstractAspectJAdvice)springAdvice).setAspectName(aspectName); ((AbstractAspectJAdvice)springAdvice).setDeclarationOrder(declarationOrder); String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod); if (argNames != null) { ((AbstractAspectJAdvice)springAdvice).setArgumentNamesFromStringArray(argNames); }
          ((AbstractAspectJAdvice)springAdvice).calculateArgumentBindings(); return (Advice)springAdvice; }}

          spring 會根據(jù)不同的注解的類型,生成對應的 Advice。

          spring 會在真正創(chuàng)建一個類之前,根據(jù)我們帶有 @Aspect 類的配置生成對應的 Advise 對象,這些對象會被緩存起來。在這之后,就是在 spring 創(chuàng)建完 bean 后,根據(jù)這個 bean 生成對應的代理對象,并替換掉(也就是說,實際調(diào)用方法時候調(diào)用的對象變?yōu)檫@個生成的代理對象) 代理對象的創(chuàng)建,代理對象的創(chuàng)建我們之前分析過了,下面補充一點生成代理的方法。

          5、代理方法

          雖然之前分析過,但這里老周還是提一下代理對象的創(chuàng)建。

          AopProxy 接口類提供了 getProxy 方法來獲取代理對象,其中有三個實現(xiàn)如下圖。

          public interface AopProxy {    Object getProxy();
          Object getProxy(@Nullable ClassLoader var1);}

          這里以 JDK 動態(tài)代理來分析

          public Object getProxy(@Nullable ClassLoader classLoader) {    if (logger.isTraceEnabled()) {        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());    }
          Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); this.findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);}

          看到 Proxy.newProxyInstance 就非常熟悉了,JDK 的動態(tài)代理。

          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    Object oldProxy = null;    boolean setProxyContext = false;    TargetSource targetSource = this.advised.targetSource;    Object target = null;
          Object retVal; try { // equals 方法 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { Boolean var18 = this.equals(args[0]); return var18; } // hashCode方法 if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { Integer var17 = this.hashCode(); return var17; } // 如果是 DecoratingProxy類 if (method.getDeclaringClass() == DecoratingProxy.class) { Class var16 = AopProxyUtils.ultimateTargetClass(this.advised); return var16; } // 實現(xiàn)了Advised接口 if (this.advised.opaque || !method.getDeclaringClass().isInterface() || !method.getDeclaringClass().isAssignableFrom(Advised.class)) { if (this.advised.exposeProxy) { // ThreadLocal里記錄下當前被代理的對象 oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; }
          target = targetSource.getTarget(); Class<?> targetClass = target != null ? target.getClass() : null; // 核心方法,獲取當前方法的攔截器 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // 調(diào)用這些攔截器及方法 retVal = invocation.proceed(); }
          Class<?> returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { retVal = proxy; } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); }
          Object var12 = retVal; return var12; }
          retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } finally { if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); }
          if (setProxyContext) { AopContext.setCurrentProxy(oldProxy); }
          }
          return retVal;}

          小結如下:

          • hashCode、equals方法單獨處理

          • 根據(jù)當前方法等,生成所需的方法攔截器

          • 調(diào)用方法及攔截器


          6、ReflectiveMethodInvocation.proceed()

          public Object proceed() throws Throwable {    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {        return this.invokeJoinpoint(); // ①    } else {        // ②        Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {            InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;            Class<?> targetClass = this.targetClass != null ? this.targetClass : this.method.getDeclaringClass();            return dm.methodMatcher.matches(this.method, targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();        } else {             // ③            return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);        }    }}


          6.1 代碼①

          這個地方的代碼就是目標對象的實際執(zhí)行的地方,也就是 findUserNameById 的實際執(zhí)行的調(diào)用的地方。

          6.2 代碼②

          這一塊是一個遞歸調(diào)用

          6.3 代碼③

          這一塊就是我們的 @Before、@Around、@After 等注解注釋的方法的執(zhí)行調(diào)用的地方,這里 @Before、@Around、@After 等注解的方法都會被封裝到不同的 MethodInterceptor 子類對象中去,也就是說 MethodInterceptor 子類對象里面會記錄這些注解對應的方法的元數(shù)據(jù)信息,當調(diào)用 MethodInterceptor#invoke 的時候會根據(jù)這些元數(shù)據(jù)信息通過反射的方式調(diào)用實際對應的方法,也就是我們上面創(chuàng)建的 PrintLogAspect 這個類的被 @Around 標注的方法。

          小結如下:

          • 會根據(jù)我們之前生成的各個 Advisor 對應的切入點,判斷下當前的方法是否滿足該切入點。如果滿足,將其適配為 MethodInterceptor 接口并返回。

          • 核心調(diào)用邏輯,就是取出一個個攔截器,先判斷下方法是否滿足攔截器條件,如果滿足就調(diào)用。


          三、總結

          • spring 在創(chuàng)建一個類之前,會看下有沒有配置 AOP(可能是xml、可能是注解),如果有的話,會把配置給轉(zhuǎn)換成一個個 advisor,然后緩存起來(這樣后面需要生成代理類時候,就可以直接使用了)。

          • 如果有繼續(xù)看它的 PointCut 對應的規(guī)則,只要在創(chuàng)建 bean 的時候符合這個 PointCut 規(guī)則的,就用動態(tài)代理(JDK Proxy、CGLib)的方式創(chuàng)建代理對象作為 bean 放到容器中。

          • 當我們從 bean 容器中獲取代理對象 bean 并調(diào)用它的方法的時候,因為這個bean是通過代理的方式創(chuàng)建的,所以必然會走org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept() 方法,而這個方法也必然會執(zhí)行org.springframework.aop.framework.ReflectiveMethodInvocation#proceed() 這個方法,而這個方法就會根據(jù)上面說的執(zhí)行過程依次執(zhí)行不同的 MethodInterceptor 子類對象的 invoke() 方法,這個方法會根據(jù)元數(shù)據(jù)信息通過反射的方式調(diào)用代理對象對應的真正的對象的方法,例如我上面創(chuàng)建的 PrintLogAspect 這個類的被 @Around 標注的方法。




          歡迎大家關注我的公眾號【老周聊架構】,Java后端主流技術棧的原理、源碼分析、架構以及各種互聯(lián)網(wǎng)高并發(fā)、高性能、高可用的解決方案。

          喜歡的話,點贊、再看、分享三連。

          點個在看你最好看



          瀏覽 34
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲精品视频区 | 手机看片亚洲 | 日韩美毛片三级片视频 | 高清精品热线视频 | 成人精品水蜜桃 |