Appearance
框架设计
为什么需要框架
在软件开发中,框架(Framework)是经过高度抽象和设计的半成品软件,它为特定领域的问题提供了通用解决方案。当一个团队反复面对相似的技术挑战时,框架的价值就凸显出来:
- 复用与效率:避免重复造轮子。框架将经过验证的最佳实践沉淀为可复用的组件,开发者只需关注业务逻辑。
- 统一规范:约束团队按照统一模式编码,降低沟通成本,新人也能快速上手。
- 质量保障:框架通常由资深开发者设计,经过大量项目验证,其稳定性和性能远超临时拼凑的工具类。
- 关注点分离:框架将横切关注点(日志、安全、事务、缓存)从业务代码中剥离,让代码更干净。
- 可扩展性:好的框架通过 SPI、插件机制等设计,允许在不修改核心代码的情况下进行扩展。
没有框架时: 有框架时:
┌──────────────────────┐ ┌──────────────────────┐
│ 业务逻辑 + 基础设施 │ │ 业务逻辑 │
│ (混杂在一起) │ │ (只关注业务) │
│ · 连接池管理 │ ├──────────────────────┤
│ · 事务控制 │ │ 框架层 │
│ · 日志记录 │ │ · 连接池管理 │
│ · 异常处理 │ │ · 事务控制 │
│ · 安全校验 │ │ · 日志记录 │
│ · 真正业务逻辑 │ │ · 安全校验 │
└──────────────────────┘ └──────────────────────┘框架 vs 库:谁调用谁?
框架和库的关键区别在于控制反转(IoC, Inversion of Control):
| 维度 | 框架(Framework) | 库(Library) |
|---|---|---|
| 控制权 | 框架控制流程,调用你的代码 | 你控制流程,调用库的 API |
| 关系 | 好莱坞原则:"Don't call us, we'll call you" | 你主动调用,库被动响应 |
| 典型例子 | Spring、MyBatis、Netty | Apache Commons、Guava、SLF4J |
| 代码结构 | 你需要遵循框架规定的接口和生命周期 | 你自由组织代码,按需调用 |
| 侵入性 | 高,代码依赖框架 | 低,可随时替换 |
java
// 使用库:你控制流程
public void doSomething() {
// 你主动调用 Guava 的工具类
List<String> result = Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.splitToList(input);
// 你决定何时、如何使用
}
// 使用框架:框架控制流程
@RestController // 框架要求你使用注解
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// 框架决定了何时调用这个方法(HTTP 请求到达时)
return userService.findById(id);
}
}框架设计原则
1. 可扩展性——SPI 机制
好的框架必须允许用户在不修改源码的情况下扩展功能。Java SPI(Service Provider Interface)是基石:
java
// ===== 框架方定义接口 =====
// 文件:connector-api.jar
public interface DatabaseConnector {
String getName();
Connection connect(String url, String user, String password);
boolean supports(String url);
}
// ===== 框架方的加载逻辑 =====
public class ConnectorLoader {
public List<DatabaseConnector> loadConnectors() {
List<DatabaseConnector> connectors = new ArrayList<>();
// 通过 ServiceLoader 加载所有实现
ServiceLoader<DatabaseConnector> loader =
ServiceLoader.load(DatabaseConnector.class);
for (DatabaseConnector connector : loader) {
connectors.add(connector);
}
return connectors;
}
}
// ===== 扩展方实现接口 =====
// 文件:META-INF/services/com.example.DatabaseConnector
// 内容:com.mycompany.MySQLConnector
public class MySQLConnector implements DatabaseConnector {
@Override
public String getName() { return "MySQL"; }
@Override
public Connection connect(String url, String user, String password) {
// MySQL 连接实现
return DriverManager.getConnection(url, user, password);
}
@Override
public boolean supports(String url) {
return url.startsWith("jdbc:mysql:");
}
}2. Spring Boot 自动配置——SPI 的进阶应用
Spring Boot 的自动配置是基于 SPI 思想的高级实现:
java
// spring-boot-autoconfigure.jar 中的 spring.factories
// 位置:META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
...
// 自动配置条件注解示例
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, ... })
public class DataSourceAutoConfiguration {
@Configuration
@ConditionalOnMissingBean(DataSource.class)
static class EmbeddedDatabaseConfiguration {
@Bean
public DataSource dataSource(DataSourceProperties properties) {
// 创建内嵌数据源
return createEmbeddedDataSource(properties);
}
}
}3. 约定优于配置
Spring Boot 通过"约定优于配置"大幅减少样板代码:
java
// 约定:spring.datasource 前缀的属性自动映射到 DataSource
// 配置方只需在 application.yml 中填写:
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
// 框架内部自动完成映射,无需手动创建 DataSource Bean
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
private String url;
private String username;
private String password;
// getter / setter
}4. 可配置性
框架需要提供灵活但不繁琐的配置方式:
| 配置方式 | 优先级 | 适用场景 |
|---|---|---|
| 代码配置(@Bean) | 最高 | 需要编程逻辑的复杂配置 |
| YAML/Properties | 中 | 环境相关配置 |
| 注解 | 中 | 声明式配置 |
| 默认值 | 最低 | 开箱即用 |
框架设计中的核心设计模式
1. 模板方法模式(Template Method)
定义算法骨架,将可变步骤延迟到子类。
java
// 抽象父类定义算法骨架
public abstract class AbstractDataProcessor {
// 模板方法:定义处理流程的骨架
public final void process() {
validate();
beforeProcess();
doProcess();
afterProcess();
onSuccess();
}
protected void validate() {
// 默认实现,子类可覆盖
}
protected void beforeProcess() {
// 钩子方法
}
// 抽象方法:子类必须实现
protected abstract void doProcess();
protected void afterProcess() {
// 钩子方法
}
protected void onSuccess() {
// 钩子方法
}
}
// 具体实现
public class ExcelDataProcessor extends AbstractDataProcessor {
@Override
protected void doProcess() {
System.out.println("处理 Excel 数据");
}
@Override
protected void validate() {
System.out.println("校验 Excel 格式");
}
}框架中的经典应用:
- Spring 的
AbstractApplicationContext.refresh() - MyBatis 的
BaseExecutor - Spring 的
JdbcTemplate、RestTemplate
2. 策略模式(Strategy)
定义一系列算法,让它们可以相互替换。
java
// 策略接口
public interface LoadBalanceStrategy {
Server select(List<Server> servers);
}
// 具体策略
public class RoundRobinStrategy implements LoadBalanceStrategy {
private int index = 0;
@Override
public Server select(List<Server> servers) {
return servers.get(index++ % servers.size());
}
}
public class RandomStrategy implements LoadBalanceStrategy {
@Override
public Server select(List<Server> servers) {
return servers.get(ThreadLocalRandom.current().nextInt(servers.size()));
}
}
// 上下文
public class LoadBalancer {
private LoadBalanceStrategy strategy;
public LoadBalancer(LoadBalanceStrategy strategy) {
this.strategy = strategy;
}
public Server route(List<Server> servers) {
return strategy.select(servers);
}
}3. 责任链模式(Chain of Responsibility)
将请求沿链传递,直到被处理。
java
// 拦截器接口
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
}
// 责任链
public class InterceptorChain {
private final List<Interceptor> interceptors;
public Object proceed(Object target) {
// 构建调用链
Invocation invocation = new Invocation(target, interceptors, 0);
return invocation.proceed();
}
}
// 调用对象
public class Invocation {
private final Object target;
private final List<Interceptor> interceptors;
private int index;
public Object proceed() {
if (index >= interceptors.size()) {
return target; // 链尾,执行目标
}
Interceptor interceptor = interceptors.get(index++);
return interceptor.intercept(this);
}
}框架中的经典应用:
- Spring AOP 的拦截器链
- Spring MVC 的
HandlerInterceptor - MyBatis 的插件机制
- Netty 的
ChannelPipeline
4. 观察者模式(Observer)
定义一对多依赖,当对象状态改变时通知所有依赖者。
java
// 事件对象
public class ApplicationEvent {
private final Object source;
private final long timestamp;
// 构造函数、getter...
}
// 监听器接口
public interface ApplicationListener<E extends ApplicationEvent> {
void onApplicationEvent(E event);
}
// 事件广播器
public class SimpleEventMulticaster {
private final List<ApplicationListener<?>> listeners = new ArrayList<>();
public void addListener(ApplicationListener<?> listener) {
listeners.add(listener);
}
public void publishEvent(ApplicationEvent event) {
for (ApplicationListener listener : listeners) {
// 类型匹配后调用
listener.onApplicationEvent(event);
}
}
}框架中的经典应用:
- Spring 的事件机制(
ApplicationEvent+ApplicationListener) - Spring Boot 的
ApplicationRunner、CommandLineRunner
5. 装饰器模式(Decorator)
动态地给对象添加额外职责。
java
// 组件接口
public interface Cache {
Object get(String key);
void put(String key, Object value);
}
// 基础实现
public class SimpleCache implements Cache {
private final Map<String, Object> map = new ConcurrentHashMap<>();
public Object get(String key) { return map.get(key); }
public void put(String key, Object value) { map.put(key, value); }
}
// 装饰器:增加日志
public class LoggingCache implements Cache {
private final Cache delegate;
public LoggingCache(Cache delegate) { this.delegate = delegate; }
public Object get(String key) {
System.out.println("Cache get: " + key);
return delegate.get(key);
}
public void put(String key, Object value) {
System.out.println("Cache put: " + key);
delegate.put(key, value);
}
}
// 装饰器:增加 LRU 淘汰
public class LRUCache implements Cache {
private final Cache delegate;
private final int maxSize;
private final LinkedHashMap<String, Object> accessOrder;
// ...
}框架中的经典应用:
- MyBatis 的二级缓存(
CachingExecutor装饰BaseExecutor) - Java IO 流(
BufferedInputStream装饰InputStream) - Spring 的
TransactionAwareCacheDecorator
实战:设计一个简单的 ORM 框架
核心流程
注解解析 → SQL 生成 → 执行 → 结果映射第一步:定义注解
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {
boolean autoIncrement() default true;
}第二步:实体类元数据解析
java
public class EntityMeta {
private String tableName;
private Class<?> entityClass;
private Field idField;
private List<Field> columns;
// getter / setter...
}
public class EntityParser {
public static EntityMeta parse(Class<?> clazz) {
EntityMeta meta = new EntityMeta();
meta.setEntityClass(clazz);
// 解析 @Table 注解
Table table = clazz.getAnnotation(Table.class);
meta.setTableName(table != null && !table.name().isEmpty()
? table.name() : clazz.getSimpleName().toLowerCase());
// 解析字段
List<Field> columns = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Id.class)) {
meta.setIdField(field);
}
if (field.isAnnotationPresent(Column.class)) {
columns.add(field);
}
}
meta.setColumns(columns);
return meta;
}
}第三步:SQL 生成器
java
public class SqlGenerator {
public static String generateInsert(EntityMeta meta) {
StringBuilder sql = new StringBuilder("INSERT INTO ");
sql.append(meta.getTableName()).append(" (");
List<Field> columns = meta.getColumns();
for (int i = 0; i < columns.size(); i++) {
Column col = columns.get(i).getAnnotation(Column.class);
String colName = col.name().isEmpty()
? columns.get(i).getName() : col.name();
sql.append(colName);
if (i < columns.size() - 1) sql.append(", ");
}
sql.append(") VALUES (");
for (int i = 0; i < columns.size(); i++) {
sql.append("?");
if (i < columns.size() - 1) sql.append(", ");
}
sql.append(")");
return sql.toString();
}
public static String generateSelectById(EntityMeta meta) {
return "SELECT * FROM " + meta.getTableName() + " WHERE id = ?";
}
public static String generateSelectAll(EntityMeta meta) {
return "SELECT * FROM " + meta.getTableName();
}
}第四步:结果映射器
java
public class ResultMapper {
public static <T> T map(ResultSet rs, Class<T> clazz) throws Exception {
T instance = clazz.getDeclaredConstructor().newInstance();
ResultSetMetaData rsMeta = rs.getMetaData();
for (int i = 1; i <= rsMeta.getColumnCount(); i++) {
String columnName = rsMeta.getColumnLabel(i);
Object value = rs.getObject(i);
// 通过反射设置字段值
for (Field field : clazz.getDeclaredFields()) {
Column col = field.getAnnotation(Column.class);
String fieldColName = (col != null && !col.name().isEmpty())
? col.name() : field.getName();
if (fieldColName.equalsIgnoreCase(columnName)) {
field.setAccessible(true);
field.set(instance, value);
break;
}
}
}
return instance;
}
}第五步:整合——核心 Session
java
public class MiniSession {
private final DataSource dataSource;
private final Map<Class<?>, EntityMeta> metaCache = new ConcurrentHashMap<>();
public MiniSession(DataSource dataSource) {
this.dataSource = dataSource;
}
public <T> void insert(T entity) throws Exception {
EntityMeta meta = getMeta(entity.getClass());
String sql = SqlGenerator.generateInsert(meta);
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
List<Field> columns = meta.getColumns();
for (int i = 0; i < columns.size(); i++) {
columns.get(i).setAccessible(true);
ps.setObject(i + 1, columns.get(i).get(entity));
}
ps.executeUpdate();
}
}
public <T> T findById(Class<T> clazz, Object id) throws Exception {
EntityMeta meta = getMeta(clazz);
String sql = SqlGenerator.generateSelectById(meta);
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, id);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
return ResultMapper.map(rs, clazz);
}
}
return null;
}
private EntityMeta getMeta(Class<?> clazz) {
return metaCache.computeIfAbsent(clazz, EntityParser::parse);
}
}第六步:使用示例
java
@Table(name = "users")
public class User {
@Id
private Long id;
@Column
private String name;
@Column(name = "email_addr")
private String email;
// getter / setter...
}
// 使用
MiniSession session = new MiniSession(dataSource);
User user = new User();
user.setName("张三");
user.setEmail("[email protected]");
session.insert(user);
User found = session.findById(User.class, 1L);实战:设计一个简单的 RPC 框架
核心流程
客户端代理 → 序列化 → 网络传输 → 反序列化 → 服务端调用 → 序列化返回第一步:服务接口定义
java
// 公共接口,客户端和服务端共享
public interface HelloService {
String sayHello(String name);
}第二步:客户端动态代理
java
public class RpcClientProxy {
@SuppressWarnings("unchecked")
public static <T> T create(Class<T> interfaceClass, String host, int port) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new RpcInvocationHandler(host, port, interfaceClass.getName())
);
}
static class RpcInvocationHandler implements InvocationHandler {
private final String host;
private final int port;
private final String serviceName;
RpcInvocationHandler(String host, int port, String serviceName) {
this.host = host;
this.port = port;
this.serviceName = serviceName;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 封装 RPC 请求
RpcRequest request = new RpcRequest();
request.setServiceName(serviceName);
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setArguments(args);
// 2. 序列化 + 发送 + 接收
try (Socket socket = new Socket(host, port);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream())) {
oos.writeObject(request);
oos.flush();
// 3. 读取响应
RpcResponse response = (RpcResponse) ois.readObject();
if (response.getException() != null) {
throw response.getException();
}
return response.getResult();
}
}
}
}第三步:服务端——接收请求并调用
java
public class RpcServer {
private final int port;
private final Map<String, Object> serviceMap = new ConcurrentHashMap<>();
private volatile boolean running = true;
public RpcServer(int port) {
this.port = port;
}
public void registerService(Class<?> interfaceClass, Object implementation) {
serviceMap.put(interfaceClass.getName(), implementation);
}
public void start() throws IOException {
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("RPC Server started on port " + port);
while (running) {
Socket socket = serverSocket.accept();
new Thread(() -> handleRequest(socket)).start();
}
}
}
private void handleRequest(Socket socket) {
try (ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream())) {
// 1. 反序列化请求
RpcRequest request = (RpcRequest) ois.readObject();
// 2. 查找服务并反射调用
Object service = serviceMap.get(request.getServiceName());
Method method = service.getClass().getMethod(
request.getMethodName(), request.getParameterTypes());
Object result = method.invoke(service, request.getArguments());
// 3. 封装并返回响应
RpcResponse response = new RpcResponse();
response.setResult(result);
oos.writeObject(response);
oos.flush();
} catch (Exception e) {
// 异常处理
}
}
}第四步:通信协议对象
java
public class RpcRequest implements Serializable {
private String serviceName; // 服务接口全限定名
private String methodName; // 方法名
private Class<?>[] parameterTypes; // 参数类型
private Object[] arguments; // 参数值
// getter / setter...
}
public class RpcResponse implements Serializable {
private Object result; // 返回值
private Exception exception; // 异常信息
// getter / setter...
}第五步:使用示例
java
// ===== 服务端 =====
RpcServer server = new RpcServer(8080);
server.registerService(HelloService.class, new HelloServiceImpl());
server.start(); // 阻塞
// ===== 客户端 =====
HelloService helloService = RpcClientProxy.create(
HelloService.class, "localhost", 8080);
String result = helloService.sayHello("World");
System.out.println(result); // "Hello, World!"RPC 框架演进方向
| 演进阶段 | 核心能力 | 新增组件 |
|---|---|---|
| V1.0 基础版 | 动态代理 + Socket + 序列化 | 代理、网络、序列化 |
| V2.0 高性能版 | Netty 替代 BIO | Reactor 线程模型 |
| V3.0 服务治理 | 服务发现、负载均衡 | 注册中心、LB 策略 |
| V4.0 企业级 | 熔断、限流、监控 | 熔断器、限流器、链路追踪 |
面试常见问题
1. 框架和库的区别是什么?
答:核心区别在于控制权。框架通过 IoC 控制程序流程,你的代码被框架调用("Don't call us, we'll call you");库则是一组工具函数,你主动调用库的 API。框架提供了一套完整的解决方案并约束了代码结构(如 Spring),库则提供特定功能的集合(如 Guava)。
2. 如何设计一个插件化/可扩展的框架?
答:核心是面向接口编程 + SPI 机制。框架定义接口规范,扩展方实现接口并通过 META-INF/services 文件或注解声明。框架通过 ServiceLoader 或自定义类扫描器发现并加载实现。关键设计要点:接口职责单一且稳定、提供清晰的扩展点文档、考虑插件间隔离和依赖管理。
3. Spring Boot 自动配置的原理是什么?
答:Spring Boot 通过 @EnableAutoConfiguration 注解触发自动配置,核心是 AutoConfigurationImportSelector 类。它读取 META-INF/spring.factories 文件中配置的所有自动配置类,然后根据 @Conditional 系列注解(@ConditionalOnClass、@ConditionalOnMissingBean 等)进行条件判断,只加载满足条件的配置类。这种方式基于 Java SPI 机制,但增加了条件过滤能力。
4. 模板方法模式在框架中有哪些典型应用?
答:Spring 的 AbstractApplicationContext.refresh() 定义了容器启动的 12 步模板,其中 postProcessBeanFactory() 和 onRefresh() 是留给子类的扩展点;MyBatis 的 BaseExecutor 定义了查询、更新的模板骨架,具体执行由子类实现;Spring 的 JdbcTemplate 封装了 JDBC 的操作模板(获取连接、执行 SQL、处理异常、释放连接),用户只需提供 SQL 和参数。
5. 设计一个 ORM 框架需要关注哪些核心问题?
答:1) 对象-关系映射(注解 vs XML 配置映射规则);2) SQL 生成(CRUD 自动化、动态 SQL 支持);3) 结果集映射(类型转换、关联查询映射);4) 缓存机制(一级缓存、二级缓存);5) 事务管理(与 Spring 事务集成);6) 连接池管理;7) 性能优化(批量操作、延迟加载、预编译 SQL 复用)。
