自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Mybatis超詳細(xì)插件機(jī)制解析,弄懂?dāng)r截器So easy

運(yùn)維 數(shù)據(jù)庫(kù)運(yùn)維
Mybatis采用責(zé)任鏈模式,通過(guò)動(dòng)態(tài)代理組織多個(gè)插件(攔截器),通過(guò)這些插件可以改變Mybatis的默認(rèn)行為(諸如SQL重寫之類的),由于插件會(huì)深入到Mybatis的核心,因此在編寫自己的插件前最好了解下它的原理,以便寫出安全高效的插件。

[[285992]]

概述

Mybatis插件又稱攔截器,本篇文章中出現(xiàn)的攔截器都表示插件。

Mybatis采用責(zé)任鏈模式,通過(guò)動(dòng)態(tài)代理組織多個(gè)插件(攔截器),通過(guò)這些插件可以改變Mybatis的默認(rèn)行為(諸如SQL重寫之類的),由于插件會(huì)深入到Mybatis的核心,因此在編寫自己的插件前最好了解下它的原理,以便寫出安全高效的插件。

MyBatis 允許你在已映射語(yǔ)句執(zhí)行過(guò)程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來(lái)攔截的方法調(diào)用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

總體概括為:

  • 攔截執(zhí)行器的方法
  • 攔截參數(shù)的處理
  • 攔截結(jié)果集的處理
  • 攔截Sql語(yǔ)法構(gòu)建的處理

Mybatis是通過(guò)動(dòng)態(tài)代理的方式實(shí)現(xiàn)攔截的,閱讀此篇文章需要先對(duì)Java的動(dòng)態(tài)代理機(jī)制有所了解。

Mybatis四大接口

既然Mybatis是對(duì)四大接口進(jìn)行攔截的,那我們先要知道Mybatis的四大接口是哪些: Executor, StatementHandler, ResultSetHandler, ParameterHandler。

 

Mybatis超詳細(xì)插件機(jī)制解析,弄懂?dāng)r截器So easy

 

上圖Mybatis框架的整個(gè)執(zhí)行過(guò)程。Mybatis插件能夠?qū)@四大對(duì)象進(jìn)行攔截,可以說(shuō)包含到了Mybatis一次SQL執(zhí)行的所有操作??梢奙ybatis的的插件很強(qiáng)大。

  1. Executor是 Mybatis的內(nèi)部執(zhí)行器,它負(fù)責(zé)調(diào)用StatementHandler操作數(shù)據(jù)庫(kù),并把結(jié)果集通過(guò) ResultSetHandler進(jìn)行自動(dòng)映射,另外,他還處理了二級(jí)緩存的操作。從這里可以看出,我們也是可以通過(guò)插件來(lái)實(shí)現(xiàn)自定義的二級(jí)緩存的。
  2. StatementHandler是Mybatis直接和數(shù)據(jù)庫(kù)執(zhí)行sql腳本的對(duì)象。另外它也實(shí)現(xiàn)了Mybatis的一級(jí)緩存。這里,我們可以使用插件來(lái)實(shí)現(xiàn)對(duì)一級(jí)緩存的操作(禁用等等)。
  3. ParameterHandler是Mybatis實(shí)現(xiàn)Sql入?yún)⒃O(shè)置的對(duì)象。插件可以改變我們Sql的參數(shù)默認(rèn)設(shè)置。
  4. ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口對(duì)象。我們可以定義插件對(duì)Mybatis的結(jié)果集自動(dòng)映射進(jìn)行修改。

插件Interceptor

