今天是2025年的225天
每日进步1点点,一年就是365点点。
与Java开发者共勉
系列介绍
"Java面试基础篇"系列!本系列旨在帮助Java开发者系统性地准备面试,每天精选至少5道经典面试题,涵盖Java基础、进阶、框架等各方面知识。坚持学习21天,助你面试通关! 基础面试题: 每日5题Java面试系列基础(1) 每日5题Java面试系列基础(2) 每日5题Java面试系列基础(3) 每日5题Java面试系列基础(4) 每日5题Java面试系列基础(5) 每日5题Java面试系列基础(6) 每日5题Java面试系列进阶(7) 每日5题Java面试系列进阶(8) 每日5题Java面试系列进阶(9)
一、注解进阶相关面试题
1. 注解相比 XML 配置有什么优缺点
优点:
- 类型安全:注解是强类型的,编译器可以在编译期检查类型错误,而XML是纯文本,只能在运行时发现错误
- 代码内聚性:配置与代码在一起,减少了上下文切换,提高了可读性
- 开发效率:IDE对注解有更好的支持,如自动补全、重构支持等
- 编译时检查:可以通过注解处理器在编译期发现问题
- 减少配置量:很多默认行为可以通过注解简化
缺点:
- 侵入性强:修改配置需要重新编译代码,而XML可以热更新
- 灵活性低:注解是硬编码的,无法像XML那样动态调整
- 可读性差:复杂的配置用XML结构更清晰
- 耦合度高:注解将配置与代码紧密耦合,不利于模块化
最佳实践:简单固定的配置用注解,复杂多变的配置用XML,Spring Boot就采用了这种混合策略。
2. 如何实现一个自定义的注解处理器
实现自定义注解处理器需要以下步骤:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface CustomAnnotation {
String value() default "";
}
@SupportedAnnotationTypes("com.example.CustomAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CustomAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
// 处理逻辑
processingEnv.getMessager().printMessage(
Diagnostic.Kind.NOTE,
"Processing " + element.getSimpleName());
}
}
return true;
}
}
高级技巧:
- 使用JavaPoet或类似的库生成代码
- 结合反射实现运行时处理
- 使用Filer API创建新源文件
- 通过Trees API进行语法树分析
3. 运行时注解和编译时注解各有什么特点和应用场景
运行时注解:
-
特点:
-
保留策略为RUNTIME(@Retention(RetentionPolicy.RUNTIME))
-
通过反射机制读取
-
会增加运行时开销
-
应用场景:
-
框架的运行时配置(如Spring的@Controller)
-
动态代理生成
-
ORM框架的实体映射(如Hibernate的@Entity)
-
AOP切面配置
编译时注解:
-
特点:
-
保留策略为SOURCE或CLASS
-
通过注解处理器处理
-
无运行时开销
-
可以生成代码
-
应用场景:
-
代码生成(如Lombok)
-
静态检查(如Android的@NonNull)
-
编译时代码验证
-
构建时元数据处理
对比总结:
特性 | 运行时注解 | 编译时注解 |
处理时机 | 运行时 | 编译时 |
性能影响 | 有 | 无 |
灵活性 | 高 | 低 |
工具支持 | 反射 | 注解处理器 |
典型应用 | 框架配置 | 代码生成 |
4. 注解在框架设计中的作用是什么
注解在现代框架设计中扮演着核心角色:
设计思想:注解本质上是框架与用户代码之间的契约,它允许框架以非侵入的方式扩展应用行为,同时保持代码的声明性和可读性。
二、序列化进阶相关面试题
1. Java 序列化和 JSON 序列化各有什么优缺点
Java原生序列化:
-
优点:
-
语言原生支持,使用简单(实现Serializable接口)
-
完整对象图序列化,保持引用关系
-
自动处理复杂对象(包括继承、多态)
-
内置安全机制(secure coding)
-
缺点:
-
Java专属,跨语言支持差
-
序列化后的二进制格式体积大
-
性能较差(反射开销大)
-
版本兼容性问题(serialVersionUID)
-
存在安全风险(可执行任意代码)
JSON序列化:
-
优点:
-
跨语言支持,通用性强
-
文本格式,可读性好
-
体积相对较小
-
解析性能较好(特别是现代库如Jackson)
-
无版本兼容性问题(向前兼容设计)
-
缺点:
-
无法直接处理复杂对象图(循环引用)
-
类型信息丢失(需要额外处理)
-
对二进制数据支持不好(需要Base64编码)
-
缺乏标准规范(不同库实现有差异)
选型建议:
- 纯Java环境、需要完整对象图序列化 → Java序列化
- 跨语言、Web API、前端交互 → JSON
- 高性能场景 → 考虑Protobuf/Thrift等二进制协议
2. 序列化中的安全风险有哪些?如何防范
主要安全风险:
防范措施:
1. 输入验证:
- 使用白名单验证反序列化的类
- 限制反序列化的深度和复杂度
2. 加密签名:
- 对序列化数据进行数字签名
- 使用安全传输通道(HTTPS)
3. 替代方案:
- 使用JSON等更安全的序列化格式
- 考虑Protobuf等有严格Schema的格式
4. 安全配置:
- Java中重写ObjectInputStream的resolveClass方法
- 使用SecurityManager限制权限
5. 敏感数据处理:
- 对敏感字段使用transient
- 实现自定义的writeObject/readObject方法
6. 工具使用:
- 使用安全库(如Apache Commons Lang的SerializationUtils)
- 定期更新依赖库修复已知漏洞
Java示例:
public class SafeObjectInputStream extends ObjectInputStream {
private static final Set<String> ALLOWED_CLASSES =
Set.of("java.lang.String", "com.example.SafeClass");
public SafeObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
if (!ALLOWED_CLASSES.contains(desc.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
3. 如何优化序列化性能
优化策略:
1. 选择合适的序列化格式:
- 高吞吐场景:Protobuf、Thrift、Avro
- 低延迟场景:FlatBuffers、Cap’n Proto
- 通用场景:Jackson(JSON)、MessagePack
2. 减少序列化数据量:
- 只序列化必要字段(使用transient或@JsonIgnore)
- 压缩序列化结果(GZIP、Snappy)
- 使用二进制格式替代文本格式
3. 优化序列化过程:
- 重用序列化器实例(如Jackson的ObjectMapper)
- 预生成序列化代码(如Protobuf)
- 使用内存池减少GC压力
4. 缓存优化:
- 缓存序列化结果(适合读多写少场景)
- 使用增量序列化
5. 并发优化:
- 使用线程本地(ThreadLocal)存储序列化器
- 选择线程安全的序列化库
6. JVM层面优化:
- 调整缓冲区大小
- 使用直接内存(DirectBuffer)减少拷贝
Java示例(Jackson优化):
// 重用ObjectMapper实例
private static final ObjectMapper mapper = new ObjectMapper();
// 配置优化
static {
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
}
// 使用更高效的JsonFactory
private static final JsonFactory factory = mapper.getFactory();
private static final ThreadLocal<JsonGenerator> generatorCache = ThreadLocal.withInitial(() -> {
try {
return factory.createGenerator(new ByteArrayOutputStream(), JsonEncoding.UTF8);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
4. 分布式系统中,序列化面临哪些挑战
分布式系统中的序列化面临以下独特挑战:
1. 跨语言兼容性:
- 不同服务可能用不同语言实现
- 需要确保各语言都能正确解析数据格式
2. Schema演进:
- 服务独立部署导致版本不一致
- 需要兼容新旧数据格式(向前/向后兼容)
- 字段增删改的处理策略
3. 性能考量:
- 高并发下的序列化/反序列化开销
- 网络传输大小对延迟的影响
- 序列化CPU开销对吞吐量的影响
4. 数据一致性:
- 分布式事务中的序列化一致性
- 时钟同步问题(Timestamp序列化)
5. 安全挑战:
- 跨服务的安全边界
- 不可信输入的验证问题
6. 特殊数据类型:
- 分布式ID的序列化
- 大对象(如文件)的处理
- 循环引用的处理
7. 环境差异:
- 不同机器字节序(Endianness)问题
- 浮点数表示差异
解决方案:
1. 采用跨语言序列化协议:
- Protocol Buffers
- Apache Thrift
- Apache Avro
2. Schema管理:
- 使用中央Schema仓库
- 定义明确的演进规则
3. 性能优化:
- 二进制协议优先
- 零拷贝技术
- 异步序列化
4. 兼容性设计:
- 每个消息包含版本号
- 只添加可选字段,不删除字段
- 使用兼容性测试工具
5. 安全设计:
- 消息签名验证
- 严格的Schema验证
- 反序列化沙箱
实践建议:
- 对于服务间通信,优先考虑Protobuf等二进制协议
- 对于前后端交互,使用JSON并考虑JSON Schema验证
- 对于大数据量传输,考虑分块序列化
- 实现完善的监控,跟踪序列化性能指标
评论前必须登录!
注册