javascript
SpringBoot 默认数据库连接池 HikariCP
目錄
?引言
1、問題描述
2、SpringBoot默認的數(shù)據(jù)庫連接池
3、HikariCP是什么
4、測試依賴
5、配置文件
5.1、數(shù)據(jù)庫連接參數(shù)
5.2、連接池數(shù)據(jù)基本參數(shù)
5.3、連接檢查參數(shù)
5.4、事務相關參數(shù)
5.5、JMX參數(shù)
6、HikariCP源碼淺析
6.1、HikariConfig--連接池配置的加載
6.2、HikariPool--連接池
1、HikariPool UML圖
2、PoolBase
3、HikariPool
4、如何獲取一個鏈接對象
6.3、ConcurrentBag--更少的鎖沖突
7、HikariCP為什么快?
7.1、通過代碼設計和優(yōu)化大幅減少線程間的鎖競爭
7.2、引入了更多 JDK 的特性
7.3、使用 javassist 直接修改 class 文件生成動態(tài)代理
8、JDK 、CGLib 、ASM 、Javassist 性能測試
1、測試代碼
2、測試結果
?引言
????????咱們開發(fā)項目的過程中用到很多的開源數(shù)據(jù)庫鏈接池,比如druid、c3p0、BoneCP等等,前端時間在部署新服務的時候發(fā)現(xiàn)了個問題,排查完畢問題正好學習學習SpringBoot的默認的數(shù)據(jù)庫連接池HikariCP的一些知識。HikariCP官網(wǎng)地址:?https://github.com/brettwooldridge/HikariCP
1、問題描述
? ? ? ? 我們新項目部署上線之后在觀察日志的時候發(fā)現(xiàn)了這個警告,經(jīng)過排查是發(fā)現(xiàn)DB方面的問題,保留現(xiàn)場如下。
2、SpringBoot默認的數(shù)據(jù)庫連接池
????????Spring-Boot-2.0.0-M1版本將默認的數(shù)據(jù)庫連接池從tomcat jdbc pool改為了HikariCP。
3、HikariCP是什么
????????HikariCP 是用于創(chuàng)建和管理連接,利用“池”的方式復用連接減少資源開銷,和其他數(shù)據(jù)源一樣,也具有連接數(shù)控制、連接可靠性測試、連接泄露控制、緩存語句等功能,另外,和 druid 一樣,HikariCP 也支持監(jiān)控功能。
????????HikariCP 是目前最快的連接池,就連風靡一時的 BoneCP 也停止維護,主動讓位給它,SpringBoot 也把它設置為默認連接池。
4、測試依賴
? ? ? ? 既然官網(wǎng)說HikariCP是最快的數(shù)據(jù)庫連接池,不妨我們進行一些嘗試,驗證一下官網(wǎng)放出的狠話。驗證也比較簡單,只需要在項目中添加依賴即可。
<!-- JNDI數(shù)據(jù)源 --><resource-ref><res-ref-name>jdbc/hikariCP-test</res-ref-name><res-type>javax.sql.DataSource</res-type><res-auth>Container</res-auth></resource-ref>5、配置文件
? ? ? ? 上面一步添加完依賴,接下來具體實操之前先了解一下HikariCP的各種配置信息。
5.1、數(shù)據(jù)庫連接參數(shù)
????????注意,這里url在后面拼接了多個參數(shù)用于避免亂碼、時區(qū)報錯問題。
#-------------基本屬性-------------------------------- jdbcUrl=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true username=root password=root #JDBC驅動使用的Driver實現(xiàn)類類名 #默認為空。會根據(jù)jdbcUrl來解析 driverClassName=com.mysql.cj.jdbc.Driver5.2、連接池數(shù)據(jù)基本參數(shù)
#-------------連接池大小相關參數(shù)-------------------------------- #最大連接池數(shù)量 #默認為10。可通過JMX動態(tài)修改 maximumPoolSize=10#最小空閑連接數(shù)量 #默認與maximumPoolSize一致。可通過JMX動態(tài)修改 minimumIdle=05.3、連接檢查參數(shù)
????????注意:針對連接失效的問題,HikariCP 強制開啟借出測試和空閑測試,不開啟回收測試,可選的只有泄露測試。所有的超時時間都可以根據(jù)JMX設置。
#-------------連接檢測情況-------------------------------- #用來檢測連接是否有效的sql,要求是一個查詢語句,常用select 'x' #如果驅動支持JDBC4,建議不設置,因為這時默認會調用Connection.isValid()方法來檢測,該方式效率會更高 #默認為空 connectionTestQuery=select 1 from dual#檢測連接是否有效的超時時間,單位毫秒 #最小允許值250 ms #默認5000 ms。 validationTimeout=5000#連接保持空閑而不被驅逐的最小時間。單位毫秒。 #該配置只有再minimumIdle < maximumPoolSize才會生效,最小允許值為10000 ms。 #默認值10000*60 = 10分鐘。 idleTimeout=600000#連接對象允許“泄露”的最大時間。單位毫秒 #最小允許值為2000 ms。 #默認0,表示不開啟泄露檢測。 leakDetectionThreshold=0#連接最大存活時間。單位毫秒 #最小允許值30000 ms #默認30分鐘。可通過JMX動態(tài)修改 maxLifetime=1800000#獲取連接時最大等待時間,單位毫秒 #獲取時間超過該配置,將拋出異常。最小允許值250 ms #默認30000 ms。 connectionTimeout=3000005.4、事務相關參數(shù)
#-------------事務相關的屬性-------------------------------- #當連接返回池中時是否設置自動提交 #默認為true autoCommit=true#當連接從池中取出時是否設置為只讀 #默認值false readOnly=false#連接池創(chuàng)建的連接的默認的TransactionIsolation狀態(tài) #可用值為下列之一:NONE,TRANSACTION_READ_UNCOMMITTED, TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE #默認值為空,由驅動決定 transactionIsolation=TRANSACTION_REPEATABLE_READ5.5、JMX參數(shù)
#-------------JMX--------------------------------#是否允許通過JMX掛起和恢復連接池 #默認為false allowPoolSuspension=false#是否開啟JMX #默認false registerMbeans=true#數(shù)據(jù)源名。 #默認自動生成 poolName=6、HikariCP源碼淺析
6.1、HikariConfig--連接池配置的加載
????????在HikariCP 中,HikariConfig用于加載配置,它的加載要更加簡潔。直接從PropertyElf.setTargetFromProperties(Object, Properties)方法開始看。
// 這個方法就是將properties的參數(shù)設置到HikariConfig中 public static void setTargetFromProperties(final Object target, final Properties properties) {if (target == null || properties == null) {return;}// 在這里會利用反射獲取List<Method> methods = Arrays.asList(target.getClass().getMethods());// 遍歷properties.forEach((key, value) -> {if (target instanceof HikariConfig && key.toString().startsWith("dataSource.")) {// 如果是dataSource.*的參數(shù),直接加入到dataSourceProperties屬性((HikariConfig) target).addDataSourceProperty(key.toString().substring("dataSource.".length()), value);}else {// 如果不是,則通過set方法設置setProperty(target, key.toString(), value, methods);}}); } private static void setProperty(final Object target, final String propName, final Object propValue, final List<Method> methods) {final Logger logger = LoggerFactory.getLogger(PropertyElf.class);// use the english locale to avoid the infamous turkish locale bug// 拼接參數(shù)的setter方法名 首字母大寫String methodName = "set" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1);// 獲取對應的Method 對象Method writeMethod = methods.stream().filter(m -> m.getName().equals(methodName) && m.getParameterCount() == 1).findFirst().orElse(null);// 如果不存在,按另一套規(guī)則拼接參數(shù)的setter方法名 全部大寫if (writeMethod == null) {String methodName2 = "set" + propName.toUpperCase(Locale.ENGLISH);writeMethod = methods.stream().filter(m -> m.getName().equals(methodName2) && m.getParameterCount() == 1).findFirst().orElse(null);}// 如果該參數(shù)setter方法不存在,則拋出異常,從這里可以看出,HikariCP 中不能存在配錯參數(shù)名的情況if (writeMethod == null) {logger.error("Property {} does not exist on target {}", propName, target.getClass());throw new RuntimeException(String.format("Property %s does not exist on target %s", propName, target.getClass()));}// 調用setter方法來配置具體參數(shù)。try {Class<?> paramClass = writeMethod.getParameterTypes()[0];if (paramClass == int.class) {writeMethod.invoke(target, Integer.parseInt(propValue.toString()));}else if (paramClass == long.class) {writeMethod.invoke(target, Long.parseLong(propValue.toString()));}else if (paramClass == boolean.class || paramClass == Boolean.class) {writeMethod.invoke(target, Boolean.parseBoolean(propValue.toString()));}else if (paramClass == String.class) {writeMethod.invoke(target, propValue.toString());}else {try {logger.debug("Try to create a new instance of \"{}\"", propValue.toString());writeMethod.invoke(target, Class.forName(propValue.toString()).newInstance());}catch (InstantiationException | ClassNotFoundException e) {logger.debug("Class \"{}\" not found or could not instantiate it (Default constructor)", propValue.toString());writeMethod.invoke(target, propValue);}}}catch (Exception e) {logger.error("Failed to set property {} on target {}", propName, target.getClass(), e);throw new RuntimeException(e);} }6.2、HikariPool--連接池
????????HikariPool 是一個非常重要的類,它負責管理連接。
1、HikariPool UML圖
HikariPoolMXBean:采用JMX控制HikariPool的入口。
/*** The javax.management MBean for a Hikari pool instance.** @author Brett Wooldridge*/ public interface HikariPoolMXBean2、PoolBase
????????HikariPool鏈接池的配置信息。
3、HikariPool
????????連接池的管理。
屬性:
//配置信息。 public final HikariConfig config; //指標記錄器包裝類。HikariCP支持Metrics監(jiān)控 IMetricsTrackerDelegate metricsTracker; //創(chuàng)建新連接的任務,Callable實現(xiàn)類。一般調用一次創(chuàng)建一個連接 private final PoolEntryCreator poolEntryCreator = new PoolEntryCreator(null /*logging prefix*/); //創(chuàng)建新連接的任務,Callable實現(xiàn)類。一般調用一次創(chuàng)建一個連接,與前者區(qū)別在于它創(chuàng)建最后一個連接,會打印日志 private final PoolEntryCreator postFillPoolEntryCreator = new PoolEntryCreator("After adding "); private final Collection<Runnable> addConnectionQueueReadOnlyView; //執(zhí)行PoolEntryCreator任務的線程池。以addConnectionQueueReadOnlyView作為等待隊列 private final ThreadPoolExecutor addConnectionExecutor; //執(zhí)行關閉連接的線程池 private final ThreadPoolExecutor closeConnectionExecutor; //用于執(zhí)行HouseKeeper(連接檢測任務和維持連接池大小)等任務 private final ScheduledExecutorService houseKeepingExecutorService; //存放連接對象的包。用于borrow、requite、add和remove對象。 private final ConcurrentBag<PoolEntry> connectionBag;4、如何獲取一個鏈接對象
/*** Get a connection from the pool, or timeout after the specified number of milliseconds.** @param hardTimeout the maximum time to wait for a connection from the pool* @return a java.sql.Connection instance* @throws SQLException thrown if a timeout occurs trying to obtain a connection*/ public Connection getConnection(final long hardTimeout) throws SQLException {// 如果我們設置了allowPoolSuspension為true,則這個鎖會生效,這個是基于信號量的鎖 MAX_PERMITS = 10000,正常情況不會用完,除非你掛起了連接池(通過JMX等方式),10000個permits會被消耗完suspendResumeLock.acquire();final long startTime = currentTime();try {// 剩余超時時間long timeout = hardTimeout;// 循環(huán)獲取,除非獲取到了連接或者超時do {// 從ConcurrentBag中拿出一個元素PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);// 前面說過,只有超時情況才會返回空,這時會跳出循環(huán)并拋出異常if (poolEntry == null) {break; // We timed out... break and throw exception}final long now = currentTime();// 如果// 1、元素被標記為丟棄// 2、空閑時間過長// 3、連接無效則會丟棄該元素// 1&2&3 --> 4、并關閉連接if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);timeout = hardTimeout - elapsedMillis(startTime);}else {metricsTracker.recordBorrowStats(poolEntry, startTime);// 創(chuàng)建Connection代理類,該代理類就是使用Javassist生成的return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);}} while (timeout > 0L);metricsTracker.recordBorrowTimeoutStats(startTime);// 超時拋出異常throw createTimeoutException(startTime);}catch (InterruptedException e) {Thread.currentThread().interrupt();throw new SQLException(poolName + " - Interrupted during connection acquisition", e);}finally {// 釋放一個permitsuspendResumeLock.release();} }6.3、ConcurrentBag--更少的鎖沖突
????????在 HikariCP 中ConcurrentBag用于存放PoolEntry對象(封裝了Connection對象,IConcurrentBagEntry實現(xiàn)類),本質上可以將它就是一個資源池。
?屬性:
//存放著當前線程返還的PoolEntry對象。如果當前線程再次借用資源,會先從這個列表中獲取。注意,這個列表的元素可以被其他線程“偷走” private final ThreadLocal<List<Object>> threadList; //添加元素的監(jiān)聽器,由HikariPool實現(xiàn),在該實現(xiàn)中,如果waiting - addConnectionQueue.size() >= 0,則會讓addConnectionExecutor執(zhí)行PoolEntryCreator任務 private final IBagStateListener listener; //當前等待獲取鏈接的線程數(shù) private final AtomicInteger waiters; //元素是否使用弱引用 private final boolean weakThreadLocals; //這是一個無容量的阻塞隊列,每個插入操作需要阻塞等待刪除操作,而刪除操作不需要等待,如果沒有元素插入,會返回null,如果設置了超時時間則需要等待。 private final SynchronousQueue<T> handoffQueue; //存放著狀態(tài)為使用中、未使用和保留三種狀態(tài)的PoolEntry對象。注意,CopyOnWriteArrayList是一個線程安全的集合,在每次寫操作時都會采用復制數(shù)組的方式來增刪元素,讀和寫使用的是不同的數(shù)組,避免了鎖競爭 private final CopyOnWriteArrayList<T> sharedList;方法:
????????在以下方法中,唯一可能出現(xiàn)線程切換到就是handoffQueue.poll(timeout, NANOSECONDS)。
/*** The method will borrow a BagEntry from the bag, blocking for the* specified timeout if none are available.** @param timeout how long to wait before giving up, in units of unit* @param timeUnit a <code>TimeUnit</code> determining how to interpret the timeout parameter* @return a borrowed instance from the bag or null if a timeout occurs* @throws InterruptedException if interrupted while waiting*/ public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {// 1. 首先從threadList獲取對象// Try the thread-local list first// 獲取綁定在當前線程的List<Object>對象,注意這個集合的實現(xiàn)一般為FastList,這是HikariCP自己實現(xiàn)的final List<Object> list = threadList.get();for (int i = list.size() - 1; i >= 0; i--) {// 獲取當前元素,并將它從集合中刪除final Object entry = list.remove(i);@SuppressWarnings("unchecked")// 如果設置了weakThreadLocals,則存放的是WeakReference對象final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;// 采用CAS方式將獲取的對象狀態(tài)由未使用改為使用中,如果失敗說明其他線程正在使用它。if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {return bagEntry;}}// 2.如果還沒獲取到,會從sharedList中獲取對象// Otherwise, scan the shared list ... then poll the handoff queue// 等待獲取連接的線程數(shù)+1final int waiting = waiters.incrementAndGet();try {// 遍歷sharedListfor (T bagEntry : sharedList) {// 采用CAS方式將獲取的對象狀態(tài)由未使用改為使用中,如果當前元素正在使用,則無法修改成功,進入下一循環(huán)if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {// If we may have stolen another waiter's connection, request another bag add.if (waiting > 1) {// 通知監(jiān)聽器添加包元素。如果waiting - addConnectionQueue.size() >= 0,則會讓addConnectionExecutor執(zhí)行PoolEntryCreator任務listener.addBagItem(waiting - 1);}return bagEntry;}}// 通知監(jiān)聽器添加包元素listener.addBagItem(waiting);// 3.如果還沒獲取到,會輪訓進入handoffQueue隊列獲取連接對象timeout = timeUnit.toNanos(timeout);do {final long start = currentTime();// 從handoffQueue隊列中獲取并刪除元素。這是一個無容量的阻塞隊列,插入操作需要阻塞等待刪除操作,而刪除操作不需要等待,如果沒有元素插入,會返回null,如果設置了超時時間則需要等待final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);// 這里會出現(xiàn)三種情況,// 1.超時,返回null// 2.獲取到元素,但狀態(tài)為正在使用,繼續(xù)執(zhí)行// 3.獲取到元素,元素狀態(tài)未未使用,修改未使用并返回if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {return bagEntry;}timeout -= elapsedNanos(start);} while (timeout > 10_000);// 超時返回nullreturn null;}finally {// 等待獲取連接的線程數(shù)-1waiters.decrementAndGet();} }7、HikariCP為什么快?
7.1、通過代碼設計和優(yōu)化大幅減少線程間的鎖競爭
????????1、元素狀態(tài)的引入,以及使用CAS方法修改狀態(tài)。在ConcurrentBag中,使用使用中、未使用、刪除和保留等表示元素的狀態(tài),而不是使用不同的集合來維護不同狀態(tài)的元素。元素狀態(tài)這一概念的引入非常關鍵,為后面的幾點提供了基礎。 ConcurrentBag的方法中多處調用 CAS 方法來判斷和修改元素狀態(tài),這一過程不需要加鎖。
????????2、threadList 的使用。當前線程歸還的元素會被綁定到ThreadLocal,該線程再次獲取元素時,在該元素未被偷走的前提下可直接獲取到,不需要去 sharedList 遍歷獲取;
7.2、引入了更多 JDK 的特性
????????尤其是 concurrent 包的工具。相比較于DBCP、C3P0等數(shù)據(jù)庫鏈接池問世較晚,很方便的享受JDK的升級帶來的方便。
????????1、采用CopyOnWriteArrayList來存放元素。在CopyOnWriteArrayList中,讀和寫使用的是不同的數(shù)組,避免了兩者的鎖競爭,至于多個線程寫入,則會加 ReentrantLock 鎖。
????????2、sharedList 的讀寫控制。borrow 和 requite 對 sharedList 來說都是不加鎖的,缺點就是會犧牲一致性。用戶線程無法進行增加元素的操作,只有 addConnectionExecutor 可以,而 addConnectionExecutor 只會開啟一個線程執(zhí)行任務,所以 add 操作不會存在鎖競爭。至于 remove 是唯一會造成鎖競爭的方法,這一點我認為也可以參照 addConnectionExecutor 來處理,在加入任務隊列前把 PoolEntry 的狀態(tài)標記為刪除中。
7.3、使用 javassist 直接修改 class 文件生成動態(tài)代理
? ? ? ? 1、使用 javassist 直接修改 class 文件生成動態(tài)代理,精簡了很多不必要的字節(jié)碼,提高代理方法運行速度。尤其JDK1.8優(yōu)化以后JDK的動態(tài)代理,CGlib代理已經(jīng)和javassist、asm等一個數(shù)量級。
8、JDK 、CGLib 、ASM 、Javassist 性能測試
????????環(huán)境:JDK 1.8,CGLib 3.3.0, ASM JDK自帶的ASM包,Javassist 3.26.0-GA。
????????數(shù)據(jù)為執(zhí)行三次,每次調用5千萬次代理方法的結果。
1、測試代碼
package cn.zzs.proxy;import javassist.*; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyFactory; import javassist.util.proxy.ProxyObject; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.FieldVisitor; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.text.DecimalFormat;/*** @author lly**/ public class App {public static void main(String[] args) throws Exception {CountService delegate = new CountServiceImpl();long time = System.currentTimeMillis();CountService jdkProxy = createJdkDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create JDK Proxy: " + time + " ms");time = System.currentTimeMillis();CountService cglibProxy = createCglibDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create CGLIB Proxy: " + time + " ms");time = System.currentTimeMillis();CountService javassistProxy = createJavassistDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create JAVAASSIST Proxy: " + time + " ms");time = System.currentTimeMillis();CountService javassistBytecodeProxy = createJavassistBytecodeDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create JAVAASSIST Bytecode Proxy: " + time + " ms");time = System.currentTimeMillis();CountService asmBytecodeProxy = createAsmBytecodeDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create ASM Proxy: " + time + " ms");System.out.println("================");for (int i = 0; i < 3; i++) {test(jdkProxy, "Run JDK Proxy: ");test(cglibProxy, "Run CGLIB Proxy: ");test(javassistProxy, "Run JAVAASSIST Proxy: ");test(javassistBytecodeProxy, "Run JAVAASSIST Bytecode Proxy: ");test(asmBytecodeProxy, "Run ASM Bytecode Proxy: ");System.out.println("----------------");}}private static void test(CountService service, String label)throws Exception {service.count(); // warm upint count = 50000000;long time = System.currentTimeMillis();for (int i = 0; i < count; i++) {service.count();}time = System.currentTimeMillis() - time;System.out.println(label + time + " ms, " + new DecimalFormat().format(count / time * 1000) + " t/s");}private static CountService createJdkDynamicProxy(final CountService delegate) {CountService jdkProxy = (CountService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{CountService.class}, new JdkHandler(delegate));return jdkProxy;}private static class JdkHandler implements InvocationHandler {final Object delegate;JdkHandler(Object delegate) {this.delegate = delegate;}public Object invoke(Object object, Method method, Object[] objects)throws Throwable {return method.invoke(delegate, objects);}}private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {Enhancer enhancer = new Enhancer();enhancer.setCallback(new CglibInterceptor(delegate));enhancer.setInterfaces(new Class[]{CountService.class});CountService cglibProxy = (CountService) enhancer.create();return cglibProxy;}private static class CglibInterceptor implements MethodInterceptor {final Object delegate;CglibInterceptor(Object delegate) {this.delegate = delegate;}public Object intercept(Object object, Method method, Object[] objects,MethodProxy methodProxy) throws Throwable {return methodProxy.invoke(delegate, objects);}}private static CountService createJavassistDynamicProxy(final CountService delegate) throws Exception {ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setInterfaces(new Class[]{CountService.class});Class<?> proxyClass = proxyFactory.createClass();CountService javassistProxy = (CountService) proxyClass.newInstance();((ProxyObject) javassistProxy).setHandler(new JavaAssitInterceptor(delegate));return javassistProxy;}private static class JavaAssitInterceptor implements MethodHandler {final Object delegate;JavaAssitInterceptor(Object delegate) {this.delegate = delegate;}public Object invoke(Object self, Method m, Method proceed,Object[] args) throws Throwable {return m.invoke(delegate, args);}}private static CountService createJavassistBytecodeDynamicProxy(CountService delegate) throws Exception {ClassPool mPool = new ClassPool(true);CtClass mCtc = mPool.makeClass(CountService.class.getName() + "JavaassistProxy");mCtc.addInterface(mPool.get(CountService.class.getName()));mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));mCtc.addField(CtField.make("public " + CountService.class.getName() + " delegate;", mCtc));mCtc.addMethod(CtNewMethod.make("public int count() { return delegate.count(); }", mCtc));Class<?> pc = mCtc.toClass();CountService bytecodeProxy = (CountService) pc.newInstance();Field filed = bytecodeProxy.getClass().getField("delegate");filed.set(bytecodeProxy, delegate);return bytecodeProxy;}private static CountService createAsmBytecodeDynamicProxy(CountService delegate) throws Exception {ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);String className = CountService.class.getName() + "AsmProxy";String classPath = className.replace('.', '/');String interfacePath = CountService.class.getName().replace('.', '/');classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, classPath, null, "java/lang/Object", new String[]{interfacePath});MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);initVisitor.visitCode();initVisitor.visitVarInsn(Opcodes.ALOAD, 0);initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");initVisitor.visitInsn(Opcodes.RETURN);initVisitor.visitMaxs(0, 0);initVisitor.visitEnd();FieldVisitor fieldVisitor = classWriter.visitField(Opcodes.ACC_PUBLIC, "delegate", "L" + interfacePath + ";", null, null);fieldVisitor.visitEnd();MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "count", "()I", null, null);methodVisitor.visitCode();methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classPath, "delegate", "L" + interfacePath + ";");methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfacePath, "count", "()I");methodVisitor.visitInsn(Opcodes.IRETURN);methodVisitor.visitMaxs(0, 0);methodVisitor.visitEnd();classWriter.visitEnd();byte[] code = classWriter.toByteArray();CountService bytecodeProxy = (CountService) new ByteArrayClassLoader().getClass(className, code).newInstance();Field filed = bytecodeProxy.getClass().getField("delegate");filed.set(bytecodeProxy, delegate);return bytecodeProxy;}private static class ByteArrayClassLoader extends ClassLoader {public ByteArrayClassLoader() {super(ByteArrayClassLoader.class.getClassLoader());}public synchronized Class<?> getClass(String name, byte[] code) {if (name == null) {throw new IllegalArgumentException("");}return defineClass(name, code, 0, code.length);}}}2、測試結果
Create JDK Proxy: 9 ms Create CGLIB Proxy: 149 ms Create JAVAASSIST Proxy: 115 ms Create JAVAASSIST Bytecode Proxy: 58 ms Create ASM Proxy: 1 ms ================ Run JDK Proxy: 479 ms, 104,384,000 t/s Run CGLIB Proxy: 541 ms, 92,421,000 t/s Run JAVAASSIST Proxy: 754 ms, 66,312,000 t/s Run JAVAASSIST Bytecode Proxy: 194 ms, 257,731,000 t/s Run ASM Bytecode Proxy: 202 ms, 247,524,000 t/s ---------------- Run JDK Proxy: 404 ms, 123,762,000 t/s Run CGLIB Proxy: 325 ms, 153,846,000 t/s Run JAVAASSIST Proxy: 681 ms, 73,421,000 t/s Run JAVAASSIST Bytecode Proxy: 179 ms, 279,329,000 t/s Run ASM Bytecode Proxy: 180 ms, 277,777,000 t/s ---------------- Run JDK Proxy: 381 ms, 131,233,000 t/s Run CGLIB Proxy: 339 ms, 147,492,000 t/s Run JAVAASSIST Proxy: 674 ms, 74,183,000 t/s Run JAVAASSIST Bytecode Proxy: 179 ms, 279,329,000 t/s Run ASM Bytecode Proxy: 181 ms, 276,243,000 t/s ----------------資料:
動態(tài)代理方案性能對比 - 梁飛的博客 - ITeye博客
GitHub - wwadge/bonecp: BoneCP is a Java JDBC connection pool implementation that is tuned for high performance by minimizing lock contention to give greater throughput for your applications. It beats older connection pools such as C3P0 and DBCP but SHOULD NOW BE CONSIDERED DEPRECATED in favour of HikariCP.
GitHub - brettwooldridge/HikariCP: 光 HikariCP?A solid, high-performance, JDBC connection pool at last.
JDK動態(tài)代理與CGLib動態(tài)代理相關問題_程序員面試經(jīng)驗分享的博客-CSDN博客
02Hikari源碼解析之ConcurrentBag、FastList分析_concurrentbag解析_一直打鐵的博客-CSDN博客
數(shù)據(jù)庫連接池性能比對(hikari druid c3p0 dbcp jdbc)_c3p0和hikari那個好_把酒問天的博客-CSDN博客
https://www.cnblogs.com/flyingeagle/articles/7102282.html
使用Javassist來動態(tài)創(chuàng)建,修改和代理類 - 算法之名的個人空間 - OSCHINA - 中文開源技術交流社區(qū)
總結
以上是生活随笔為你收集整理的SpringBoot 默认数据库连接池 HikariCP的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 想知道未来孩子长相?Python人脸融合
- 下一篇: 月考勤报表的TSQL查询