Mybatis的插件實(shí)現(xiàn)要實(shí)現(xiàn)Interceptor接口,我們看下這個(gè)接口定義的方法。

  1. public interface Interceptor {   
  2.   Object intercept(Invocation invocation) throws Throwable;     
  3.   Object plugin(Object target);   
  4.   void setProperties(Properties properties); 

這個(gè)接口只聲明了三個(gè)方法:

  • setProperties方法是在Mybatis進(jìn)行配置插件的時(shí)候可以配置自定義相關(guān)屬性,即:接口實(shí)現(xiàn)對(duì)象的參數(shù)配置。
  • plugin方法是插件用于封裝目標(biāo)對(duì)象的,通過(guò)該方法我們可以返回目標(biāo)對(duì)象本身,也可以返回一個(gè)它的代理,可以決定是否要進(jìn)行攔截進(jìn)而決定要返回一個(gè)什么樣的目標(biāo)對(duì)象,官方提供了示例:return Plugin.wrap(target, this)。
  • intercept方法就是要進(jìn)行攔截的時(shí)候要執(zhí)行的方法。

理解這個(gè)接口的定義,先要知道java動(dòng)態(tài)代理機(jī)制。plugin接口即返回參數(shù)target對(duì)象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理對(duì)象。在調(diào)用對(duì)應(yīng)對(duì)象的接口的時(shí)候,可以進(jìn)行攔截并處理。

Mybatis四大接口對(duì)象創(chuàng)建方法

Mybatis的插件是采用對(duì)四大接口的對(duì)象生成動(dòng)態(tài)代理對(duì)象的方法來(lái)實(shí)現(xiàn)的。那么現(xiàn)在我們看下Mybatis是怎么創(chuàng)建這四大接口對(duì)象的。

  1. public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 
  2.   //確保ExecutorType不為空(defaultExecutorType有可能為空) 
  3.   executorType = executorType == null ? defaultExecutorType : executorType; 
  4.   executorType = executorType == null ? ExecutorType.SIMPLE : executorType; 
  5.   Executor executor;  if (ExecutorType.BATCH == executorType) { 
  6.    executor = new BatchExecutor(this, transaction); 
  7.   } else if (ExecutorType.REUSE == executorType) { 
  8.    executor = new ReuseExecutor(this, transaction); 
  9.   } else { 
  10.    executor = new SimpleExecutor(this, transaction); 
  11.   }  if (cacheEnabled) { 
  12.    executor = new CachingExecutor(executor); 
  13.   } 
  14.   executor = (Executor) interceptorChain.pluginAll(executor); 
  15.   return executor; 
  16.  
  17. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { 
  18.   StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); 
  19.   statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); 
  20.   return statementHandler; 
  21.  
  22. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { 
  23.   ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); 
  24.   parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); 
  25.   return parameterHandler; 
  26.  
  27. public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { 
  28.   ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); 
  29.   resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); 
  30.   return resultSetHandler; 

查看源碼可以發(fā)現(xiàn), Mybatis框架在創(chuàng)建好這四大接口對(duì)象的實(shí)例后,都會(huì)調(diào)用InterceptorChain.pluginAll()方法。InterceptorChain對(duì)象是插件執(zhí)行鏈對(duì)象,看源碼就知道里面維護(hù)了Mybatis配置的所有插件(Interceptor)對(duì)象。

  1. // target --> Executor/ParameterHandler/ResultSetHander/StatementHandler 
  2. public Object pluginAll(Object target) { 
  3.   for (Interceptor interceptor : interceptors) { 
  4.    target = interceptor.plugin(target); 
  5.   } 
  6.   return target; 

其實(shí)就是按順序執(zhí)行我們插件的plugin方法,一層一層返回我們?cè)瓕?duì)象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理對(duì)象。當(dāng)我們調(diào)用四大接口的方法的時(shí)候,實(shí)際上是調(diào)用代理對(duì)象的相應(yīng)方法,代理對(duì)象又會(huì)調(diào)用四大接口的實(shí)例。

Plugin對(duì)象

我們知道,官方推薦插件實(shí)現(xiàn)plugin方法為:Plugin.wrap(target, this);

  1. public static Object wrap(Object target, Interceptor interceptor) { 
  2.   // 獲取插件的Intercepts注解 
  3.   Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); 
  4.   Class<?> type = target.getClass(); 
  5.   Class<?>[] interfaces = getAllInterfaces(type, signatureMap); 
  6.   if (interfaces.length > 0) { 
  7.    return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); 
  8.   } 
  9.   return target; 

這個(gè)方法其實(shí)是Mybatis簡(jiǎn)化我們插件實(shí)現(xiàn)的工具方法。其實(shí)就是根據(jù)當(dāng)前攔截的對(duì)象創(chuàng)建了一個(gè)動(dòng)態(tài)代理對(duì)象。代理對(duì)象的InvocationHandler處理器為新建的Plugin對(duì)象。

插件配置注解@Intercepts

Mybatis的插件都要有Intercepts注解來(lái)指定要攔截哪個(gè)對(duì)象的哪個(gè)方法。我們知道,Plugin.warp方法會(huì)返回四大接口對(duì)象的代理對(duì)象(通過(guò)new Plugin()創(chuàng)建的IvocationHandler處理器),會(huì)攔截所有的執(zhí)行方法。在代理對(duì)象執(zhí)行對(duì)應(yīng)方法的時(shí)候,會(huì)調(diào)用InvocationHandler處理器的invoke方法。Mybatis中利用了注解的方式配置指定攔截哪些方法。具體如下:

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  2.   try { 
  3.    Set<Method> methods = signatureMap.get(method.getDeclaringClass()); 
  4.    if (methods != null && methods.contains(method)) { 
  5.      return interceptor.intercept(new Invocation(target, method, args)); 
  6.    } 
  7.    return method.invoke(target, args); 
  8.   } catch (Exception e) { 
  9.    throw ExceptionUtil.unwrapThrowable(e); 
  10.   } 

