hikaril連接sql2000_hikari連接池解析(版本:HikariCP-2.5.1.jar)

maxLifetime參數

maxLifetime參數必須小于數據庫的time_wait,默認是1800000,即30分鐘。如果設置為0,表示存活時間無限大。如果不等于0且小于30秒則會被重置回30分鐘。HikariConfig類中有該參數的校驗規則。

HikariPool類中,當我們初始化連接池的時候,它的構造方法中,實例化了this.POOL_ENTRY_CREATOR = new HikariPool.PoolEntryCreator();該類實現了Callable接口,用來初始化連接。

public Boolean call() throws Exception {

for(long sleepBackoff = 250L; HikariPool.this.poolState == 0 && HikariPool.this.totalConnections.get() < HikariPool.this.config.getMaximumPoolSize(); sleepBackoff = Math.min(TimeUnit.SECONDS.toMillis(10L), Math.min(HikariPool.this.connectionTimeout, (long)((double)sleepBackoff * 1.5D)))) {

PoolEntry poolEntry = HikariPool.this.createPoolEntry();

if (poolEntry != null) {

HikariPool.this.totalConnections.incrementAndGet();

HikariPool.this.connectionBag.add(poolEntry);

return Boolean.TRUE;

}

UtilityElf.quietlySleep(sleepBackoff);

}

return Boolean.FALSE;

}

復制代碼

在其中調用createPoolEntry()生成一個連接。

private PoolEntry createPoolEntry() {

try {

final PoolEntry poolEntry = this.newPoolEntry();

long maxLifetime = this.config.getMaxLifetime();

if (maxLifetime > 0L) {

long variance = maxLifetime > 10000L ? ThreadLocalRandom.current().nextLong(maxLifetime / 40L) : 0L;

long lifetime = maxLifetime - variance;

poolEntry.setFutureEol(this.houseKeepingExecutorService.schedule(new Runnable() {

public void run() {

HikariPool.this.softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false);

}

}, lifetime, TimeUnit.MILLISECONDS));

}

this.LOGGER.debug("{} - Added connection {}", this.poolName, poolEntry.connection);

return poolEntry;

} catch (Exception var8) {

if (this.poolState == 0) {

this.LOGGER.debug("{} - Cannot acquire connection from data source", this.poolName, var8);

}

return null;

}

}

復制代碼

在該方法中,設置了一個延時任務,具體的延時執行時間是根據maxLifetime來計算,觸發時間距離maxlifetime的差值是根據 maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;

來計算(up to 2.5% of the maxlifetime),在連接存活將要到達maxLifetime之前觸發evit,用來防止出現大面積的connection因maxLifetime同一時刻失效。

當被觸發時,會標記evict為true,標記為evict只是表示連接池中的該連接不可用,但還在連接池當中,還會被borrow出來,只是getConnection的時候判斷了,如果是isMarkedEvicted,則會從連接池中移除該連接,然后close掉。

HikariCP中通過獨立的線程池closeConnectionExecutor進行物理連接的關閉。出現以下三種情況時會觸發連接的自動關閉:

連接斷開;

連接存活時間超出最大生存時間(maxLifeTime)

連接空閑時間超出最大空閑時間(idleTimeout)

closeConnectionExecutor關閉連接后,會調用fillPool()方法對連接池進行連接填充

validationTimeout

validationTimeout用來指定驗證連接有效性的超時時間(默認是5秒,最小不能小于250毫秒),在HikariPool.getConnection方法中會調用isConnectionAlive(Connection connection)對連接進行驗證。

如果是jdbc4的話,可以使用isUseJdbc4Validation,是直接利用connection.isValid(validationSeconds)來驗證連接的有效性;否則的話則用connectionTestQuery查詢語句來查詢驗證。

