假日尾聲:技術進階與自我反思
前言
于是,假日迎來了它的尾聲,把快樂和焦躁都留存在昨天。
我只覺情感的自相矛盾在加重,學習讓我焦躁,縱欲無法填補空虛,于是我的心被拖入了無止盡的拉扯中。
我還沒有找到必須留存的理由,但反之而言,我也遠沒有到可以決定自己生命自由的時刻。
還是閉上雙眼,向前走吧,明日難測,卻因昨日懊悔。
今天來統計一下剩余的進階要求和已經完成的進階要求,再針對性地完成。
日程
9點半,開始學習吧。
- 上午:連接池動態擴縮容問題弄了一早上
- 下午:完成了簡單結果集映射
- 晚上8點:做完結果集相關的blog
- 嘻嘻,五一的學科作業還沒寫,我完蛋了。
學習內容
省流
- 連接池動態擴縮容
- 項目階段性進度檢查
- 簡單結果集映射
1. 連接池動態擴縮容
1)動態擴容
在等待線程大于最大等待線程值時,臨時將線程池的大小擴大1.5倍。
if (rawConn == null && waitCount.get() > hakimiConfig.getMaxWaitThreads()) {int temporaryMax = (int) (hakimiConfig.getMaxSize() * 1.5); // 擴容上限:maxSize 的 1.5 倍int current;do {current = createdCount.get();if (current >= temporaryMax) {break; // 已達到擴容上限}} while (!createdCount.compareAndSet(current, current + 1));if (current < temporaryMax) {log.warn("已啟用動態擴容");try {rawConn = createPhysicalConnection();} catch (SQLException e) {createdCount.decrementAndGet(); // 創建失敗時回滾計數器throw e;}}
}
2)動態縮容
這里使用了 ScheduledExecutorService
定時任務調度器,scheduleWithFixedDelay
會周期性地觸發里面的回調函數。
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
// 啟動縮容任務(每5分鐘檢查一次)
public void startShrinkTask() {scheduler.scheduleWithFixedDelay(() -> {try {int currentIdle = idleConnections.size();int minIdle = hakimiConfig.getMinIdle();// 僅當空閑連接 > minIdle 時才嘗試縮容if (currentIdle <= minIdle) {return;}// 計算最多可回收的連接數(避免過度縮容)int maxShrink = currentIdle - minIdle;int shrunk = 0;while (shrunk < maxShrink) {// 非阻塞取出連接(避免長時間鎖住隊列)Connection conn = idleConnections.poll(10, TimeUnit.MILLISECONDS);if (conn == null) {break; // 隊列已空}// 檢查是否超時if (isIdleTimeout(conn, hakimiConfig.getIdleTimeoutMillis())) {closeConnection(conn);createdCount.decrementAndGet();log.warn("關閉空閑連接");shrunk++;} else {// 未超時,放回隊列(避免誤殺)idleConnections.offer(conn);}}} catch (Exception e) {log.error("Shrink task error", e);}}, 300, 300, TimeUnit.SECONDS);
}// 檢查連接是否空閑超時
private boolean isIdleTimeout(Connection conn, long idleTimeoutMillis) {if (conn instanceof Proxy) {InvocationHandler handler = Proxy.getInvocationHandler(conn);if (handler instanceof HakimiConnectionPool.ConnectionInvocationHandler) {long idleTime = System.currentTimeMillis() -((HakimiConnectionPool.ConnectionInvocationHandler) handler).lastUsedTime;return idleTime > idleTimeoutMillis;}}return false;
}
2. 項目階段性進度檢查
已完成進階需求
- Maven 分模塊化構建項目。
- 完成數據庫連接池,支持動態擴縮容(c3p0、druid)(并發安全(hard))。
- 將常量配置轉移到
yml
配置文件。 - 輸出日志文件。
- 通過依賴注入(DI)進行控制反轉(IOC),使用全局上下文進行全局對象的對象間依賴注入(使用注解)。
- 實現 AOP 動態代理。
目前需要完成的進階需求
- SQL 構建器 + 自定義 SQL,高兼容性的結果映射。
- 用
DispatcherController
來統一接收數據,并轉發給路徑給對應 Controller 處理。 - 自定義異常拋出,全局異常處理器,過濾器。
- 后端以多線程模式運行,保證并發安全。(考慮方法級鎖)
往后的進階需求
- 統一接收 JSON 格式數據,對數據進行混合加密(RSA + AES)。
- 判題機制,可以運行 C++ 代碼。
- Nginx 部署前端 + Docker 容器技術。
- 將項目部署到公網。
3. 簡單結果集映射
1)關鍵方法:預包裝代理方法
private static <T> BiFunction<ResultSet, Integer, T> createMapper(Class<T> targetClass) {try {// 獲取或創建構造函數句柄Constructor<T> constructor = targetClass.getDeclaredConstructor();constructor.setAccessible(true);MethodHandle constructorHandle = MethodHandles.lookup().unreflectConstructor(constructor); // 使用MethodHandle包裝構造器,比傳統反射調用性能更高// 從緩存獲取或創建字段信息Map<String, MethodHandle> setters = Cache.SETTER_CACHE.computeIfAbsent(targetClass, KatSimpleMapper::createSetters);Map<String, Class<?>> fieldTypes = Cache.FIELD_TYPE_CACHE.computeIfAbsent(targetClass, KatSimpleMapper::getFieldTypes);return (rs, index) -> {try {@SuppressWarnings("unchecked")T instance = (T) constructorHandle.invoke(); // 創建目標對象實例for (Map.Entry<String, MethodHandle> entry : setters.entrySet()) {String fieldName = entry.getKey();String columnName = namingStrategy.convert(fieldName);try {Object value = rs.getObject(columnName);if (value != null) {Class<?> fieldType = fieldTypes.get(fieldName);Object convertedValue = convertType(value, fieldType); // 類型轉換entry.getValue().invoke(instance, convertedValue); // 設置屬性值}} catch (SQLException e) {// 列不存在時跳過}}return instance;} catch (Throwable e) {throw new RuntimeException("Mapping failed for " + targetClass.getName(), e);}};} catch (Exception e) {throw new RuntimeException("Mapper creation failed for " + targetClass.getName(), e);}
}
2)包裝方法:負責將觸發代理方法,將mapper結果映射到結果集中
public static <T> List<T> map(ResultSet rs, Class<T> targetClass) throws SQLException {@SuppressWarnings("unchecked")BiFunction<ResultSet, Integer, T> mapper = (BiFunction<ResultSet, Integer, T>)Cache.MAPPER_CACHE.computeIfAbsent(targetClass, KatSimpleMapper::createMapper);List<T> results = new ArrayList<>();while (rs.next()) {results.add(mapper.apply(rs, 1));}return results;
}
3)輔助方法
- 數據庫字段類型到java字段類型的轉換
private static Object convertType(Object value, Class<?> targetType) {if (value == null) return null;if (targetType.isInstance(value)) return value;// 數值類型轉換if (value instanceof Number number) {if (targetType == Double.class || targetType == double.class) {return number.doubleValue();}if (targetType == Float.class || targetType == float.class) {return number.floatValue();}if (targetType == Integer.class || targetType == int.class) {return number.intValue();}if (targetType == Long.class || targetType == long.class) {return number.longValue();}if (targetType == Short.class || targetType == short.class) {return number.shortValue();}}// 日期類型轉換if (value instanceof java.sql.Date sqlDate) {if (targetType == LocalDate.class) {return sqlDate.toLocalDate();}if (targetType == LocalDateTime.class) {return sqlDate.toLocalDate().atStartOfDay();}}// 布爾類型轉換if (value instanceof Boolean bool) {if (targetType == Integer.class || targetType == int.class) {return bool ? 1 : 0;}if (targetType == Double.class || targetType == double.class) {return bool ? 1.0 : 0.0;}}// 時間戳轉換if (value instanceof Timestamp timestamp) {if (targetType == LocalDateTime.class) {return timestamp.toLocalDateTime();}if (targetType == LocalDate.class) {return timestamp.toLocalDateTime().toLocalDate();}}return value;
}
- 創建setter方法并緩存
private static <T> Map<String, MethodHandle> createSetters(Class<T> targetClass) {try {Map<String, MethodHandle> setters = new HashMap<>();MethodHandles.Lookup lookup = MethodHandles.lookup(); // 獲取MethodHandles.Lookup實例,用于方法/字段查找for (Field field : targetClass.getDeclaredFields()) {try {MethodHandle setter = lookup.unreflectSetter(field); // 嘗試通過標準setter方法獲取MethodHandlesetters.put(field.getName(), setter);} catch (IllegalAccessException e) {// 如果字段沒有setter,嘗試直接設置字段值field.setAccessible(true);MethodHandle setter = lookup.unreflectSetter(field);setters.put(field.getName(), setter);}}return Collections.unmodifiableMap(setters); // 返回不可修改的Map} catch (Exception e) {throw new RuntimeException("Failed to create setters for " + targetClass.getName(), e);}
}
結語
。