假日尾声:技术进阶与自我反思
前言
于是,假日迎来了它的尾声,把快乐和焦躁都留存在昨天。
我只觉情感的自相矛盾在加重,学习让我焦躁,纵欲无法填补空虚,于是我的心被拖入了无止尽的拉扯中。
我还没有找到必须留存的理由,但反之而言,我也远没有到可以决定自己生命自由的时刻。
还是闭上双眼,向前走吧,明日难测,却因昨日懊悔。
今天来统计一下剩余的进阶要求和已经完成的进阶要求,再针对性地完成。
日程
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);}
}
结语
。