leakDetectionThreshold`

該參數主要用來開啟連接泄漏檢測,在通過getConnection()獲取連接的時候,會繼續調用另外一個createProxyConnection()方法獲取連接,這里我們關注入參this.leakTask.schedule(poolEntry)。

public final Connection getConnection(long hardTimeout) throws SQLException {

this.suspendResumeLock.acquire();

long startTime = clockSource.currentTime();

try {

long timeout = hardTimeout;

do {

PoolEntry poolEntry = (PoolEntry)this.connectionBag.borrow(timeout, TimeUnit.MILLISECONDS);

if (poolEntry == null) {

break;

}

long now = clockSource.currentTime();

if (!poolEntry.isMarkedEvicted() && (clockSource.elapsedMillis(poolEntry.lastAccessed, now) <= this.ALIVE_BYPASS_WINDOW_MS || this.isConnectionAlive(poolEntry.connection))) {

this.metricsTracker.recordBorrowStats(poolEntry, startTime);

//獲取連接

Connection var10 = poolEntry.createProxyConnection(this.leakTask.schedule(poolEntry), now);

return var10;

}

復制代碼

該schedule方法返回一個ProxyLeakTask對象

//返回 ProxyLeakTask

ProxyLeakTask schedule(PoolEntry bagEntry) {

return this.leakDetectionThreshold == 0L ? NO_LEAK : new ProxyLeakTask(this, bagEntry);

}

復制代碼

這里判斷leakDetectionThreshold參數是否為0,默認是0,不開啟檢測。否則,就會開啟一個延時執行任務,時間正好為設置的leakDetectionThreshold值,該任務的作用就是用來拋出Apparent connection leak detected異常。

private ProxyLeakTask(ProxyLeakTask parent, PoolEntry poolEntry) {

this.exception = new Exception("Apparent connection leak detected");

this.connectionName = poolEntry.connection.toString();

//延時執行

this.scheduledFuture = parent.executorService.schedule(this, parent.leakDetectionThreshold, TimeUnit.MILLISECONDS);

}

復制代碼

截取一部分異常,如下

22:14:49.096 volte-cmd-service-test [HikariPool-1 housekeeper] WARN com.zaxxer.hikari.pool.ProxyLeakTask - Connection leak detection triggered for com.mysql.jdbc.JDBC4Connection@429fe922, stack trace follows

java.lang.Exception: Apparent connection leak detected

at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122)

at org.hibernate.internal.AbstractSessionImpl$NonContextualJdbcConnectionAccess.obtainConnection(AbstractSessionImpl.java:386)

at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:87)

at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.java:112)

at org.hibernate.internal.SessionImpl.connection(SessionImpl.java:489)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:215)

at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:200)

at org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle.doGetConnection(HibernateJpaDialect.java:414)

at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:177)

復制代碼

也就是從我們獲取這個連接開始,到歸還連接之前的這一段時間,如果超過了leakDetectionThreshold,則會拋出上面的異常。

HouseKeeper

它是HikariPool中的一個內部類,實現了Runnable接口,主要就是對連接進行管理。在初始化HikariPool的時候,會創建一個scheduleWithFixedDelay任務(已固定延遲時間執行,就是說兩個任務之間的時間間隔是固定的,但每個任務的執行時長可能是不定的,與scheduleFixedRate的區別就是,不管任務是否執行完,到點就執行下一次任務),默認30s執行一次,刷新配置,進行判斷。

public HikariPool(HikariConfig config) {

super(config);

this.ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", TimeUnit.MILLISECONDS.toMillis(500L));

this.HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", TimeUnit.SECONDS.toMillis(30L));

this.POOL_ENTRY_CREATOR = new HikariPool.PoolEntryCreator();

this.connectionBag = new ConcurrentBag(this);

this.totalConnections = new AtomicInteger();

this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;

this.checkFailFast();

if (config.getMetricsTrackerFactory() != null) {

this.setMetricsTrackerFactory(config.getMetricsTrackerFactory());

} else {

this.setMetricRegistry(config.getMetricRegistry());

}

this.setHealthCheckRegistry(config.getHealthCheckRegistry());

this.registerMBeans(this);

ThreadFactory threadFactory = config.getThreadFactory();

this.addConnectionExecutor = UtilityElf.createThreadPoolExecutor(config.getMaximumPoolSize(), this.poolName + " connection adder", threadFactory, new DiscardPolicy());

this.closeConnectionExecutor = UtilityElf.createThreadPoolExecutor(config.getMaximumPoolSize(), this.poolName + " connection closer", threadFactory, new CallerRunsPolicy());

//創建定時任務類

if (config.getScheduledExecutorService() == null) {

ThreadFactory threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(this.poolName + " housekeeper", true);

this.houseKeepingExecutorService = new ScheduledThreadPoolExecutor(1, (ThreadFactory)threadFactory, new DiscardPolicy());

//傳遞false參數給這個方法,執行shutdown()方法之后,待處理的任務將不會被執行。

this.houseKeepingExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);

//取消任務后,判斷是否需要從阻塞隊列中移除任務

this.houseKeepingExecutorService.setRemoveOnCancelPolicy(true);

} else {

this.houseKeepingExecutorService = config.getScheduledExecutorService();

}

this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), this.houseKeepingExecutorService);

//初始化HouseKeeper

this.houseKeepingExecutorService.scheduleWithFixedDelay(new HikariPool.HouseKeeper(), 100L, this.HOUSEKEEPING_PERIOD_MS, TimeUnit.MILLISECONDS);

}

復制代碼

時間回撥處理

在HouseKeeper的run方法中,會先對時間進行判斷。

這里主要就是通過一個時間差來判斷這個時間差返回內是否有時間回撥,在初始化的時候會通過下面構造方法生成一個時間戳

// previous=當前時間-30s(默認的定時任務間隔時間)

private HouseKeeper() {

this.previous = HikariPool.clockSource.plusMillis(HikariPool.clockSource.currentTime(), -HikariPool.this.HOUSEKEEPING_PERIOD_MS);

}

復制代碼

當初始化完成的第一次30s后或者上次任務執行完的30s后,再執行該任務,如果當前的時間戳+128ms還要小于previous(上次執行后減去30s的時間戳)+30s,則表示有時間回撥

//檢測逆行時間,根據NTP規范允許+128ms

if (HikariPool.clockSource.plusMillis(now, 128L) < HikariPool.clockSource.plusMillis(this.previous, HikariPool.this.HOUSEKEEPING_PERIOD_MS)) {

HikariPool.this.LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));

this.previous = now;

HikariPool.this.softEvictConnections();

HikariPool.this.fillPool();

return;

}

復制代碼

此時,會打印日志,并重置previous為當前時間,設置連接為不可用,再重新生成連接。

保持最小連接

如果時間沒有錯誤,則會判斷idleTimeout,如果大于0,取出當前空閑連接

判斷是否大于最小連接數minimumIdle,如果大于,則繼續對當前的空閑連接基于lastAccessed(最后一次訪問時間)進行排序,再遍歷

如果取出的每個連接的空閑時間已經超過了idleTimeout,并且成功將連接從NOT_IN_USE(閑置中)更改為RESERVED(標記為保留中)

則關閉該連接

最后再新創建連接

public void run() {

try {

//刷新connectionTimeout、validationTimeout

HikariPool.this.connectionTimeout = HikariPool.this.config.getConnectionTimeout();

HikariPool.this.validationTimeout = HikariPool.this.config.getValidationTimeout();

HikariPool.this.leakTask.updateLeakDetectionThreshold(HikariPool.this.config.getLeakDetectionThreshold());

long idleTimeout = HikariPool.this.config.getIdleTimeout();

long now = HikariPool.clockSource.currentTime();

//時鐘回撥判斷

if (HikariPool.clockSource.plusMillis(now, 128L) < HikariPool.clockSource.plusMillis(this.previous, HikariPool.this.HOUSEKEEPING_PERIOD_MS)) {

HikariPool.this.LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));

this.previous = now;

HikariPool.this.softEvictConnections();

HikariPool.this.fillPool();

return;

}

if (now > HikariPool.clockSource.plusMillis(this.previous, 3L * HikariPool.this.HOUSEKEEPING_PERIOD_MS / 2L)) {

HikariPool.this.LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));

}

this.previous = now;

String afterPrefix = "Pool ";

if (idleTimeout > 0L) {

//取出空閑連接 連接狀態,IN_USE(1:使用中)、NOT_IN_USE(0:閑置中)、REMOVED(-1:已移除)、RESERVED(-1:標記為保留中)

List idleList = HikariPool.this.connectionBag.values(0);

int removable = idleList.size() - HikariPool.this.config.getMinimumIdle();

if (removable > 0) {

HikariPool.this.logPoolState("Before cleanup ");

afterPrefix = "After cleanup ";

//排序

idleList.sort(PoolEntry.LASTACCESS_COMPARABLE);

Iterator var8 = idleList.iterator();

while(var8.hasNext()) {

PoolEntry poolEntry = (PoolEntry)var8.next();

//idleTimeout判斷,連接狀態修改

if (HikariPool.clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && HikariPool.this.connectionBag.reserve(poolEntry)) {

HikariPool.this.closeConnection(poolEntry, "(connection has passed idleTimeout)");

--removable;

if (removable == 0) {

break;//keep min idle cons

}

}

}

}

}

HikariPool.this.logPoolState(afterPrefix);

HikariPool.this.fillPool();

} catch (Exception var10) {

HikariPool.this.LOGGER.error("Unexpected exception in housekeeping task", var10);

}

}

}

復制代碼

問題

minimumIdle不一致問題

當前版本在應用初始化的時候,連接池也會進行初始化,但是當我們配置的數據源屬性minimumIdle

//HikariCP-3.4.5.jar

private synchronized void fillPool()

{

final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())

- addConnectionQueueReadOnlyView.size();

if (connectionsToAdd <= 0) logger.debug("{} - Fill pool skipped, pool is at sufficient level.", poolName);

//生成的個數減去了1

for (int i = 0; i < connectionsToAdd; i++) {

addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);

}

}

//HikariCP-2.5.1.jar 的該方法

for (int i = 0; i < connectionsToAdd; i++) {

addBagItem();

}

復制代碼

超時問題

錯誤日志如下:

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.

復制代碼首先檢查配置是否有問題,maxLifetime不能大于數據庫的time_wait,查詢mysql配置show variables like ‘%timeout%’,默認為8小時。

配置沒有問題,則有可能與HikariCP無關。這個錯誤的產生原因就是請求向池中borrow時,沒有可用連接超時導致。第一點,此時我們要思考我們的連接池數量設置是否合理,與業務量相關;第二點,看我們代碼是否存在慢sql;第三點,與使用的持久層框架有關,分析我們的連接到底是被誰所持有,它的連接管理方法是怎么樣的,什么情況下才會歸還連接。

這篇文章就是因為我遇到這個錯而無法定位才決定好好研究下的,我的這個錯誤產生的原因就是上面的第三點,我的項目采用的是jpa做數據庫交互,且是一個非常簡單的單表查詢,連接應該很快歸還池中才對,但是經過我的測試,當經過數據庫查詢以后,連接并沒有被釋放,反而是在我的整個會話結束后,才會歸還連接。

jpa的核心是hibernate-core,在網上查詢了hibernate的連接釋放策略,知道了原因。hibernate 中連接釋放的策略hibernate. connection. release_ mode有以下四種屬性:

on_close,當Session被顯式關閉或被斷開連接時,才會釋放JDBC連接

after_transaction,每次事務結束都會釋放鏈接

after_statement,在每次JDBC調用后,都會主動的釋放連接

auto,為JTA和CMT事務策略選擇after_statement, 為JDBC事務策略選擇after_transaction

我的springboot項目版本為1.x,即便將hibernate-core升級到較高版本,并開啟事物,也還是基于on_close方式去釋放連接;我測試了2.x版本的,不開啟事物時也是on_close,開啟事物后,就成了after_transaction,具體1.x版本為何開啟事物也不生效還不清楚。

參考資料

流程圖的方式講解,很清晰易懂,版本也比較高

HikariCP是如何管理數據庫連接的?

關于找一找教程網

本站文章僅代表作者觀點,不代表本站立場,所有文章非營利性免費分享。

本站提供了軟件編程、網站開發技術、服務器運維、人工智能等等IT技術文章,希望廣大程序員努力學習,讓我們用科技改變世界。

[hikari連接池解析(版本:HikariCP-2.5.1.jar)]http://www.zyiz.net/tech/detail-143838.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/259982.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/259982.shtml
英文地址,請注明出處:http://en.pswp.cn/news/259982.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

app開發歷程————Android程序解析服務器端的JSON格式數據,顯示在界面上

上一篇文章寫的是服務器端利用Servlet 返回JSON字符串&#xff0c;本文主要是利用android客戶端訪問服務器端鏈接&#xff0c;解析JSON格式數據&#xff0c;放到相應的位置上。 首先&#xff0c;android程序的布局文件main.xml 1 <LinearLayout xmlns:android"http://s…

Android IOS WebRTC 音視頻開發總結(八十七)-- WebRTC中丟包重傳NACK實現分析

Android IOS WebRTC 音視頻開發總結&#xff08;八十七&#xff09;-- WebRTC中丟包重傳NACK實現分析 本文主要介紹WebRTC中丟包重傳NACK的實現&#xff0c;作者&#xff1a;weizhenwei &#xff0c;文章最早發表在編風網&#xff0c;微信ID&#xff1a;befoio 支持原創&#x…

如何去除TD之間的空隙

table{border-collapse:collapse;}轉載于:https://www.cnblogs.com/passer1991/archive/2013/02/27/2935967.html

android切換到上個頁面,Android 返回上一個界面刷新數據

有些界面需要返回上一個界面刷刷新數據,再此做個記錄.首先startActivityForResult進行Actvity進行跳轉,這是跳轉前的界面.// 通過 startActivityForResult() 啟動 ActivityBIntent intent new Intent(getActivity(), NoticeActivity.class);startActivityForResult(intent, 1)…

composer設置代理_composer 設置代理

Docker registry V2部署私有Docker Registry 搭建 Insecure Registry 修改Registry server上的Docker daemon的配置,為DOCKER_OPTS增加–insecure ...css中的position&colon;relative和absolute 屬性語法: position : static | absolute | fixed | relative 取值: static :…

為網格布局圖片打造的超炫 CSS 加載動畫

今天&#xff0c;我想與大家分享一些專門為網格布局的圖像制作的很酷的 CSS 加載動畫效果。您可以把這些效果用在你的作品集&#xff0c;博客或任何你想要的網頁中。設置很簡單。我們使用了下面這些工具庫來實現這個效果&#xff1a; Normalize.css 來替代傳統的 CSS 復位&…

HTML多選框滾動條,《HTM單選.doc

《HTM單選1. 下面標記中&#xff0c;( )在標記的位置添加一個回車符。【選擇一項】A. B. C. D. 2. 要實現以下功能&#xff1a;在網頁中插入一個圖片joke11.gif,使用者通過單擊該圖片&#xff0c;連接到joke11.htm上去。下面的HTML代碼&#xff0c;( )是正確的。【選擇一項】A…

python時間處理模塊有哪些_Python模塊之時間處理

time 模塊>>> import time>>> dir(time)[__doc__, __name__, __package__, accept2dyear, altzone, asctime, clock, ctime, daylight, gmtime, localtime, mktime, sleep, strftime,strptime, struct_time, time, timezone, tzname]包含的變量:timezone -- …

wel

歡迎來到mathant.com 這個網站是什么 這個網站是我搭建在阿里云vps上的個人網站。目前的用途是充當個人博客和云存儲&#xff0c;當然它的功能不止如此。我會在以后的日子里完善他&#xff0c;希望他能變得更好。目前我在主機上只搭建了這個個人博客和一個ftp服務器。這個網站采…

php 安裝rabbitmq擴展無報錯版

需要安裝rabbitmq-c&#xff0c;rabbitmq-c是一個用于C語言的&#xff0c;與AMQP server進行交互的client庫。下載了v0.5.2版本(https://github.com/alanxz/rabbitmq-c/releases/download/v0.5.2/rabbitmq-c-0.5.2.tar.gztar xvf rabbitmq-c-0.5.2.tarcd rabbitmq-c-0.5.2autor…

ImageMagick 打水印支持透明度設置

convert 35021021120924162418300.jpg DD.png -geometry 60002048 -compose dissolve -define compose:args50 -composite -quality 95 35021021120924162418300_res.jpg轉載于:https://www.cnblogs.com/mfryf/archive/2013/03/04/2943209.html

spring mvc使用html頁面,使用Spring MVC的純HTML頁面應用程序

在Spring MVC所有的請求經過FrontController - DispatcherServlet的有你需要告訴Spring allowe JSP和HTML都在你的情況例dispatcher-servlet.xml:xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:p"http://www.springframework.org/schema/p"x…

python win+r時不成功_Win與R(不使用Anaconda的情況下)

配置R的路徑信息&#xff1a;Path&#xff1a;添加R.dll的路徑 如&#xff1a;E:\software\R\R-3.5.1\bin\x64R_HOME:R的安裝路徑 如&#xff1a;E:\software\R\R-3.5.1\binR_USER:R的使用路徑 如&#xff1a;E:\software\R\R-3.5.1\bin\x64pip install rpy2在win下安裝失敗&am…

Devexpress VCL Build v2014 vol 14.1.4 發布

雖然這次沒加什么新東西&#xff0c;但是及時更新支持xe7&#xff0c;還算可以。 Whats New in 14.1.4 (VCL Product Line) New Major Features in 14.1 Whats New in VCL Products 14.1 Feature Highlights To learn about feature highlights in this version, please refer …

一個YII社區學習網站

2019獨角獸企業重金招聘Python工程師標準>>> https://getyii.com/ 轉載于:https://my.oschina.net/u/2552765/blog/803311

一站式 Java Web 框架 firefly-2.0_07發布

Firefly是一個高性能一站式Web框架。 涵蓋了web開發的主要技術棧。 包含Template engine、IOC、MVC framework、HTTP Server、Common tools、Log、Json parser等模塊。 firefly-2.0_07修復了模版壓縮對javascript單行注釋的影響&#xff0c;并新增了自定義錯誤頁面功能。 更新日…

計算機控制學什么,計算機控制技術專業介紹

專業前景需要早了解&#xff0c;計算機控制技術專業學什么&#xff0c;好不好找工作等是學子和家長朋友們十分關心的問題。以下是個人簡歷網整理的計算機控制技術專業介紹、主要課程、培養目標、就業前景&#xff0c;供大家參考。1、計算機控制技術專業簡介計算機控制技術專業&…

【Python】Python 批量轉換PDF到Excel

PDF是面向展示和打印使用的&#xff0c;并未考慮編輯使用&#xff0c;所以缺少了很多編輯屬性且非常難修改PDF里面的數據。當您需要分析或修改PDF文檔數據時&#xff0c;可以將PDF保存為Excel工作簿&#xff0c;實現輕松編輯數據的需求。PDF轉Excel&#xff0c;技術關鍵就是提取…

js showModalDialog參數的使用詳解(轉)

js showModalDialog參數的使用詳解_javascript技巧_腳本之家 http://www.jb51.net/article/45281.htm 本篇文章主要是對js中showModalDialog參數的使用進行了詳細的分析介紹&#xff0c;需要的朋友可以過來參考下&#xff0c;希望對大家有所幫助 基本介紹&#xff1a; showModa…

ad19生成gerber文件_在“AD19”中怎樣將PCB文件轉換為GERBER

四川自貢是歷史悠久的老工業城市&#xff0c;上世紀八、九十年代&#xff0c;自貢的鍋爐、泵業、閥門全國聞名&#xff0c;在近年發展中&#xff0c;電子產業也取得可喜的成績。Altium Designer在設計電子產品中是應用較多的工具&#xff0c;它的版本更新很快&#xff0c;從最早…