可以看到,只有通過(guò)Intercepts注解指定的方法才會(huì)執(zhí)行我們自定義插件的intercept方法。未通過(guò)Intercepts注解指定的將不會(huì)執(zhí)行我們的intercept方法。

官方插件開發(fā)方式

  1. @Intercepts({@Signature(type = Executor.class, method = "query"
  2.     args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) 
  3. public class TestInterceptor implements Interceptor { 
  4.   public Object intercept(Invocation invocation) throws Throwable { 
  5.    Object target = invocation.getTarget(); //被代理對(duì)象 
  6.    Method method = invocation.getMethod(); //代理方法 
  7.    Object[] args = invocation.getArgs(); //方法參數(shù) 
  8.    // do something ...... 方法攔截前執(zhí)行代碼塊 
  9.    Object result = invocation.proceed(); 
  10.    // do something .......方法攔截后執(zhí)行代碼塊 
  11.    return result; 
  12.   } 
  13.   public Object plugin(Object target) { 
  14.    return Plugin.wrap(target, this); 
  15.   } 

以上就是Mybatis官方推薦的插件實(shí)現(xiàn)的方法,通過(guò)Plugin對(duì)象創(chuàng)建被代理對(duì)象的動(dòng)態(tài)代理對(duì)象??梢园l(fā)現(xiàn),Mybatis的插件開發(fā)還是很簡(jiǎn)單的。

自定義開發(fā)方式

Mybatis的插件開發(fā)通過(guò)內(nèi)部提供的Plugin對(duì)象可以很簡(jiǎn)單的開發(fā)。只有理解了插件實(shí)現(xiàn)原理,對(duì)應(yīng)不采用Plugin對(duì)象我們一樣可以自己實(shí)現(xiàn)插件的開發(fā)。下面是我個(gè)人理解之后的自己實(shí)現(xiàn)的一種方式。

  1. public class TestInterceptor implements Interceptor { 
  2.   public Object intercept(Invocation invocation) throws Throwable { 
  3.     Object target = invocation.getTarget(); //被代理對(duì)象 
  4.     Method method = invocation.getMethod(); //代理方法 
  5.     Object[] args = invocation.getArgs(); //方法參數(shù) 
  6.     // do something ...... 方法攔截前執(zhí)行代碼塊 
  7.     Object result = invocation.proceed(); 
  8.     // do something .......方法攔截后執(zhí)行代碼塊 
  9.     return result; 
  10.   } 
  11.   public Object plugin(final Object target) { 
  12.     return Proxy.newProxyInstance(Interceptor.class.getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { 
  13.       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  14.         return intercept(new Invocation(target, method, args)); 
  15.       } 
  16.     }); 
  17.   } 
  18.   public void setProperties(Properties properties) { 
  19.   } 

當(dāng)然,Mybatis插件的那這個(gè)時(shí)候Intercepts的注解起不到作用了。

小結(jié)

我們?cè)贛yBatis配置了一個(gè)插件,在運(yùn)行發(fā)生了什么

  1. 所有可能被攔截的處理類都會(huì)生成一個(gè)代理
  2. 處理類代理在執(zhí)行對(duì)應(yīng)方法時(shí),判斷要不要執(zhí)行插件中的攔截方法
  3. 執(zhí)行插接中的攔截方法后,推進(jìn)目標(biāo)的執(zhí)行

如果有N個(gè)插件,就有N個(gè)代理,每個(gè)代理都要執(zhí)行上面的邏輯。這里面的層層代理要多次生成動(dòng)態(tài)代理,是比較影響性能的。雖然能指定插件攔截的位置,但這個(gè)是在執(zhí)行方法時(shí)動(dòng)態(tài)判斷,初始化的時(shí)候就是簡(jiǎn)單的把插件包裝到了所有可以攔截的地方。

因此,在編寫插件時(shí)需注意以下幾個(gè)原則:

  • 不編寫不必要的插件;
  • 實(shí)現(xiàn)plugin方法時(shí)判斷一下目標(biāo)類型,是本插件要攔截的對(duì)象才執(zhí)行Plugin.wrap方法,否者直接返回目標(biāo)本身,這樣可以減少目標(biāo)被代理的次數(shù)。
  1. // 假如我們只要攔截Executor對(duì)象,那么我們應(yīng)該這么做 
  2. public Object plugin(final Object target) { 
  3.   if (target instanceof Executor) { 
  4.    return Plugin.wrap(target, this); 
  5.   } else { 
  6.    return target; 
  7.   } 

Mybatis插件很強(qiáng)大,可以對(duì)Mybatis框架進(jìn)行很大的擴(kuò)展。當(dāng)然,如果你不理解Mybatis插件的原理,開發(fā)起來(lái)只能是模擬兩可。在實(shí)際開發(fā)過(guò)程中,我們可以參考別人寫的插件。下面是一個(gè)Mybatis分頁(yè)的插件,可以為以后開發(fā)做參考。

  1. /** 
  2.  * Mybatis - 通用分頁(yè)插件(如果開啟二級(jí)緩存需要注意) 
  3.  */ 
  4. @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}), 
  5.     @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) 
  6. @Log4j 
  7. public class PageHelper implements Interceptor { 
  8.  
  9.   public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>(); 
  10.  
  11.   /** 
  12.    * 開始分頁(yè) 
  13.    * 
  14.    * @param pageNum 
  15.    * @param pageSize 
  16.    */ 
  17.   public static void startPage(int pageNum, int pageSize) { 
  18.     localPage.set(new Page(pageNum, pageSize)); 
  19.   } 
  20.  
  21.   /** 
  22.    * 結(jié)束分頁(yè)并返回結(jié)果,該方法必須被調(diào)用,否則localPage會(huì)一直保存下去,直到下一次startPage 
  23.    * 
  24.    * @return 
  25.    */ 
  26.   public static Page endPage() { 
  27.     Page page = localPage.get(); 
  28.     localPage.remove(); 
  29.     return page; 
  30.   } 
  31.  
  32.   public Object intercept(Invocation invocation) throws Throwable { 
  33.     if (localPage.get() == null) { 
  34.       return invocation.proceed(); 
  35.     } 
  36.     if (invocation.getTarget() instanceof StatementHandler) { 
  37.       StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); 
  38.       MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler); 
  39.       // 分離代理對(duì)象鏈(由于目標(biāo)類可能被多個(gè)插件攔截,從而形成多次代理,通過(guò)下面的兩次循環(huán) 
  40.       // 可以分離出最原始的的目標(biāo)類) 
  41.       while (metaStatementHandler.hasGetter("h")) { 
  42.         Object object = metaStatementHandler.getValue("h"); 
  43.         metaStatementHandler = SystemMetaObject.forObject(object); 
  44.       } 
  45.       // 分離最后一個(gè)代理對(duì)象的目標(biāo)類 
  46.       while (metaStatementHandler.hasGetter("target")) { 
  47.         Object object = metaStatementHandler.getValue("target"); 
  48.         metaStatementHandler = SystemMetaObject.forObject(object); 
  49.       } 
  50.       MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); 
  51.       //分頁(yè)信息if (localPage.get() != null) { 
  52.       Page page = localPage.get(); 
  53.       BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); 
  54.       // 分頁(yè)參數(shù)作為參數(shù)對(duì)象parameterObject的一個(gè)屬性 
  55.       String sql = boundSql.getSql(); 
  56.       // 重寫sql 
  57.       String pageSql = buildPageSql(sql, page); 
  58.       //重寫分頁(yè)sql 
  59.       metaStatementHandler.setValue("delegate.boundSql.sql", pageSql); 
  60.       Connection connection = (Connection) invocation.getArgs()[0]; 
  61.       // 重設(shè)分頁(yè)參數(shù)里的總頁(yè)數(shù)等 
  62.       setPageParameter(sql, connection, mappedStatement, boundSql, page); 
  63.       // 將執(zhí)行權(quán)交給下一個(gè)插件 
  64.       return invocation.proceed(); 
  65.     } else if (invocation.getTarget() instanceof ResultSetHandler) { 
  66.       Object result = invocation.proceed(); 
  67.       Page page = localPage.get(); 
  68.       page.setResult((List) result); 
  69.       return result; 
  70.     } 
  71.     return null
  72.   } 
  73.  
  74.   /** 
  75.    * 只攔截這兩種類型的 
  76.    * <br>StatementHandler 
  77.    * <br>ResultSetHandler 
  78.    * 
  79.    * @param target 
  80.    * @return 
  81.    */ 
  82.   public Object plugin(Object target) { 
  83.     if (target instanceof StatementHandler || target instanceof ResultSetHandler) { 
  84.       return Plugin.wrap(target, this); 
  85.     } else { 
  86.       return target; 
  87.     } 
  88.   } 
  89.  
  90.   public void setProperties(Properties properties) { 
  91.  
  92.   } 
  93.  
  94.   /** 
  95.    * 修改原SQL為分頁(yè)SQL 
  96.    * 
  97.    * @param sql 
  98.    * @param page 
  99.    * @return 
  100.    */ 
  101.   private String buildPageSql(String sql, Page page) { 
  102.     StringBuilder pageSql = new StringBuilder(200); 
  103.     pageSql.append("select * from ("); 
  104.     pageSql.append(sql); 
  105.     pageSql.append(" ) temp limit ").append(page.getStartRow()); 
  106.     pageSql.append(" , ").append(page.getPageSize()); 
  107.     return pageSql.toString(); 
  108.   } 
  109.  
  110.   /** 
  111.    * 獲取總記錄數(shù) 
  112.    * 
  113.    * @param sql 
  114.    * @param connection 
  115.    * @param mappedStatement 
  116.    * @param boundSql 
  117.    * @param page 
  118.    */ 
  119.   private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement, 
  120.                  BoundSql boundSql, Page page) { 
  121.     // 記錄總記錄數(shù) 
  122.     String countSql = "select count(0) from (" + sql + ") temp"
  123.     PreparedStatement countStmt = null
  124.     ResultSet rs = null
  125.     try { 
  126.       countStmt = connection.prepareStatement(countSql); 
  127.       BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, 
  128.           boundSql.getParameterMappings(), boundSql.getParameterObject()); 
  129.       setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject()); 
  130.       rs = countStmt.executeQuery(); 
  131.       int totalCount = 0; 
  132.       if (rs.next()) { 
  133.         totalCount = rs.getInt(1); 
  134.       } 
  135.       page.setTotal(totalCount); 
  136.       int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1); 
  137.       page.setPages(totalPage); 
  138.     } catch (SQLException e) { 
  139.       log.error("Ignore this exception", e); 
  140.     } finally { 
  141.       try { 
  142.         rs.close(); 
  143.       } catch (SQLException e) { 
  144.         log.error("Ignore this exception", e); 
  145.       } 
  146.       try { 
  147.         countStmt.close(); 
  148.       } catch (SQLException e) { 
  149.         log.error("Ignore this exception", e); 
  150.       } 
  151.     } 
  152.   } 
  153.  
  154.   /** 
  155.    * 代入?yún)?shù)值 
  156.    * 
  157.    * @param ps 
  158.    * @param mappedStatement 
  159.    * @param boundSql 
  160.    * @param parameterObject 
  161.    * @throws SQLException 
  162.    */ 
  163.   private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, 
  164.                 Object parameterObject) throws SQLException { 
  165.     ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); 
  166.     parameterHandler.setParameters(ps); 
  167.   } 
  168.  
  169.   @Data //采用lombok插件編譯 
  170.   public static class Page<E> { 
  171.     private int pageNum; 
  172.     private int pageSize; 
  173.     private int startRow; 
  174.     private int endRow; 
  175.     private long total; 
  176.     private int pages; 
  177.     private List<E> result; 
  178.  
  179.     public Page(int pageNum, int pageSize) { 
  180.       this.pageNum = pageNum; 
  181.       this.pageSize = pageSize; 
  182.       this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0; 
  183.       this.endRow = pageNum * pageSize; 
  184.     } 
  185.  
  186.   } 

 

責(zé)任編輯:武曉燕 來(lái)源: 今日頭條
相關(guān)推薦

2024-12-27 08:39:10

2011-06-09 17:26:17

Qt 插件 API

2009-12-11 10:29:03

PHP插件機(jī)制

2010-09-08 14:39:35

2021-06-22 06:52:46

Vite 插件機(jī)制Rollup

2025-01-02 10:10:51

2022-07-11 10:37:41

MapPart集合

2013-11-04 09:35:38

Firefox插件攔截FLASH

2020-12-10 08:21:27

XML映射Mybatis

2024-07-17 09:23:58

Vite插件機(jī)制

2023-11-07 10:19:08

2009-06-24 16:00:00

2024-05-06 00:00:00

C#工具代碼

2025-02-28 08:14:53

2009-09-27 17:37:32

Hibernate攔截

2024-02-28 09:35:52

2023-09-05 08:58:07

2011-01-21 15:02:14

jQuerywebJavaScript

2011-05-16 10:14:11

Hibernate

2011-11-21 14:21:26

SpringMVCJava框架
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)