<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>

          Mybatis 使用的 9 種設(shè)計模式,真是太有用了

          共 3201字,需瀏覽 7分鐘

           ·

          2020-09-28 01:19

          關(guān)注泥瓦匠”,回復“1024”,領(lǐng)取精選技術(shù)資料



          來源:螞蟻學Python crazyant.net/2022.html


          • 1、Builder模式
          • 2、工廠模式
          • 3、單例模式
          • 4、代理模式
          • 5、組合模式
          • 6、模板方法模式
          • 7、適配器模式
          • 8、裝飾者模式
          • 9、迭代器模式

          雖然我們都知道有26個設(shè)計模式,但是大多停留在概念層面,真實開發(fā)中很少遇到,Mybatis源碼中使用了大量的設(shè)計模式,閱讀源碼并觀察設(shè)計模式在其中的應(yīng)用,能夠更深入的理解設(shè)計模式。

          Mybatis至少遇到了以下的設(shè)計模式的使用:

          1. Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
          2. 工廠模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
          3. 單例模式,例如ErrorContext和LogFactory;
          4. 代理模式,Mybatis實現(xiàn)的核心,比如MapperProxy、ConnectionLogger,用的jdk的動態(tài)代理;還有executor.loader包使用了cglib或者javassist達到延遲加載的效果;
          5. 組合模式,例如SqlNode和各個子類ChooseSqlNode等;
          6. 模板方法模式,例如BaseExecutor和SimpleExecutor,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler;
          7. 適配器模式,例如Log的Mybatis接口和它對jdbc、log4j等各種日志框架的適配實現(xiàn);
          8. 裝飾者模式,例如Cache包中的cache.decorators子包中等各個裝飾者的實現(xiàn);
          9. 迭代器模式,例如迭代器模式PropertyTokenizer;

          接下來挨個模式進行解讀,先介紹模式自身的知識,然后解讀在Mybatis中怎樣應(yīng)用了該模式。

          1、Builder模式

          Builder模式的定義是“將一個復雜對象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。”,它屬于創(chuàng)建類模式,一般來說,如果一個對象的構(gòu)建比較復雜,超出了構(gòu)造函數(shù)所能包含的范圍,就可以使用工廠模式和Builder模式,相對于工廠模式會產(chǎn)出一個完整的產(chǎn)品,Builder應(yīng)用于更加復雜的對象的構(gòu)建,甚至只會構(gòu)建產(chǎn)品的一個部分。

          在Mybatis環(huán)境的初始化過程中,SqlSessionFactoryBuilder會調(diào)用XMLConfigBuilder讀取所有的MybatisMapConfig.xml和所有的*Mapper.xml文件,構(gòu)建Mybatis運行的核心對象Configuration對象,然后將該Configuration對象作為參數(shù)構(gòu)建一個SqlSessionFactory對象。

          其中XMLConfigBuilder在構(gòu)建Configuration對象時,也會調(diào)用XMLMapperBuilder用于讀取*Mapper文件,而XMLMapperBuilder會使用XMLStatementBuilder來讀取和build所有的SQL語句。

          在這個過程中,有一個相似的特點,就是這些Builder會讀取文件或者配置,然后做大量的XpathParser解析、配置或語法的解析、反射生成對象、存入結(jié)果緩存等步驟,這么多的工作都不是一個構(gòu)造函數(shù)所能包括的,因此大量采用了Builder模式來解決。

          對于builder的具體類,方法都大都用build*開頭,比如SqlSessionFactoryBuilder為例,它包含以下方法:

          即根據(jù)不同的輸入?yún)?shù)來構(gòu)建SqlSessionFactory這個工廠對象。

          2、工廠模式

          在Mybatis中比如SqlSessionFactory使用的是工廠模式,該工廠沒有那么復雜的邏輯,是一個簡單工廠模式。

          簡單工廠模式(Simple Factory Pattern):又稱為靜態(tài)工廠方法(Static Factory Method)模式,它屬于類創(chuàng)建型模式。在簡單工廠模式中,可以根據(jù)參數(shù)的不同返回不同類的實例。簡單工廠模式專門定義一個類來負責創(chuàng)建其他類的實例,被創(chuàng)建的實例通常都具有共同的父類。

          SqlSession可以認為是一個Mybatis工作的核心的接口,通過這個接口可以執(zhí)行執(zhí)行SQL語句、獲取Mappers、管理事務(wù)。類似于連接MySQL的Connection對象。

          可以看到,該Factory的openSession方法重載了很多個,分別支持autoCommit、Executor、Transaction等參數(shù)的輸入,來構(gòu)建核心的SqlSession對象。

          在DefaultSqlSessionFactory的默認工廠實現(xiàn)里,有一個方法可以看出工廠怎么產(chǎn)出一個產(chǎn)品:

          	private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
          boolean autoCommit)
          {
          Transaction tx = null;
          try {
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          final Executor executor = configuration.newExecutor(tx, execType);
          return new DefaultSqlSession(configuration, executor, autoCommit);
          } catch (Exception e) {
          closeTransaction(tx); // may have fetched a connection so lets call
          // close()
          throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
          } finally {
          ErrorContext.instance().reset();
          }
          }

          這是一個openSession調(diào)用的底層方法,該方法先從configuration讀取對應(yīng)的環(huán)境配置,然后初始化TransactionFactory獲得一個Transaction對象,然后通過Transaction獲取一個Executor對象,最后通過configuration、Executor、是否autoCommit三個參數(shù)構(gòu)建了SqlSession。

          在這里其實也可以看到端倪,SqlSession的執(zhí)行,其實是委托給對應(yīng)的Executor來進行的。

          而對于LogFactory,它的實現(xiàn)代碼:

          public final class LogFactory {
          private static Constructor logConstructor;

          private LogFactory() {
          // disable construction
          }

          public static Log getLog(Class aClass) {
          return getLog(aClass.getName());
          }

          這里有個特別的地方,是Log變量的的類型是Constructorextends Log>,也就是說該工廠生產(chǎn)的不只是一個產(chǎn)品,而是具有Log公共接口的一系列產(chǎn)品,比如Log4jImpl、Slf4jImpl等很多具體的Log。

          3、單例模式

          單例模式(Singleton Pattern):單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例,這個類稱為單例類,它提供全局訪問的方法。

          單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創(chuàng)建這個實例;三是它必須自行向整個系統(tǒng)提供這個實例。單例模式是一種對象創(chuàng)建型模式。單例模式又名單件模式或單態(tài)模式。

          在Mybatis中有兩個地方用到單例模式,ErrorContext和LogFactory,其中ErrorContext是用在每個線程范圍內(nèi)的單例,用于記錄該線程的執(zhí)行環(huán)境錯誤信息,而LogFactory則是提供給整個Mybatis使用的日志工廠,用于獲得針對項目配置好的日志對象。

          ErrorContext的單例實現(xiàn)代碼:

          public class ErrorContext {

          private static final ThreadLocal LOCAL = new ThreadLocal();

          private ErrorContext() {
          }

          public static ErrorContext instance() {
          ErrorContext context = LOCAL.get();
          if (context == null) {
          context = new ErrorContext();
          LOCAL.set(context);
          }
          return context;
          }

          構(gòu)造函數(shù)是private修飾,具有一個static的局部instance變量和一個獲取instance變量的方法,在獲取實例的方法中,先判斷是否為空如果是的話就先創(chuàng)建,然后返回構(gòu)造好的對象。

          只是這里有個有趣的地方是,LOCAL的靜態(tài)實例變量使用了ThreadLocal修飾,也就是說它屬于每個線程各自的數(shù)據(jù),而在instance()方法中,先獲取本線程的該實例,如果沒有就創(chuàng)建該線程獨有的ErrorContext。

          4、代理模式

          代理模式可以認為是Mybatis的核心使用的模式,正是由于這個模式,我們只需要編寫Mapper.java接口,不需要實現(xiàn),由Mybatis后臺幫我們完成具體SQL的執(zhí)行。

          代理模式(Proxy Pattern) :給某一個對象提供一個代 理,并由代理對象控制對原對象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一種對象結(jié)構(gòu)型模式。

          代理模式包含如下角色:

          • Subject: 抽象主題角色
          • Proxy: 代理主題角色
          • RealSubject: 真實主題角色

          這里有兩個步驟,第一個是提前創(chuàng)建一個Proxy,第二個是使用的時候會自動請求Proxy,然后由Proxy來執(zhí)行具體事務(wù);

          當我們使用Configuration的getMapper方法時,會調(diào)用mapperRegistry.getMapper方法,而該方法又會調(diào)用mapperProxyFactory.newInstance(sqlSession)來生成一個具體的代理:

          /**
          * @author Lasse Voss
          */

          public class MapperProxyFactory<T> {

          private final Class mapperInterface;
          private final Map methodCache = new ConcurrentHashMap();

          public MapperProxyFactory(Class mapperInterface) {
          this.mapperInterface = mapperInterface;
          }

          public Class getMapperInterface() {
          return mapperInterface;
          }

          public Map getMethodCache() {
          return methodCache;
          }

          @SuppressWarnings("unchecked")
          protected T newInstance(MapperProxy mapperProxy) {
          return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface },
          mapperProxy);
          }

          public T newInstance(SqlSession sqlSession) {
          final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
          return newInstance(mapperProxy);
          }

          }

          在這里,先通過T newInstance(SqlSession sqlSession)方法會得到一個MapperProxy對象,然后調(diào)用T newInstance(MapperProxymapperProxy)生成代理對象然后返回。

          而查看MapperProxy的代碼,可以看到如下內(nèi)容:

          public class MapperProxy<T> implements InvocationHandler, Serializable {

          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          try {
          if (Object.class.equals(method.getDeclaringClass())) {
          return method.invoke(this, args);
          } else if (isDefaultMethod(method)) {
          return invokeDefaultMethod(proxy, method, args);
          }
          } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
          }
          final MapperMethod mapperMethod = cachedMapperMethod(method);
          return mapperMethod.execute(sqlSession, args);
          }

          非常典型的,該MapperProxy類實現(xiàn)了InvocationHandler接口,并且實現(xiàn)了該接口的invoke方法。

          通過這種方式,我們只需要編寫Mapper.java接口類,當真正執(zhí)行一個Mapper接口的時候,就會轉(zhuǎn)發(fā)給MapperProxy.invoke方法,而該方法則會調(diào)用后續(xù)的sqlSession.cud>executor.execute>prepareStatement等一系列方法,完成SQL的執(zhí)行和返回。

          5、組合模式

          組合模式組合多個對象形成樹形結(jié)構(gòu)以表示“整體-部分”的結(jié)構(gòu)層次。

          組合模式對單個對象(葉子對象)和組合對象(組合對象)具有一致性,它將對象組織到樹結(jié)構(gòu)中,可以用來描述整體與部分的關(guān)系。同時它也模糊了簡單元素(葉子對象)和復雜元素(容器對象)的概念,使得客戶能夠像處理簡單元素一樣來處理復雜元素,從而使客戶程序能夠與復雜元素的內(nèi)部結(jié)構(gòu)解耦。

          在使用組合模式中需要注意一點也是組合模式最關(guān)鍵的地方:葉子對象和組合對象實現(xiàn)相同的接口。這就是組合模式能夠?qū)⑷~子節(jié)點和對象節(jié)點進行一致處理的原因。

          Mybatis支持動態(tài)SQL的強大功能,比如下面的這個SQL:

          "update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
          UPDATE users
          "SET" prefixOverrides=",">
          <if test="name != null and name != ''">
          name = #{name}
          if>
          <if test="age != null and age != ''">
          , age = #{age}
          if>
          <if test="birthday != null and birthday != ''">
          , birthday = #{birthday}
          if>

          where id = ${id}

          在這里面使用到了trim、if等動態(tài)元素,可以根據(jù)條件來生成不同情況下的SQL;

          在DynamicSqlSource.getBoundSql方法里,調(diào)用了rootSqlNode.apply(context)方法,apply方法是所有的動態(tài)節(jié)點都實現(xiàn)的接口:

          public interface SqlNode {
          boolean apply(DynamicContext context);
          }

          對于實現(xiàn)該SqlSource接口的所有節(jié)點,就是整個組合模式樹的各個節(jié)點:

          組合模式的簡單之處在于,所有的子節(jié)點都是同一類節(jié)點,可以遞歸的向下執(zhí)行,比如對于TextSqlNode,因為它是最底層的葉子節(jié)點,所以直接將對應(yīng)的內(nèi)容append到SQL語句中:

          	@Override
          public boolean apply(DynamicContext context) {
          GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
          context.appendSql(parser.parse(text));
          return true;
          }

          但是對于IfSqlNode,就需要先做判斷,如果判斷通過,仍然會調(diào)用子元素的SqlNode,即contents.apply方法,實現(xiàn)遞歸的解析。

          	@Override
          public boolean apply(DynamicContext context) {
          if (evaluator.evaluateBoolean(test, context.getBindings())) {
          contents.apply(context);
          return true;
          }
          return false;
          }

          6、模板方法模式

          模板方法模式是所有模式中最為常見的幾個模式之一,是基于繼承的代碼復用的基本技術(shù)。

          模板方法模式需要開發(fā)抽象類和具體子類的設(shè)計師之間的協(xié)作。一個設(shè)計師負責給出一個算法的輪廓和骨架,另一些設(shè)計師則負責給出這個算法的各個邏輯步驟。代表這些具體邏輯步驟的方法稱做基本方法(primitive method);而將這些基本方法匯總起來的方法叫做模板方法(template method),這個設(shè)計模式的名字就是從此而來。

          模板類定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。

          在Mybatis中,sqlSession的SQL執(zhí)行,都是委托給Executor實現(xiàn)的,Executor包含以下結(jié)構(gòu):

          其中的BaseExecutor就采用了模板方法模式,它實現(xiàn)了大部分的SQL執(zhí)行邏輯,然后把以下幾個方法交給子類定制化完成:

          	protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

          protected abstract List doFlushStatements(boolean isRollback) throws SQLException;

          protected abstract List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
          ResultHandler resultHandler, BoundSql boundSql)
          throws SQLException
          ;

          該模板方法類有幾個子類的具體實現(xiàn),使用了不同的策略:

          • 簡單SimpleExecutor:每執(zhí)行一次update或select,就開啟一個Statement對象,用完立刻關(guān)閉Statement對象。(可以是Statement或PrepareStatement對象)
          • 重用ReuseExecutor:執(zhí)行update或select,以sql作為key查找Statement對象,存在就使用,不存在就創(chuàng)建,用完后,不關(guān)閉Statement對象,而是放置于Map內(nèi),供下一次使用。(可以是Statement或PrepareStatement對象)
          • 批量BatchExecutor:執(zhí)行update(沒有select,JDBC批處理不支持select),將所有sql都添加到批處理中(addBatch()),等待統(tǒng)一執(zhí)行(executeBatch()),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢后,等待逐一執(zhí)行executeBatch()批處理的;BatchExecutor相當于維護了多個桶,每個桶里都裝了很多屬于自己的SQL,就像蘋果藍里裝了很多蘋果,番茄藍里裝了很多番茄,最后,再統(tǒng)一倒進倉庫。(可以是Statement或PrepareStatement對象)

          比如在SimpleExecutor中這樣實現(xiàn)update方法:

          	@Override
          public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
          Statement stmt = null;
          try {
          Configuration configuration = ms.getConfiguration();
          StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null,
          null);
          stmt = prepareStatement(handler, ms.getStatementLog());
          return handler.update(stmt);
          } finally {
          closeStatement(stmt);
          }
          }

          7、適配器模式

          適配器模式(Adapter Pattern) :將一個接口轉(zhuǎn)換成客戶希望的另一個接口,適配器模式使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。適配器模式既可以作為類結(jié)構(gòu)型模式,也可以作為對象結(jié)構(gòu)型模式。

          在Mybatsi的logging包中,有一個Log接口:

          /**
          * @author Clinton Begin
          */

          public interface Log {

          boolean isDebugEnabled();

          boolean isTraceEnabled();

          void error(String s, Throwable e);

          void error(String s);

          void debug(String s);

          void trace(String s);

          void warn(String s);

          }

          該接口定義了Mybatis直接使用的日志方法,而Log接口具體由誰來實現(xiàn)呢?Mybatis提供了多種日志框架的實現(xiàn),這些實現(xiàn)都匹配這個Log接口所定義的接口方法,最終實現(xiàn)了所有外部日志框架到Mybatis日志包的適配:

          比如對于Log4jImpl的實現(xiàn)來說,該實現(xiàn)持有了org.apache.log4j.Logger的實例,然后所有的日志方法,均委托該實例來實現(xiàn)。

          public class Log4jImpl implements Log {

          private static final String FQCN = Log4jImpl.class.getName();

          private Logger log;

          public Log4jImpl(String clazz) {
          log = Logger.getLogger(clazz);
          }

          @Override
          public boolean isDebugEnabled() {
          return log.isDebugEnabled();
          }

          @Override
          public boolean isTraceEnabled() {
          return log.isTraceEnabled();
          }

          @Override
          public void error(String s, Throwable e) {
          log.log(FQCN, Level.ERROR, s, e);
          }

          @Override
          public void error(String s) {
          log.log(FQCN, Level.ERROR, s, null);
          }

          @Override
          public void debug(String s) {
          log.log(FQCN, Level.DEBUG, s, null);
          }

          @Override
          public void trace(String s) {
          log.log(FQCN, Level.TRACE, s, null);
          }

          @Override
          public void warn(String s) {
          log.log(FQCN, Level.WARN, s, null);
          }

          }

          8、裝飾者模式

          裝飾模式(Decorator Pattern) :動態(tài)地給一個對象增加一些額外的職責(Responsibility),就增加對象功能來說,裝飾模式比生成子類實現(xiàn)更為靈活。其別名也可以稱為包裝器(Wrapper),與適配器模式的別名相同,但它們適用于不同的場合。根據(jù)翻譯的不同,裝飾模式也有人稱之為“油漆工模式”,它是一種對象結(jié)構(gòu)型模式。

          mybatis中,緩存的功能由根接口Cache(org.apache.ibatis.cache.Cache)定義。整個體系采用裝飾器設(shè)計模式,數(shù)據(jù)存儲和緩存的基本功能由PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永久緩存實現(xiàn),然后通過一系列的裝飾器來對PerpetualCache永久緩存進行緩存策略等方便的控制。如下圖:

          用于裝飾PerpetualCache的標準裝飾器共有8個(全部在org.apache.ibatis.cache.decorators包中):

          1. FifoCache:先進先出算法,緩存回收策略
          2. LoggingCache:輸出緩存命中的日志信息
          3. LruCache:最近最少使用算法,緩存回收策略
          4. ScheduledCache:調(diào)度緩存,負責定時清空緩存
          5. SerializedCache:緩存序列化和反序列化存儲
          6. SoftCache:基于軟引用實現(xiàn)的緩存管理策略
          7. SynchronizedCache:同步的緩存裝飾器,用于防止多線程并發(fā)訪問
          8. WeakCache:基于弱引用實現(xiàn)的緩存管理策略

          另外,還有一個特殊的裝飾器TransactionalCache:事務(wù)性的緩存

          正如大多數(shù)持久層框架一樣,mybatis緩存同樣分為一級緩存和二級緩存

          • 一級緩存,又叫本地緩存,是PerpetualCache類型的永久緩存,保存在執(zhí)行器中(BaseExecutor),而執(zhí)行器又在SqlSession(DefaultSqlSession)中,所以一級緩存的生命周期與SqlSession是相同的。
          • 二級緩存,又叫自定義緩存,實現(xiàn)了Cache接口的類都可以作為二級緩存,所以可配置如encache等的第三方緩存。二級緩存以namespace名稱空間為其唯一標識,被保存在Configuration核心配置對象中。

          二級緩存對象的默認類型為PerpetualCache,如果配置的緩存是默認類型,則mybatis會根據(jù)配置自動追加一系列裝飾器。

          Cache對象之間的引用順序為:

          SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache

          9、迭代器模式

          迭代器(Iterator)模式,又叫做游標(Cursor)模式。GOF給出的定義為:提供一種方法訪問一個容器(container)對象中各個元素,而又不需暴露該對象的內(nèi)部細節(jié)。

          Java的Iterator就是迭代器模式的接口,只要實現(xiàn)了該接口,就相當于應(yīng)用了迭代器模式:

          比如Mybatis的PropertyTokenizer是property包中的重量級類,該類會被reflection包中其他的類頻繁的引用到。這個類實現(xiàn)了Iterator接口,在使用時經(jīng)常被用到的是Iterator接口中的hasNext這個函數(shù)。

          public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
          private String name;
          private String indexedName;
          private String index;
          private String children;

          public PropertyTokenizer(String fullname) {
          int delim = fullname.indexOf('.');
          if (delim > -1) {
          name = fullname.substring(0, delim);
          children = fullname.substring(delim + 1);
          } else {
          name = fullname;
          children = null;
          }
          indexedName = name;
          delim = name.indexOf('[');
          if (delim > -1) {
          index = name.substring(delim + 1, name.length() - 1);
          name = name.substring(0, delim);
          }
          }

          public String getName() {
          return name;
          }

          public String getIndex() {
          return index;
          }

          public String getIndexedName() {
          return indexedName;
          }

          public String getChildren() {
          return children;
          }

          @Override
          public boolean hasNext() {
          return children != null;
          }

          @Override
          public PropertyTokenizer next() {
          return new PropertyTokenizer(children);
          }

          @Override
          public void remove() {
          throw new UnsupportedOperationException(
          "Remove is not supported, as it has no meaning in the context of properties.");
          }
          }

          可以看到,這個類傳入一個字符串到構(gòu)造函數(shù),然后提供了iterator方法對解析后的子串進行遍歷,是一個很常用的方法類。


          --END--


          下方二維碼關(guān)注我

          因為堅持分享可落地的技術(shù)架構(gòu)文章

          瀏覽 71
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  免费的AV在线 | 老骚逼视频 | 国产a级视频 | 少女的报答-戚小怜 | 丁香五月中文字幕 |