#10
规划
现在的路线是黑马的Java + AI 课程部分基本上是差不多
但是我改变思路了 写代码项目 然后向用工具学高级知识点 然后不会的再
问ai 记笔记
然后是这套课程不一样需要学的地方
一个是有三个实际小项目可以做一下
另一个是Java加强篇 也就是高级语法部分也可以看 还有注解 和junit可以看
然后百度网盘资料里 的开发手册也可以了解一下编码规范
其次是还有一个Java的题目可以当做是期末复习题写了
56.动态代理
Java 动态代理是一种运行时机制,允许创建实现特定接口的代理类,用于拦截和增强方法调用。以下是核心要点:
- 核心组件
-
InvocationHandler
:自定义拦截逻辑,实现invoke
方法。
-
Proxy.newProxyInstance
:生成代理对象,需传入类加载器、接口数组和处理器。
- 工作流程
-
- 目标类需实现接口 → 创建
InvocationHandler
→ 动态生成代理对象 → 所有方法调用转发到invoke
。
- 目标类需实现接口 → 创建
- 典型应用
-
- AOP(日志、事务)、RPC、缓存代理。
- 局限
-
- 仅支持接口代理,不支持类代理(可通过 CGLIB 弥补)。
- 优势
-
- 无需编写代理类,运行时动态增强,降低代码耦合。
在企业开发中,Java动态代理的优势显著,主要体现在**解耦性**、**可维护性**和**扩展性**上。以下是具体好处和典型应用场景:
### **一、核心优势**
1. **非侵入式增强**
无需修改原有代码即可添加功能(如日志、事务),符合开闭原则。
2. **横切关注点复用**
将安全校验、缓存、事务等逻辑集中管理,避免代码重复。
3. **松耦合架构**
代理层作为中间件,隔离业务逻辑与系统服务(如监控、审计)。
4. **运行时动态配置**
根据环境或条件动态调整代理行为(如测试环境与生产环境的差异化处理)。
### **二、企业开发中的典型应用**
#### 1. **AOP(面向切面编程)**
- **场景**:日志记录、权限校验、事务管理、性能监控。
- **案例**:
某电商系统需记录所有API调用日志,使用动态代理拦截Controller层方法,自动记录入参、出参和执行时间。
#### 2. **事务管理**
- **场景**:跨多个DAO操作时保证数据一致性。
- **实现**:
```java
// 事务代理处理器
public class TransactionHandler implements InvocationHandler {
private final Object target;
private final TransactionManager txManager;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
txManager.begin(); // 开启事务
try {
Object result = method.invoke(target, args);
txManager.commit(); // 提交事务
return result;
} catch (Exception e) {
txManager.rollback(); // 回滚事务
throw e;
}
}
}
```
#### 3. **远程服务调用(RPC)**
- **场景**:微服务间通信(如Feign Client)。
- **原理**:
通过代理将方法调用转换为HTTP请求,隐藏网络通信细节。
#### 4. **缓存代理**
- **场景**:减少重复查询数据库。
- **实现**:
```java
// 缓存代理示例
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String cacheKey = generateKey(method, args);
Object value = cache.get(cacheKey);
if (value != null) return value; // 缓存命中
value = method.invoke(target, args); // 查询数据库
cache.put(cacheKey, value); // 写入缓存
return value;
}
```
#### 5. **权限控制**
- **场景**:接口访问权限校验。
- **案例**:
在调用用户管理API前,检查当前用户是否有管理员权限。
#### 6. **Mock测试**
- **场景**:单元测试中模拟外部服务。
- **工具**:Mockito、EasyMock等框架基于动态代理实现。
### **三、企业级框架中的应用**
1. **Spring AOP**
- 默认使用JDK动态代理(接口代理),若目标类未实现接口则使用CGLIB。
- 通过`@Before`、`@After`等注解声明切面逻辑。
2. **MyBatis Mapper接口**
- 无需编写实现类,通过动态代理自动生成SQL执行逻辑。
3. **Hystrix熔断**
- 通过代理拦截服务调用,在异常时执行降级逻辑。
4. **分布式追踪(如Zipkin)**
- 通过代理自动注入请求ID,实现全链路日志关联。
### **四、注意事项**
1. **性能开销**:反射调用比直接调用慢,高频接口需权衡。
2. **仅支持接口代理**:若需代理类,可使用CGLIB或Byte Buddy。
3. **调试复杂度**:代理逻辑可能增加调试难度,需合理使用日志。
### **总结**
动态代理是企业级框架的核心技术之一,通过**代理模式+反射机制**,在不修改原有代码的前提下实现功能增强,广泛应用于事务、缓存、权限、RPC等场景,显著提升系统的可维护性和扩展性。
示例
以下是一个使用 Java 动态代理实现 **日志记录** 和 **性能监控** 的完整示例。该示例演示了如何在方法调用前后自动添加横切逻辑,而无需修改目标类的代码。
### **1. 定义业务接口**
```java
// 用户服务接口
public interface UserService {
String createUser(String username, int age);
void deleteUser(int userId);
}
```
### **2. 实现业务接口**
```java
// 用户服务实现类
public class UserServiceImpl implements UserService {
@Override
public String createUser(String username, int age) {
System.out.println("创建用户: " + username + ", 年龄: " + age);
// 模拟数据库操作耗时
try { Thread.sleep(200); } catch (InterruptedException e) {}
return "用户ID: " + (int) (Math.random() * 1000);
}
@Override
public void deleteUser(int userId) {
System.out.println("删除用户: " + userId);
try { Thread.sleep(150); } catch (InterruptedException e) {}
}
}
```
### **3. 创建自定义 InvocationHandler**
```java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 日志与性能监控处理器
public class LogPerformanceHandler implements InvocationHandler {
private final Object target; // 目标对象
public LogPerformanceHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 记录方法调用开始时间
long startTime = System.currentTimeMillis();
System.out.println("----- 开始调用方法: " + method.getName() + " -----");
// 2. 打印方法参数
if (args != null && args.length > 0) {
System.out.print("参数: ");
for (Object arg : args) {
System.out.print(arg + ", ");
}
System.out.println();
}
// 3. 调用目标方法
Object result = method.invoke(target, args);
// 4. 记录方法调用结束时间并计算耗时
long endTime = System.currentTimeMillis();
System.out.println("方法返回值: " + result);
System.out.println("方法耗时: " + (endTime - startTime) + "ms");
System.out.println("----- 结束调用方法: " + method.getName() + " -----\n");
return result;
}
}
```
### **4. 创建代理工厂工具类**
```java
import java.lang.reflect.Proxy;
// 代理工厂类
public class ProxyFactory {
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogPerformanceHandler(target)
);
}
}
```
### **5. 测试动态代理**
```java
public class Main {
public static void main(String[] args) {
// 创建真实对象
UserService realService = new UserServiceImpl();
// 创建代理对象
UserService proxyService = ProxyFactory.createProxy(realService);
// 调用代理方法
String userId = proxyService.createUser("张三", 25);
proxyService.deleteUser(Integer.parseInt(userId.split(": ")[1]));
}
}
```
### **运行结果**
```
----- 开始调用方法: createUser -----
参数: 张三, 25,
创建用户: 张三, 年龄: 25
方法返回值: 用户ID: 735
方法耗时: 201ms
----- 结束调用方法: createUser -----
----- 开始调用方法: deleteUser -----
参数: 735,
删除用户: 735
方法返回值: null
方法耗时: 151ms
----- 结束调用方法: deleteUser -----
```
### **关键技术点解析**
1. **Proxy.newProxyInstance**
- 第一个参数:目标类的类加载器
- 第二个参数:目标类实现的接口数组
- 第三个参数:自定义的 InvocationHandler
2. **InvocationHandler.invoke**
- `proxy`:代理对象本身(避免在此方法中调用 proxy 的方法,会导致递归)
- `method`:被调用的方法对象
- `args`:方法参数数组
- 返回值:目标方法的返回结果
3. **反射调用**
```java
method.invoke(target, args);
// target:目标对象
// args:方法参数
```
### **应用场景扩展**
- **事务管理**:在方法前后添加事务开启/提交/回滚逻辑
- **权限校验**:调用方法前检查用户权限
- **缓存处理**:优先从缓存读取数据,无缓存时再调用真实方法
- **远程调用**:将方法调用转换为网络请求发送到远程服务
通过动态代理,你可以在不修改业务代码的前提下,对方法进行增强,实现横切关注点的统一管理,符合面向切面编程(AOP)的思想。
57.断言
Java断言(Assertion)是一种在代码中进行条件检查的机制,用于在开发和测试阶段确保程序的内部状态符合预期。断言主要用于验证那些“不应该发生”的情况,帮助开发者快速定位问题。
### **1. 断言的基本语法**
Java断言有两种形式:
#### 形式一:简单断言
```java
assert condition;
```
当`condition`为`false`时,抛出`AssertionError`。
#### 形式二:带消息的断言
```java
assert condition : message;
```
当`condition`为`false`时,抛出带`message`的`AssertionError`,便于调试。
### **2. 断言的启用与禁用**
断言默认是**禁用**的,需在运行时通过`-ea`(`-enableassertions`)参数启用:
```bash
java -ea MainClass # 启用所有断言
java -ea:com.example... MainClass # 仅启用com.example包及其子包的断言
java -da:com.example.TestClass MainClass # 禁用特定类的断言
```
### **3. 断言的典型应用场景**
#### (1)验证方法参数的内部状态
```java
public void divide(int numerator, int denominator) {
assert denominator != 0 : "分母不能为零";
// 业务逻辑
}
```
#### (2)验证控制流不会到达的代码
```java
public void process(int type) {
switch (type) {
case 1: handleType1(); break;
case 2: handleType2(); break;
default:
assert false : "未知类型: " + type; // 防御性编程
}
}
```
#### (3)验证类的不变量
```java
public class Counter {
private int count = 0;
public void increment() {
count++;
assert count > 0 : "计数器溢出"; // 确保计数器始终为正
}
}
```
### **4. 断言与异常的区别**
| **对比项** | **断言(Assertion)** | **异常(Exception)** |
|------------------|-------------------------------------|-------------------------------------|
| **目的** | 检测程序内部错误,确保逻辑正确性 | 处理预期的外部错误(如输入非法、网络异常) |
| **启用状态** | 默认禁用,主要用于开发和测试 | 始终启用,用于生产环境 |
| **处理方式** | 抛出`AssertionError`(继承`Error`) | 抛出`Exception`或其子类 |
| **使用场景** | 验证不应该发生的情况 | 处理可能发生的错误 |
### **5. 使用断言的注意事项**
#### (1)不要用断言验证用户输入
用户输入属于外部数据,必须通过异常处理进行验证:
```java
// 错误示例:不要用断言验证用户输入
public void setAge(int age) {
assert age > 0 : "年龄必须为正数"; // 错误!用户输入可能非法
}
// 正确示例:用异常处理用户输入
public void setAge(int age) {
if (age <= 0) {
throw new IllegalArgumentException("年龄必须为正数");
}
}
```
#### (2)不要在断言条件中执行副作用操作
断言可能被禁用,因此断言中的代码可能不会执行:
```java
// 错误示例:断言中包含副作用
assert list.remove(null) : "列表不能包含null"; // 若断言禁用,remove不会执行
// 正确示例:将副作用代码放在断言外
boolean noNulls = !list.contains(null);
assert noNulls : "列表不能包含null";
```
#### (3)断言的性能影响
启用断言会带来轻微的性能开销(条件检查),但禁用时性能与无断言代码相同。
### **6. 断言的替代方案**
在生产环境中,可使用以下方式替代断言:
- **日志记录**:记录关键状态和异常
- **单元测试**:使用JUnit等框架编写测试用例
- **前置条件检查**:使用Guava的`Preconditions`类
```java
import com.google.common.base.Preconditions;
public void process(String input) {
Preconditions.checkNotNull(input, "输入不能为空");
Preconditions.checkArgument(input.length() > 5, "输入长度必须>5");
}
```
### **总结**
Java断言是一种轻量级的调试工具,用于在开发阶段验证程序内部假设的正确性。使用断言可以帮助开发者快速定位逻辑错误,但需注意:
- **不要用断言处理可恢复的错误**
- **不要在断言中包含业务逻辑**
- **生产环境默认禁用断言**
合理使用断言可以提高代码的健壮性和可维护性。
示例
在企业开发中,Java断言主要用于**内部不变量验证**、**防御性编程**和**测试阶段的健壮性检查**。以下是几个典型场景的示例:
### **1. 验证框架配置假设**
```java
public class DatabaseConfig {
private static final int MAX_CONNECTIONS = 100;
private int connectionCount;
public void openConnection() {
// 确保连接数不超过上限(防御性编程)
assert connectionCount < MAX_CONNECTIONS : "连接数超出上限";
connectionCount++;
// 打开连接的逻辑
}
}
```
### **2. 验证状态机转换**
```java
public class Order {
private enum Status { CREATED, PAID, SHIPPED, COMPLETED }
private Status currentStatus = Status.CREATED;
public void pay() {
// 确保只有CREATED状态可以支付
assert currentStatus == Status.CREATED : "非法状态转换: " + currentStatus;
currentStatus = Status.PAID;
}
}
```
### **3. 验证算法前置条件**
```java
public class MathUtils {
public static double[] normalize(double[] values) {
// 确保输入不为空(内部使用的工具方法)
assert values != null && values.length > 0 : "输入数组不能为空";
double sum = 0;
for (double v : values) sum += v;
double[] result = new double[values.length];
for (int i = 0; i < values.length; i++) {
result[i] = values[i] / sum;
}
return result;
}
}
```
### **4. 验证第三方库返回值**
```java
public class CacheClient {
private final Cache cache;
public String get(String key) {
String value = cache.get(key);
// 假设根据业务规则,某些key永远不应该返回null
assert value != null : "缓存中缺失必要数据: " + key;
return value;
}
}
```
### **5. 多线程环境中的不变量检查**
```java
public class Counter {
private int count = 0;
public synchronized void increment() {
int oldValue = count;
count++;
// 确保多线程环境下计数正确
assert count == oldValue + 1 : "计数错误";
}
}
```
### **6. 生产环境中启用断言的场景**
在关键业务流程中,可以通过自定义错误处理启用断言:
```java
public static void main(String[] args) {
// 启用断言并捕获AssertionError
try {
System.setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(java.security.Permission perm) {
// 允许所有权限
}
});
// 执行核心业务逻辑
processOrder();
} catch (AssertionError e) {
// 记录严重错误(生产环境中不应该发生的情况)
Logger.severe("断言失败: " + e.getMessage());
// 触发降级策略或告警
}
}
```
### **7. 与前置条件检查的配合使用**
在Guava框架中,可以结合`Preconditions`进行生产环境检查:
```java
import com.google.common.base.Preconditions;
public void processUser(User user) {
// 生产环境始终检查
Preconditions.checkNotNull(user, "用户不能为空");
// 开发阶段检查内部状态
assert user.getRole() != null : "用户角色未初始化";
}
```
### **企业开发最佳实践**
1. **明确断言边界**
- 用于验证**内部状态**而非外部输入
- 检查**不应该发生**的情况而非可能发生的错误
2. **结合自动化测试**
在CI/CD流程中启用断言:
```bash
mvn test -DargLine="-ea" # Maven测试时启用断言
```
3. **生产环境处理策略**
- 默认禁用断言,避免影响性能
- 关键模块可通过JVM参数临时启用(如灰度环境)
4. **日志与告警集成**
当断言失败时,自动触发告警系统(如钉钉、邮件通知)。
通过合理使用断言,企业可以在不影响生产稳定性的前提下,提升开发阶段的问题发现效率,降低线上故障风险。
58.jdk logging
JDK Logging 总结
1. 核心特性
- 内置框架:JDK 1.4 起内置,无需第三方依赖,适合轻量级项目。
- 分级日志:支持
SEVERE
、WARNING
、INFO
、CONFIG
、FINE
、FINER
、FINEST
共 7 个级别,默认级别为INFO
,低于该级别的日志会被过滤。
- 灵活配置:可通过 代码 或
logging.properties
配置文件 定义日志输出目的地(控制台、文件等)、格式(文本 / XML)和级别。
2. 核心组件
3. 基本使用流程
- 获取 Logger 实例:java
- 记录日志:java
- 配置输出(代码或文件):
-
- 代码配置:手动添加 Handler 并设置级别、格式。java
-
- 配置文件(
logging.properties
):properties
- 配置文件(
4. 关键配置项
- 日志级别控制:
-
- 全局级别:通过
Logger.getLogger("").setLevel(Level.INFO)
设置根 Logger 级别。
- 全局级别:通过
-
- 自定义级别:为特定包或类的 Logger 设置独立级别(如
Logger.getLogger("com.example").setLevel(Level.FINE)
)。
- 自定义级别:为特定包或类的 Logger 设置独立级别(如
- Handler 配置:
-
- 控制台输出:
ConsoleHandler
,适合开发调试。
- 控制台输出:
-
- 文件输出:
FileHandler
,支持日志分割、归档(如按大小或时间分割)。
- 文件输出:
- 格式控制:使用
SimpleFormatter
或自定义Formatter
定义日志格式(包含时间、级别、类名、消息等)。
5. 优缺点与适用场景
6. 最佳实践
- 按类命名 Logger:便于定位日志来源(如
Logger.getLogger(MyClass.class.getName())
)。
- 生产环境配置:
-
- 禁用控制台输出,使用
FileHandler
写入文件。
- 禁用控制台输出,使用
-
- 设置合理日志级别(如
INFO
或WARNING
),避免冗余日志。
- 设置合理日志级别(如
- 性能优化:对高开销的日志参数使用
提前判断:
- 集成其他框架:通过桥接库(如
jcl-over-slf4j
)将 JDK Logging 重定向到 SLF4J/Logback 等更强大的日志框架。
7. 与其他日志框架对比
总结:JDK Logging 是 Java 内置的轻量级日志解决方案,适合简单场景或对依赖敏感的项目。若需高性能、复杂配置或社区生态支持,可考虑 Log4j 2 或 Logback,并通过 SLF4J 统一日志接口。
组件 | 作用 |
Logger | 日志记录器,通过类名获取实例(如 |
Level | 控制日志级别,决定哪些日志会被输出。 |
Handler | 定义日志输出目的地(如 |
Formatter | 格式化日志格式(如 |
Filter | 可选组件,用于更细粒度的日志过滤。 |
private static final Logger logger = Logger.getLogger(MyClass.class.getName());
logger.info("普通信息"); // 默认输出
logger.log(Level.SEVERE, "异常信息", e); // 记录异常堆栈
FileHandler fileHandler = new FileHandler("app.log");
fileHandler.setFormatter(new SimpleFormatter());
logger.addHandler(fileHandler);
handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
java.util.logging.FileHandler.pattern=app_%u.log // 日志文件名模式
优点 | 缺点 | 适用场景 |
无需额外依赖 | 配置灵活性低于 Log4j/Logback | 中小型项目、轻量级应用 |
学习成本低 | 社区支持和扩展组件较少 | 对依赖敏感的项目(如 Android) |
与 JDK 深度集成 | 性能略低于 Log4j 2 | 快速原型开发、简单日志需求 |
isLoggable
if (logger.isLoggable(Level.FINE)) {
logger.fine("计算结果:" + expensiveCalculation());
}
框架 | JDK Logging | Log4j 2 | SLF4J + Logback |
是否内置 | 是 | 否 | 否 |
配置灵活性 | 一般(仅文本) | 高(XML/JSON) | 高(XML/Groovy) |
性能 | 中等 | 高 | 高 |
扩展性 | 有限 | 丰富 | 丰富 |
Java Logging(JDK Logging)是Java平台内置的日志框架,自JDK 1.4起引入,无需额外依赖即可使用。它提供了灵活的日志级别控制、日志格式化和日志输出管理,适合中小型项目和企业级应用。
### **1. 核心组件**
#### (1)Logger
- 日志记录器,是应用程序与日志系统的接口。
- 通过`Logger.getLogger(name)`获取实例,名称通常为类的全限定名。
#### (2)Level
- 日志级别,从高到低依次为:
```java
SEVERE > WARNING > INFO > CONFIG > FINE > FINER > FINEST
```
- 默认级别为`INFO`,低于该级别的日志将被忽略。
#### (3)Handler
- 负责将日志输出到目的地(如控制台、文件、网络)。
- 常见实现:`ConsoleHandler`、`FileHandler`、`SocketHandler`。
#### (4)Formatter
- 格式化日志记录,如`SimpleFormatter`(文本格式)、`XMLFormatter`(XML格式)。
#### (5)Filter
- 可选组件,用于更精细地控制日志输出。
### **2. 基本用法**
```java
import java.util.logging.Logger;
public class LogExample {
private static final Logger logger = Logger.getLogger(LogExample.class.getName());
public static void main(String[] args) {
logger.severe("严重错误发生");
logger.warning("警告信息");
logger.info("一般信息"); // 默认级别为INFO,会输出
logger.config("配置信息"); // 默认级别下不会输出
logger.fine("详细信息"); // 默认级别下不会输出
logger.finer("更详细信息");
logger.finest("最详细信息");
// 带参数的日志
String username = "testUser";
logger.info("用户登录: " + username);
// 异常日志
try {
int result = 1 / 0;
} catch (Exception e) {
logger.log(java.util.logging.Level.SEVERE, "计算错误", e);
}
}
}
```
### **3. 配置方式**
#### (1)代码配置
```java
import java.util.logging.*;
public class LogConfigExample {
public static void main(String[] args) throws Exception {
Logger logger = Logger.getLogger("com.example");
// 设置级别
logger.setLevel(Level.ALL);
// 添加控制台处理器
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
consoleHandler.setFormatter(new SimpleFormatter());
logger.addHandler(consoleHandler);
// 添加文件处理器
FileHandler fileHandler = new FileHandler("app.log", true); // 追加模式
fileHandler.setLevel(Level.FINE);
fileHandler.setFormatter(new XMLFormatter());
logger.addHandler(fileHandler);
// 禁用父级处理器(避免重复输出)
logger.setUseParentHandlers(false);
logger.info("配置完成,开始记录日志");
}
}
```
#### (2)配置文件(logging.properties)
```properties
# 全局配置
handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
.level=INFO
# 控制台处理器配置
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
# 文件处理器配置
java.util.logging.FileHandler.level=FINE
java.util.logging.FileHandler.pattern=app.log
java.util.logging.FileHandler.limit=5000000
java.util.logging.FileHandler.count=5
java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter
# 特定包的日志级别
com.example.level=FINE
```
加载配置文件:
```java
public static void main(String[] args) throws Exception {
InputStream configFile = LogConfigExample.class.getResourceAsStream("/logging.properties");
LogManager.getLogManager().readConfiguration(configFile);
Logger logger = Logger.getLogger("com.example");
logger.fine("使用配置文件的日志示例");
}
```
### **4. 日志级别控制**
通过设置不同级别的`Logger`和`Handler`,可以实现精细的日志控制:
```java
// 根Logger设置为INFO
Logger rootLogger = Logger.getLogger("");
rootLogger.setLevel(Level.INFO);
// 特定包设置为FINE
Logger exampleLogger = Logger.getLogger("com.example");
exampleLogger.setLevel(Level.FINE);
// 特定Handler设置为WARNING
for (Handler handler : rootLogger.getHandlers()) {
handler.setLevel(Level.WARNING);
}
```
### **5. 自定义Handler和Formatter**
#### (1)自定义Handler示例
```java
import java.util.logging.*;
import java.io.*;
public class CustomHandler extends StreamHandler {
public CustomHandler(String fileName) throws IOException {
super(new FileOutputStream(fileName), new SimpleFormatter());
}
@Override
public void publish(LogRecord record) {
super.publish(record);
flush(); // 立即刷新
}
@Override
public void close() {
flush();
}
}
```
#### (2)自定义Formatter示例
```java
import java.util.logging.*;
public class CustomFormatter extends Formatter {
@Override
public String format(LogRecord record) {
return "[" + record.getLevel() + "] " +
new java.util.Date(record.getMillis()) + " - " +
record.getSourceClassName() + "." +
record.getSourceMethodName() + "() - " +
formatMessage(record) + "\n";
}
}
```
### **6. JDK Logging与其他框架的比较**
| **特性** | **JDK Logging** | **Log4j 2** | **SLF4J + Logback** |
|------------------|-----------------------|----------------------|----------------------|
| **是否内置** | 是(JDK 1.4+) | 否(需依赖) | 否(需依赖) |
| **配置方式** | 代码或properties文件 | XML、JSON、YAML | XML、Groovy |
| **性能** | 中等 | 高 | 高 |
| **社区活跃度** | 一般 | 高 | 高 |
| **扩展性** | 一般 | 高 | 高 |
### **7. 最佳实践**
1. **使用类名作为Logger名称**
```java
private static final Logger logger = Logger.getLogger(MyClass.class.getName());
```
2. **避免直接使用根Logger**
根Logger会影响所有日志输出,建议使用命名Logger。
3. **性能优化**
对于开销大的日志参数,使用条件判断:
```java
if (logger.isLoggable(Level.FINE)) {
logger.fine("复杂计算结果: " + expensiveOperation());
}
```
4. **生产环境配置**
- 将日志输出到文件而非控制台
- 设置合理的日志级别(如`INFO`或`WARNING`)
- 定期归档和清理日志文件
5. **集成其他框架**
通过桥接器将JDK Logging重定向到其他日志框架:
```xml
<!-- 添加JCL-over-SLF4J依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.9</version>
</dependency>
```
### **总结**
JDK Logging是Java平台自带的日志框架,功能完备且无需额外依赖,适合中小型项目和对依赖管理敏感的场景。通过合理配置,可以满足企业级应用的日志需求,同时支持与其他日志框架集成。
59.common logging
逐渐被 SLF4J 取代。
60.SLF4J
SLF4J(Simple Logging Facade for Java)是Java生态中最流行的日志门面框架,它为不同的日志实现(如Logback、Log4j 2)提供统一的API接口,使应用程序可以在部署时动态选择日志实现,而无需修改代码。相比早期的JCL(Apache Commons Logging),SLF4J设计更简洁、性能更高,且支持参数化日志等现代特性。
### **1. 核心设计思想**
- **门面模式**:通过`LoggerFactory`获取`Logger`实例,屏蔽底层日志实现的差异。
- **编译时绑定**:通过静态代理绑定具体日志实现,避免运行时反射开销。
- **参数化日志**:支持占位符语法,提升性能并减少字符串拼接。
### **2. 基本用法**
#### (1)添加依赖(以Maven为例)
```xml
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<!-- 选择日志实现(这里使用Logback) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
```
#### (2)使用SLF4J记录日志
```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jExample {
private static final Logger log = LoggerFactory.getLogger(Slf4jExample.class);
public static void main(String[] args) {
String name = "Alice";
int age = 30;
// 参数化日志(避免字符串拼接)
log.info("用户信息:姓名={}, 年龄={}", name, age);
try {
int result = 1 / 0;
} catch (Exception e) {
log.error("计算失败", e);
}
}
}
```
### **3. 参数化日志的优势**
- **性能优化**:仅在日志级别启用时才进行参数格式化。
- **代码简洁**:避免手动字符串拼接,提升可读性。
```java
// 传统方式(无论日志是否启用都会执行字符串拼接)
log.debug("处理用户: " + user.getName() + ", 余额: " + user.getBalance());
// SLF4J参数化方式(仅当日志级别启用时才格式化)
log.debug("处理用户: {}, 余额: {}", user.getName(), user.getBalance());
```
### **4. 绑定不同的日志实现**
SLF4J通过**桥接器(Bridge)**和**适配器(Adapter)**实现与不同日志框架的集成:
#### (1)绑定Logback(推荐)
```xml
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
```
- **优势**:SLF4J作者开发的原生实现,性能最优,配置灵活。
#### (2)绑定Log4j 2
```xml
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.20.0</version>
</dependency>
```
#### (3)绑定JDK Logging
```xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.9</version>
</dependency>
```
### **5. 桥接其他日志框架**
若项目依赖其他日志API(如JCL、Log4j 1.x),可通过SLF4J桥接器将其重定向到SLF4J:
#### (1)JCL(Apache Commons Logging)到SLF4J
```xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.9</version>
</dependency>
<!-- 排除原JCL依赖 -->
```
#### (2)Log4j 1.x到SLF4J
```xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>2.0.9</version>
</dependency>
<!-- 排除原Log4j依赖 -->
```
### **6. 配置示例(以Logback为例)**
创建`logback.xml`配置文件:
```xml
<configuration>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 根Logger -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
<!-- 特定包的日志级别 -->
<logger name="com.example" level="DEBUG" />
</configuration>
```
### **7. MDC(Mapped Diagnostic Context)**
MDC用于在日志中附加上下文信息(如请求ID、用户ID),方便分布式系统追踪:
```java
import org.slf4j.MDC;
public class WebController {
public void handleRequest(HttpServletRequest request) {
// 放入请求ID
MDC.put("requestId", UUID.randomUUID().toString());
try {
// 业务逻辑
log.info("处理请求: {}", request.getRequestURI());
} finally {
// 清除MDC
MDC.clear();
}
}
}
```
配置文件中使用`%X{requestId}`获取MDC值:
```xml
<pattern>%d{yyyy-MM-dd} [%X{requestId}] %-5level %logger - %msg%n</pattern>
```
### **8. 与其他日志框架对比**
| **特性** | **SLF4J** | **JCL (Commons Logging)** | **Log4j 2** |
|------------------|-------------------------|---------------------------|-----------------------|
| **定位** | 日志门面 | 日志门面 | 日志实现 |
| **绑定方式** | 编译时静态绑定 | 运行时反射 | 直接使用 |
| **性能** | 高(无反射开销) | 低(反射开销) | 高 |
| **参数化日志** | 支持(`{}`占位符) | 不支持 | 支持 |
| **MDC支持** | 原生支持 | 有限支持 | 支持 |
| **社区活跃度** | 高(主流框架) | 低(维护停滞) | 高 |
### **9. 最佳实践**
1. **依赖管理**:
- 只引入`slf4j-api`,具体实现通过`logback-classic`或`log4j-slf4j-impl`提供。
- 使用桥接器统一日志输出(如将JCL、Log4j 1.x重定向到SLF4J)。
2. **日志配置**:
- 开发环境使用控制台输出,生产环境配置滚动文件输出。
- 通过配置文件(如`logback.xml`)集中管理日志格式和级别。
3. **性能优化**:
- 优先使用参数化日志(避免字符串拼接)。
- 对高频率日志(如循环内),使用`isDebugEnabled()`提前判断:
```java
if (log.isDebugEnabled()) {
log.debug("大量数据: {}", expensiveOperation());
}
```
4. **分布式系统**:
- 使用MDC添加请求上下文信息(如traceId、spanId)。
- 结合ELK Stack或Graylog等日志聚合工具分析日志。
### **总结**
SLF4J是Java日志门面的事实标准,通过统一API和灵活的绑定机制,让应用程序可以无缝切换不同的日志实现。其参数化日志、MDC等特性提升了开发体验和系统可观测性,推荐所有Java项目使用SLF4J作为日志门面,并搭配Logback或Log4j 2作为底层实现。
61. Logback/Log4j 2
Logback和Log4j 2是Java生态中最流行的两个**日志实现框架**,通常与SLF4J门面配合使用。它们都提供了强大的日志功能,但在设计、性能和特性上存在差异。
### **一、Logback**
Logback是SLF4J的原生实现,由SLF4J的作者开发,与SLF4J无缝集成,具有高性能和灵活的配置能力。
#### **1. 核心特性**
- **零拷贝架构**:通过事件对象传递日志信息,减少内存分配和垃圾回收压力。
- **自动重新加载配置**:支持配置文件热更新,无需重启应用。
- **过滤器系统**:提供基于MDC、Marker、日志级别等多维度的过滤机制。
- **高级滚动策略**:支持按时间、大小或自定义条件分割日志文件。
- **内置多种Appender**:包括控制台、文件、Socket、SMTP、数据库等。
#### **2. 配置示例(logback.xml)**
```xml
<configuration scan="true" scanPeriod="30 seconds">
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 滚动文件输出(按天分割) -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory> <!-- 保留30天日志 -->
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 异步Appender(提升性能) -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
<queueSize>1000</queueSize>
<discardingThreshold>0</discardingThreshold>
</appender>
<!-- 根Logger -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="ASYNC" />
</root>
<!-- 特定包的日志级别 -->
<logger name="com.example" level="DEBUG" />
<logger name="org.springframework" level="WARN" />
</configuration>
```
#### **3. 性能优化**
- **异步日志**:使用`AsyncAppender`减少IO阻塞:
```xml
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
```
- **参数化日志**:利用SLF4J的占位符语法避免字符串拼接。
### **二、Log4j 2**
Log4j 2是Apache Log4j的下一代版本,在性能和扩展性上进行了重大改进,支持插件式架构和丰富的配置格式。
#### **1. 核心特性**
- **多线程优化**:使用无锁数据结构(如`ConcurrentHashMap`)和LMAX Disruptor框架,在多线程环境下性能显著优于Logback。
- **插件系统**:通过插件机制扩展Appender、Layout和Filter,无需修改核心代码。
- **配置灵活性**:支持XML、JSON、YAML等多种配置格式,并提供Web控制台动态调整配置。
- **自动重新加载**:配置文件变化时自动生效,无需重启应用。
- **上下文数据**:通过`ThreadContext`(类似MDC)添加上下文信息到日志。
#### **2. 配置示例(log4j2.xml)**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Appenders>
<!-- 控制台输出 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<!-- 滚动文件输出(按大小和时间分割) -->
<RollingFile name="RollingFile" fileName="app.log"
filePattern="app-%d{yyyy-MM-dd}.%i.log">
<PatternLayout>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- 特定包的日志级别 -->
<Logger name="com.example" level="DEBUG" additivity="false">
<AppenderRef ref="RollingFile"/>
</Logger>
<!-- 根Logger -->
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
```
#### **3. 性能优化**
- **异步Logger**:通过`AsyncLogger`实现无锁异步日志:
```xml
<Configuration>
<Appenders>...</Appenders>
<Loggers>
<AsyncRoot level="info">
<AppenderRef ref="RollingFile"/>
</AsyncRoot>
</Loggers>
</Configuration>
```
- **Location信息优化**:避免在日志中获取调用者位置信息(`%class`、`%method`),因为反射调用开销大。
### **三、Logback vs Log4j 2**
| **特性** | **Logback** | **Log4j 2** |
|------------------------|--------------------------------------|--------------------------------------|
| **性能(多线程)** | 优秀,但Log4j 2使用Disruptor框架更优 | 显著优于Logback(特别是异步场景) |
| **配置灵活性** | 支持XML和Groovy | 支持XML、JSON、YAML,且提供Web控制台 |
| **异步日志实现** | 基于队列 | 无锁设计,使用LMAX Disruptor |
| **内存占用** | 较低 | 略高(但换取更高吞吐量) |
| **社区活跃度** | 高 | 高 |
| **与SLF4J集成** | 原生支持,无缝集成 | 通过`log4j-slf4j-impl`桥接 |
| **文件滚动策略** | 丰富,但Log4j 2更灵活 | 支持更复杂的滚动策略(如自定义条件) |
### **四、如何选择?**
1. **优先选择Log4j 2的场景**:
- 高并发、多线程应用(如Web服务器、消息中间件)。
- 需要极致性能和低延迟。
- 需要动态调整日志配置(如通过Web界面)。
- 需要支持多种配置格式(如JSON、YAML)。
2. **优先选择Logback的场景**:
- 项目已使用SLF4J,追求最简配置。
- 对内存占用敏感的应用(如嵌入式系统)。
- 需要更轻量级的依赖(Logback核心包约1.5MB,Log4j 2约6MB)。
### **五、最佳实践**
1. **统一日志门面**:
无论使用Logback还是Log4j 2,都通过SLF4J API记录日志,避免直接依赖具体实现。
2. **异步日志**:
对高吞吐量场景,优先使用异步日志(Logback的`AsyncAppender`或Log4j 2的`AsyncLogger`)。
3. **生产环境配置**:
- 避免在生产环境使用`DEBUG`或`TRACE`级别,除非必要。
- 配置合理的日志滚动策略,避免磁盘空间耗尽。
4. **日志分析**:
结合ELK Stack(Elasticsearch + Logstash + Kibana)或Graylog等工具进行日志聚合和分析。
### **总结**
Logback和Log4j 2都是优秀的日志框架,Logback适合追求简单配置和轻量级依赖的场景,而Log4j 2在高性能和扩展性上表现更优。现代Java项目通常推荐使用**SLF4J + Log4j 2**的组合,以获得最佳性能和灵活性。
Logback 与 Log4j 2 核心对比
1. 定位与共性
- 日志实现框架:均需配合 SLF4J 门面使用。
- 核心功能:支持日志级别控制、文件滚动、异步输出、MDC 上下文等。
2. 性能差异
3. 特性对比
4. 适用场景
- Logback:
-
- 中小型项目(依赖轻量)。
-
- 追求与 SLF4J 无缝集成。
-
- 配置简单(XML 为主)。
- Log4j 2:
-
- 高并发系统(如微服务、中间件)。
-
- 需要动态调整日志配置。
-
- 复杂的日志处理需求(如自定义插件)。
5. 最佳实践
- 统一门面:通过 SLF4J API 记录日志。
- 异步优先:生产环境使用异步模式(
AsyncAppender
/AsyncLogger
)。
- 生产配置:
-
- 禁用 DEBUG 级别,避免频繁 IO。
-
- 配置合理的日志保留策略(如按时间 / 大小滚动)。
- 生态集成:结合 ELK/Graylog 进行日志聚合分析。
选择建议:新项目优先考虑 SLF4J + Log4j 2,性能与扩展性更优;若追求极简配置,可选 SLF4J + Logback。
维度 | Logback | Log4j 2 |
同步模式 | 优秀 | 更优(无锁设计) |
异步模式 | 基于队列 | 基于 LMAX Disruptor(低延迟) |
多线程场景 | 良好 | 显著优于 Logback |
特性 | Logback | Log4j 2 |
配置格式 | XML/Groovy | XML/JSON/YAML/Web 控制台 |
文件滚动 | 按时间 / 大小 | 支持自定义条件(如 CPU 使用率) |
插件系统 | 较简单 | 高度可扩展(插件式架构) |
内存占用 | 较低(约 1.5MB) | 较高(约 6MB) |
自动重载 | 支持(需配置 scan 参数) | 原生支持(monitorInterval) |
62.GUI SWING
Java Swing 是 Java 平台提供的一套图形用户界面(GUI)工具包,属于 Java Foundation Classes (JFC) 的一部分。它提供了丰富的组件(如按钮、文本框、列表等)和布局管理器,用于开发跨平台的桌面应用程序。Swing 是轻量级的(不依赖原生操作系统组件),且具有高度可定制性。
### **1. 核心组件与架构**
Swing 组件采用 **MVC(模型-视图-控制器)** 架构,主要分为三类:
- **顶层容器**:`JFrame`、`JDialog`、`JApplet`(已淘汰)。
- **中间容器**:`JPanel`、`JScrollPane`、`JTabbedPane` 等。
- **基本组件**:`JButton`、`JLabel`、`JTextField`、`JTable` 等。
所有 Swing 组件均以 `J` 开头,继承自 `javax.swing.JComponent`。
### **2. 简单示例:Hello World**
```java
import javax.swing.*;
import java.awt.*;
public class HelloSwing {
public static void main(String[] args) {
// 在事件调度线程中创建和操作GUI
SwingUtilities.invokeLater(() -> {
// 创建顶层窗口
JFrame frame = new JFrame("Hello Swing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.setLocationRelativeTo(null); // 居中显示
// 创建面板和按钮
JPanel panel = new JPanel();
JButton button = new JButton("Click Me");
button.addActionListener(e -> {
JOptionPane.showMessageDialog(frame, "Hello, Swing!");
});
// 添加组件到面板
panel.add(button);
frame.getContentPane().add(panel);
// 显示窗口
frame.setVisible(true);
});
}
}
```
### **3. 布局管理器**
Swing 提供多种布局管理器,控制组件的排列方式:
- **FlowLayout**:按添加顺序排列,自动换行(默认布局)。
- **BorderLayout**:分为东、西、南、北、中五个区域。
- **GridLayout**:网格布局,所有组件大小相同。
- **GridBagLayout**:复杂的网格布局,支持组件跨列跨行。
- **BoxLayout**:水平或垂直排列组件。
```java
// 示例:使用GridLayout
panel.setLayout(new GridLayout(2, 2)); // 2行2列
panel.add(new JButton("1"));
panel.add(new JButton("2"));
panel.add(new JButton("3"));
panel.add(new JButton("4"));
```
### **4. 事件处理**
Swing 使用 **事件委托模型**,通过接口实现事件监听:
```java
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击");
}
});
// Lambda表达式简化(Java 8+)
button.addActionListener(e -> System.out.println("按钮被点击"));
```
### **5. 高级组件**
- **表格(JTable)**:显示和编辑二维数据。
```java
String[] columnNames = {"姓名", "年龄"};
Object[][] data = {{"张三", 25}, {"李四", 30}};
JTable table = new JTable(data, columnNames);
JScrollPane scrollPane = new JScrollPane(table);
```
- **树(JTree)**:显示层次结构数据。
```java
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
DefaultMutableTreeNode child = new DefaultMutableTreeNode("Child");
root.add(child);
JTree tree = new JTree(root);
```
- **对话框(JOptionPane)**:显示简单消息或获取用户输入。
```java
// 消息对话框
JOptionPane.showMessageDialog(frame, "操作成功");
// 确认对话框
int result = JOptionPane.showConfirmDialog(frame, "确定要删除吗?");
if (result == JOptionPane.YES_OPTION) {
// 用户点击"是"
}
```
### **6. 线程安全与 EDT**
Swing 不是线程安全的,所有 GUI 操作必须在 **事件调度线程(Event Dispatch Thread, EDT)** 中执行:
```java
// 正确方式:使用SwingUtilities.invokeLater
SwingUtilities.invokeLater(() -> {
// 更新UI的代码
label.setText("Loading...");
});
```
### **7. 外观与感觉(Look and Feel)**
Swing 支持多种外观风格,可动态切换:
```java
// 使用系统默认外观
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// 使用Nimbus外观(Java 6+)
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
```
### **8. 优缺点与替代方案**
| **优点** | **缺点** | **替代方案** |
|-------------------------|---------------------------|---------------------------|
| 跨平台一致性 | 界面设计繁琐 | JavaFX(Java 8+ 官方推荐) |
| 组件丰富 | 性能一般 | SWT(Eclipse平台使用) |
| 无需额外依赖 | 缺乏现代UI设计支持 | LibGDX(游戏开发) |
| 高度可定制 | 学习曲线较陡 | 第三方框架(如Apache Pivot)|
### **9. 最佳实践**
1. **使用布局管理器**:避免硬编码组件位置(`setBounds()`)。
2. **事件处理**:优先使用 Lambda 表达式简化代码。
3. **EDT 操作**:所有 UI 更新必须在 EDT 中执行。
4. **资源管理**:窗口关闭时释放资源(如数据库连接)。
5. **调试工具**:使用 [WindowBuilder](https://www.eclipse.org/windowbuilder/) 等可视化设计工具加速开发。
### **总结**
Swing 是 Java 传统的 GUI 解决方案,适合开发中小型桌面应用。尽管存在界面设计不够现代的问题,但它功能全面、跨平台稳定,且无需额外依赖。对于新应用,推荐考虑 **JavaFX**(Java 8+)或 **第三方框架**(如 Vaadin),以获得更现代的 UI 体验
。
Java Swing 是 Java 提供的跨平台轻量级 GUI 工具包,基于 MVC 架构,组件以 J
开头(如 JFrame
、JButton
),支持丰富的界面元素和布局管理器(如 FlowLayout、BorderLayout)。通过事件委托模型(如 ActionListener
)处理用户交互,代码示例中常需在事件调度线程(EDT)中更新 UI。 其优点是跨平台性强、组件丰富,适合中小型桌面应用(如企业工具);缺点是界面设计繁琐、性能一般,现代项目多转向 JavaFX。开发时需注意布局管理器的合理使用、线程安全(通过 SwingUtilities.invokeLater
操作 UI),以及资源释放。尽管逐渐被替代,Swing 仍是学习 GUI 编程基础逻辑的重要入门选择。
63.项目一信息管理系统
了解程序开发思维 设计的架构即可
黑马程序员的基于Swing的信息管理系统通常是一个教学案例,用于演示如何使用Java Swing开发一个完整的桌面应用程序。这类系统一般包含用户管理、数据增删改查、界面交互等功能,适合Java初学者掌握GUI编程和数据库操作的基础知识。
### **1. 系统架构**
典型的三层架构:
- **视图层(View)**:Swing组件(JFrame、JPanel、JTable等)
- **业务逻辑层(Service)**:处理业务规则(如数据验证、权限控制)
- **数据访问层(DAO)**:操作数据库(如JDBC连接MySQL)
### **2. 核心功能模块**
#### (1)用户管理
- 用户登录/注册界面
- 权限控制(管理员/普通用户)
#### (2)数据管理
- 学生/员工信息的增删改查
- 数据表格展示(JTable)
- 分页功能
#### (3)数据导入导出
- 支持Excel/CSV导入导出
- 数据备份与恢复
#### (4)统计分析
- 简单的数据可视化(柱状图、饼图)
### **3. 技术实现细节**
#### (1)数据库设计
```sql
-- 示例:学生信息表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT,
gender VARCHAR(10),
major VARCHAR(50)
);
```
#### (2)Swing界面设计
```java
// 创建主窗口示例
public class MainFrame extends JFrame {
private JTable table;
private DefaultTableModel model;
public MainFrame() {
// 设置窗口属性
setTitle("学生信息管理系统");
setSize(800, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 初始化表格
String[] columnNames = {"ID", "姓名", "年龄", "性别", "专业"};
model = new DefaultTableModel(columnNames, 0);
table = new JTable(model);
// 添加滚动面板
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane, BorderLayout.CENTER);
// 添加按钮面板
JPanel buttonPanel = new JPanel();
JButton addButton = new JButton("添加");
JButton editButton = new JButton("修改");
JButton deleteButton = new JButton("删除");
buttonPanel.add(addButton);
buttonPanel.add(editButton);
buttonPanel.add(deleteButton);
add(buttonPanel, BorderLayout.SOUTH);
// 添加事件监听器
addButton.addActionListener(e -> openAddDialog());
editButton.addActionListener(e -> openEditDialog());
deleteButton.addActionListener(e -> deleteSelectedRow());
}
// 其他方法...
}
```
#### (3)数据库操作(DAO层)
```java
public class StudentDAO {
private Connection getConnection() throws SQLException {
// 获取数据库连接(示例使用MySQL)
String url = "jdbc:mysql://localhost:3306/student_management";
String username = "root";
String password = "password";
return DriverManager.getConnection(url, username, password);
}
public List<Student> getAllStudents() {
List<Student> students = new ArrayList<>();
String sql = "SELECT * FROM student";
try (Connection conn = getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
Student student = new Student(
rs.getInt("id"),
rs.getString("name"),
rs.getInt("age"),
rs.getString("gender"),
rs.getString("major")
);
students.add(student);
}
} catch (SQLException e) {
e.printStackTrace();
}
return students;
}
// 增删改方法...
}
```
### **4. 关键技术点**
#### (1)表格数据绑定
```java
// 更新表格数据
public void refreshTable() {
model.setRowCount(0); // 清空表格
List<Student> students = studentDAO.getAllStudents();
for (Student student : students) {
Object[] rowData = {
student.getId(),
student.getName(),
student.getAge(),
student.getGender(),
student.getMajor()
};
model.addRow(rowData);
}
}
```
#### (2)对话框设计
```java
// 添加学生对话框
private void openAddDialog() {
JDialog dialog = new JDialog(this, "添加学生", true);
dialog.setSize(400, 300);
dialog.setLayout(new BorderLayout());
// 创建表单面板
JPanel formPanel = new JPanel(new GridLayout(5, 2));
JTextField nameField = new JTextField();
JTextField ageField = new JTextField();
JTextField genderField = new JTextField();
JTextField majorField = new JTextField();
formPanel.add(new JLabel("姓名:"));
formPanel.add(nameField);
formPanel.add(new JLabel("年龄:"));
formPanel.add(ageField);
formPanel.add(new JLabel("性别:"));
formPanel.add(genderField);
formPanel.add(new JLabel("专业:"));
formPanel.add(majorField);
// 添加按钮
JPanel buttonPanel = new JPanel();
JButton saveButton = new JButton("保存");
JButton cancelButton = new JButton("取消");
saveButton.addActionListener(e -> {
// 保存逻辑
String name = nameField.getText();
int age = Integer.parseInt(ageField.getText());
String gender = genderField.getText();
String major = majorField.getText();
studentDAO.addStudent(new Student(0, name, age, gender, major));
refreshTable();
dialog.dispose();
});
cancelButton.addActionListener(e -> dialog.dispose());
buttonPanel.add(saveButton);
buttonPanel.add(cancelButton);
dialog.add(formPanel, BorderLayout.CENTER);
dialog.add(buttonPanel, BorderLayout.SOUTH);
dialog.setVisible(true);
}
```
#### (3)多线程处理
```java
// 后台加载数据(避免UI卡顿)
public void loadDataAsync() {
SwingWorker<List<Student>, Void> worker = new SwingWorker<List<Student>, Void>() {
@Override
protected List<Student> doInBackground() throws Exception {
return studentDAO.getAllStudents();
}
@Override
protected void done() {
try {
List<Student> students = get();
updateTable(students);
} catch (Exception e) {
e.printStackTrace();
}
}
};
worker.execute();
}
```
### **5. 系统优化建议**
1. **界面优化**:
- 使用 Nimbus 外观提升视觉效果
```java
try {
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) {
e.printStackTrace();
}
```
2. **数据库连接池**:
- 使用 HikariCP 或 C3P0 提高数据库访问性能
3. **数据校验**:
- 添加输入验证(如非空检查、类型转换)
4. **国际化支持**:
- 使用 ResourceBundle 实现多语言切换
### **6. 常见问题与解决方案**
| **问题** | **解决方案** |
|------------------------|----------------------------------------------------------------------------|
| 界面卡顿 | 使用 SwingWorker 在后台线程执行耗时操作(如数据库查询)。 |
| 中文显示乱码 | 设置 JVM 参数 `-Dfile.encoding=UTF-8`,确保数据库和 IDE 编码一致。 |
| 数据库连接失败 | 检查 URL、用户名、密码是否正确,确认数据库服务已启动。 |
| 表格数据不更新 | 确保调用 `model.fireTableDataChanged()` 或重新设置表格模型。 |
### **总结**
黑马的 Swing 信息管理系统是一个综合性教学项目,涵盖了 Swing 组件使用、数据库操作、事件处理、多线程等核心知识点。通过该项目,学习者可以掌握 Java 桌面应用开发的完整流程,为进一步学习 JavaFX 或 Web 开发打下基础。实际开发中,建议结合 MVC 模式优化代码结构,并使用工具(如 WindowBuilder)提升界面设计效率。
64.数字华容道石头迷阵
黑马程序员的数字华容道(石头迷阵)项目是一个基于Java Swing开发的经典滑块拼图游戏。游戏目标是通过移动数字方块,将混乱的数字按顺序排列(如1-15)。这个项目适合Java初学者练习GUI编程、事件处理和算法设计。
### **1. 游戏规则**
- **棋盘**:4x4网格,共15个数字方块和1个空白格。
- **目标**:将数字从1到15按顺序排列(最后一格为空白)。
- **操作**:通过方向键或点击数字,将数字移入空白格。
### **2. 核心功能模块**
#### (1)游戏界面
- 4x4网格布局
- 数字方块(JButton)
- 计时器和步数统计
- 开始/重置按钮
#### (2)游戏逻辑
- 随机生成初始布局(确保有解)
- 判断方块移动合法性
- 检测游戏胜利条件
- 计时和步数统计
#### (3)数据结构
- 使用二维数组存储方块状态
- 记录空白格位置
- 存储游戏时间和步数
### **3. 技术实现细节**
#### (1)界面设计
```java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class PuzzleGame extends JFrame {
private JButton[][] blocks = new JButton[4][4]; // 存储方块
private int[][] board = new int[4][4]; // 存储数字布局
private int emptyRow, emptyCol; // 空白格位置
private JLabel timeLabel, stepLabel; // 时间和步数标签
private Timer timer; // 计时器
private int seconds = 0; // 游戏时间
private int steps = 0; // 移动步数
public PuzzleGame() {
// 设置窗口属性
setTitle("数字华容道");
setSize(400, 500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
// 初始化顶部面板(显示时间和步数)
JPanel topPanel = new JPanel();
timeLabel = new JLabel("时间: 0秒");
stepLabel = new JLabel("步数: 0");
JButton startButton = new JButton("开始游戏");
startButton.addActionListener(e -> startGame());
topPanel.add(timeLabel);
topPanel.add(stepLabel);
topPanel.add(startButton);
add(topPanel, BorderLayout.NORTH);
// 初始化游戏面板(4x4网格)
JPanel gamePanel = new JPanel(new GridLayout(4, 4));
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
blocks[i][j] = new JButton();
blocks[i][j].setFont(new Font("宋体", Font.BOLD, 24));
blocks[i][j].addActionListener(new BlockListener(i, j));
gamePanel.add(blocks[i][j]);
}
}
add(gamePanel, BorderLayout.CENTER);
// 初始化计时器
timer = new Timer(1000, e -> {
seconds++;
timeLabel.setText("时间: " + seconds + "秒");
});
}
// 其他方法...
}
```
#### (2)游戏初始化
```java
// 开始新游戏
private void startGame() {
// 停止之前的计时器
if (timer.isRunning()) {
timer.stop();
}
// 重置数据
seconds = 0;
steps = 0;
timeLabel.setText("时间: 0秒");
stepLabel.setText("步数: 0");
// 生成随机布局(确保有解)
generateSolvablePuzzle();
// 开始计时
timer.start();
}
// 生成有解的随机布局
private void generateSolvablePuzzle() {
// 初始化有序布局
int num = 1;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (i == 3 && j == 3) {
board[i][j] = 0; // 空白格
emptyRow = i;
emptyCol = j;
} else {
board[i][j] = num++;
}
}
}
// 随机移动生成乱序布局(确保有解)
Random random = new Random();
int moves = 200; // 随机移动200次
while (moves > 0) {
int direction = random.nextInt(4); // 0-3: 上右下左
switch (direction) {
case 0: // 上
if (emptyRow < 3) {
swap(emptyRow, emptyCol, emptyRow + 1, emptyCol);
moves--;
}
break;
case 1: // 右
if (emptyCol > 0) {
swap(emptyRow, emptyCol, emptyRow, emptyCol - 1);
moves--;
}
break;
case 2: // 下
if (emptyRow > 0) {
swap(emptyRow, emptyCol, emptyRow - 1, emptyCol);
moves--;
}
break;
case 3: // 左
if (emptyCol < 3) {
swap(emptyRow, emptyCol, emptyRow, emptyCol + 1);
moves--;
}
break;
}
}
// 更新界面
updateBoard();
}
```
#### (3)方块移动逻辑
```java
// 方块点击事件监听器
private class BlockListener implements ActionListener {
private int row, col;
public BlockListener(int row, int col) {
this.row = row;
this.col = col;
}
@Override
public void actionPerformed(ActionEvent e) {
// 判断是否可以移动
if (isAdjacent(row, col, emptyRow, emptyCol)) {
// 交换方块
swap(row, col, emptyRow, emptyCol);
// 更新步数
steps++;
stepLabel.setText("步数: " + steps);
// 更新界面
updateBoard();
// 检查是否获胜
if (isWin()) {
timer.stop();
JOptionPane.showMessageDialog(
PuzzleGame.this,
"恭喜你获胜!用时: " + seconds + "秒,步数: " + steps,
"游戏胜利",
JOptionPane.INFORMATION_MESSAGE
);
}
}
}
}
// 判断两个位置是否相邻
private boolean isAdjacent(int r1, int c1, int r2, int c2) {
return (Math.abs(r1 - r2) == 1 && c1 == c2) ||
(Math.abs(c1 - c2) == 1 && r1 == r2);
}
// 交换两个位置的方块
private void swap(int r1, int c1, int r2, int c2) {
int temp = board[r1][c1];
board[r1][c1] = board[r2][c2];
board[r2][c2] = temp;
// 更新空白格位置
if (board[r1][c1] == 0) {
emptyRow = r1;
emptyCol = c1;
} else if (board[r2][c2] == 0) {
emptyRow = r2;
emptyCol = c2;
}
}
```
#### (4)胜利条件检测
```java
// 检查是否获胜
private boolean isWin() {
int num = 1;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (i == 3 && j == 3) {
return board[i][j] == 0; // 最后一格必须为空白
} else if (board[i][j] != num++) {
return false;
}
}
}
return true;
}
```
### **4. 关键技术点**
#### (1)布局管理器
使用`GridLayout`实现4x4网格,确保方块大小均匀。
#### (2)事件处理
- 通过内部类实现`ActionListener`监听方块点击
- 使用`Timer`类实现计时功能
#### (3)有解性判断
通过计算逆序数判断布局是否有解(4x4华容道的必要条件:逆序数+空白格所在行数为偶数)。
#### (4)界面刷新
每次移动后调用`updateBoard()`方法更新按钮显示:
```java
// 更新界面显示
private void updateBoard() {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (board[i][j] == 0) {
blocks[i][j].setText("");
blocks[i][j].setEnabled(false); // 空白格不可点击
} else {
blocks[i][j].setText(String.valueOf(board[i][j]));
blocks[i][j].setEnabled(true);
}
}
}
}
```
### **5. 优化建议**
1. **添加键盘控制**:支持方向键移动方块
```java
// 添加键盘事件监听
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_UP:
// 向上移动逻辑
break;
case KeyEvent.VK_DOWN:
// 向下移动逻辑
break;
case KeyEvent.VK_LEFT:
// 向左移动逻辑
break;
case KeyEvent.VK_RIGHT:
// 向右移动逻辑
break;
}
}
});
```
2. **美化界面**:
- 使用自定义图标替代数字
- 添加动画效果(方块移动过渡)
- 优化按钮样式和背景
3. **记录最佳成绩**:
- 使用文件或数据库存储历史最佳时间和步数
4. **增加难度级别**:
- 支持3x3、5x5等不同大小的棋盘
### **6. 常见问题与解决方案**
| **问题** | **解决方案** |
|------------------------|----------------------------------------------------------------------------|
| 方块无法移动 | 检查`isAdjacent`方法逻辑是否正确,确保空白格位置记录准确。 |
| 游戏无法获胜 | 确保生成的布局有解(通过逆序数判断),检查`isWin`方法逻辑。 |
| 界面卡顿 | 避免在事件处理中执行耗时操作,确保所有UI更新在EDT线程中执行。 |
| 计时器不工作 | 确保`Timer`正确启动和停止,检查事件监听器是否正确注册。 |
### **总结**
黑马的数字华容道项目是一个综合性的Java Swing实践案例,涵盖了GUI设计、事件处理、游戏逻辑实现等核心知识点。通过该项目,学习者可以深入理解Java Swing的组件使用、多线程处理和算法设计,同时掌握游戏开发的基本流程。实际开发中,建议采用MVC模式分离视图和逻辑,并通过代码重构提升可维护性。
快速回顾
56. 动态代理
Java 动态代理运行时生成代理类,通过InvocationHandler
拦截方法调用,实现 AOP(日志、事务等)。优势是解耦、非侵入式增强,局限于接口代理,可结合 CGLIB 扩展。
57. 断言(Assertion)
开发阶段验证内部状态,默认禁用,需-ea
启用。用于参数校验、控制流防御等,不处理外部输入。生产环境用日志或前置条件(如 Guava)替代,避免性能影响。
58. JDK Logging
JDK 内置日志框架,支持 7 级日志、配置文件和 Handler(控制台 / 文件)。轻量无依赖,适合中小项目,但配置灵活性和性能低于 Log4j/Logback,可桥接 SLF4J。
59. Common Logging
早期日志门面,现逐渐被 SLF4J 取代。通过LogFactory
获取日志实例,支持动态绑定实现,但反射开销大,社区维护停滞。
60. SLF4J
日志门面框架,统一 API 适配 Logback/Log4j 2 等实现。参数化日志提升性能,支持 MDC 上下文,推荐项目使用 SLF4J + 具体实现(如 Logback)。
61. Logback/Log4j 2
- Logback:SLF4J 原生实现,轻量灵活,配置 XML 为主,适合中小项目。
- Log4j 2:高性能,支持 JSON/YAML 配置和异步日志,多线程场景更优,适合高并发系统。
62. GUI Swing
Java 跨平台 GUI 工具包,基于 MVC,组件以J
开头。通过布局管理器和事件监听器开发界面,适合桌面应用,但现代项目转向 JavaFX,需注意 EDT 线程安全。
63. 信息管理系统
Swing 教学项目,三层架构(视图 / 业务 / 数据),实现增删改查、权限控制等。使用 JTable 展示数据,JDBC 操作数据库,需优化布局和线程处理。
64. 数字华容道
Swing 实现的滑块游戏,4x4 网格,随机生成有解布局,通过移动方块排序。核心逻辑:移动合法性判断、胜利检测、计时统计,可扩展键盘控制和难度级别。