1. qa测过的代码别改,非得改的话,改之前就要通知qa
晚了半小时下班,因为优化了一下那该死的xx代码,忘记通知qa了。以后养成习惯:
修改前评估影响范围(是否涉及已测功能点)。
主动同步 QA:
告知原因(性能优化/bug修复)
明确告知需要回归的功能点
争取 QA 的时间窗口
QA 明确回测过,方可提交上线
2. 自测接口,缺失数据的情况
接口调用时,不应假设“所有依赖数据都一定存在”,应主动设计好 “缺数据” 时的容错逻辑 和 明确信息提示。
比如做数据统计,实际数据要和业务指标做比较。那就要考虑,如果人家没有业务指标咋办?这个没有很正常,某个公司可能就是还没来得及定指标。开发阶段最好就考虑到,然后测试阶段,告知qa,让qa也覆盖到。
3. 数据库一定要记得加唯一索引
不要在业务层单靠 Redis 锁解决这种问题,那是“弱防御”。即使业务层有校验,也必须靠数据库兜底。
Redis 锁不能防“非业务流程”写入。
4. 写完代码,还要考虑怎么验证数据
不是代码写完就结束了,代码写完只算一阶段结束,真正结束是测试通过、验收通过。
写代码的时候,最好就给出测试方案,比如搞个sql用来删数据,不要让测试测着测着就测不下去来找了。要运筹帷幄,掌控全局。
5. Redisson 看门狗机制。续期核心实现
private void renewExpiration() {
ExpirationEntry ee = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
if (ee != null) {
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = (ExpirationEntry)RedissonBaseLock.EXPIRATION_RENEWAL_MAP.get(RedissonBaseLock.this.getEntryName());
if (ent != null) {
Long threadId = ent.getFirstThreadId();
if (threadId != null) {
CompletionStage<Boolean> future = RedissonBaseLock.this.renewExpirationAsync(threadId);
future.whenComplete((res, e) -> {
if (e != null) {
RedissonBaseLock.log.error("Can't update lock " + RedissonBaseLock.this.getRawName() + " expiration", e);
RedissonBaseLock.EXPIRATION_RENEWAL_MAP.remove(RedissonBaseLock.this.getEntryName());
} else {
if (res) {
RedissonBaseLock.this.renewExpiration();
} else {
RedissonBaseLock.this.cancelExpirationRenewal((Long)null);
}
}
});
}
}
}
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
}
RedissonBaseLock.this.renewExpiration();并不是在调用函数本体,而是“安排一个10秒后执行的定时任务”。
此递归不是立即执行递归体,而是“安排”在未来执行,所以不会爆栈,也不会瞬间多次执行。
用的是Netty提供的Timing Wheel实现。
6. Netty HashedWheelTimer
它是 Netty 提供的高效时间轮定时器实现,适合大批量定时任务的场景;
优点:内存占用低,任务调度时间复杂度近似 O(1),非常适合像 Redisson 这种需要大量定时续期任务的场景。
HashedWheelTimer实现:通过一个环形数组(时间轮),将未来的任务分散到不同的槽里,每个槽维护一个任务链表。时间轮指针每次跳动处理当前槽的任务,任务到期就执行。
7. Timing Wheel
时间轮(Timing Wheel):类似机械钟表的指针,时间轮把时间划分成一个个“槽(bucket)”,每个槽对应一个时间间隔。当时间指针转动到某个槽时,执行该槽中所有的定时任务。
用于实现延时功能,性能很高。
8. TMD MapStruct 不是 Service,不写业务逻辑
业务逻辑不要写到mapstruct那个破转换里面。mapstruct功能是做字段映射,目的是提高开发效率,减少模板代码。不是让你写业务逻辑的。
mapstruct只是处理两个对象的转换的,除了转换别做那么多事情。为了方便,可以做一些枚举的映射、数据类型转换或数据展示形式转换。但是,业务逻辑不应该在这里面写,尤其是一看就属于领域层的业务逻辑。
你判断个能否编辑,判断逻辑竟然在mapstruct里面,咋想的???这明显应该放到领域里面。
9. 某个领域逻辑(比如 canChangeXXX())应该放到 Domain Entity 还是 Domain Service?
如果逻辑只依赖对象自身的数据,应该放在 Domain Entity;
如果逻辑依赖多个对象或外部上下文,放到 Domain Service。
10. mq命名规范
Topic: {业务域}{实体} {动作},表达业务动作。
Tag:{子类型} or {状态},精细过滤,控制消费。
Consumer Group:{系统缩写}{功能模块}{动作}_group ,表示具体消费职责。
类型
名称
含义
Topic
resume_update_event
简历修改事件
Tag
education, work_exp, basic_info
更新了哪一块信息
Consumer Group
es_resume_sync_group
消费并同步数据到 Elasticsearch
类型
名称
含义
Topic
order_status_change
订单状态变更
Tag
paid, canceled, refunded, completed
对应具体变更类型
Consumer Group
notify_order_status_group
用于推送用户通知的消费者组
Topic / Tag 是“语义本身”,不需要后缀;Consumer Group 是“角色身份”,加 _group 更清晰也更实用。
11. 怎么 让 AI 成为真正的“项目协作成员”而不是代码补全工具?
tmd我怎么知道。
12. 有逻辑删除的表 left join的时候deleted = 0 应该写在 ON 后面
select * from A left join B ON A.id = B.xxx AND deleted = 0 千万不要写到 where 条件里面去了,会破坏left join
13. 为什么需要Lambda表达式
本质:为了解决 **函数无法作为一等公民**的问题。都这么说,什么TM的叫一等公民?
其实就是java8之前,Java 的函数不能独立存在,必须嵌在 类(interface)的方法中。这就导致一个核心问题:如果我只是想把一个逻辑(行为)传递给另一个方法,我居然得造一个接口,写一堆实现代码。
// 我想让线程执行一个行为,但只能传对象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("do something");
}
}).start();
// 有了 Lambda 之后,语义直接多了
new Thread(() -> System.out.println("do something")).start();
Lambda 让 Java 不再只是面向对象的语言,而是逐步拥有了函数式编程的能力,最关键的一点就是:行为可以当成值来传递了。
14. lambda 表达式原理
匿名内部类的实现方式是:编译时生成 class 文件。
Lambda 表达式实现方式是: lambda 表达式在编译期不会生成具体实现类,而是在运行期借助 invokedynamic + LambdaMetafactory 动态生成实例。且只会生成 Main.class,不会生成额外的 .class 文件。
编译期:lambda 发生了什么?
当你写下:Runnable r = () -> System.out.println("Hello");
编译器并不会为 lambda 生成一个 .class 文件(不像匿名内部类那样),而是转为 invokedynamic 指令,相当于埋了个“钩子”,告诉 JVM:“等你运行到这里时,请通过 LambdaMetafactory 来决定我该怎么执行”。
运行期:lambda 是怎么被实例化的?
当 JVM 执行到 lambda 表达式时,invokedynamic 被触发,会调用:java.lang.invoke.LambdaMetafactory.metafactory(...)
它做了几件事:
这个“动态生成类并创建实例”的过程只有一次,之后会缓存起来,提高性能。
关键点总结:
问题
结论
lambda 是什么时候生成的?
运行时,通过 LambdaMetafactory
编译期干了什么?
把 lambda 转换成 invokedynamic指令
lambda 有没有生成 class 文件?
没有显式生成,是运行时生成匿名类并加载到内存中的
是否每次都会重新生成?
不会,有缓存机制,一次生成,多次复用
15. BizContent 入参
“BizContent” 是在接入**第三方平台接口(尤其是支付、认证、平台网关类)**中常见的一种入参封装方式,全称可理解为 Business Content(业务内容)。这种设计通常出现在支付宝、微信、钉钉、众腾这类平台接口里。
通常指:将业务请求参数整体封装为一个 JSON 对象,统一放在一个字段里(例如 bizContent)传输给后端接口或三方系统。
其中:
规范统一,便于签名计算、扩展性强、对接统一、降低接口爆炸风险。
{
"method": "apply.withdraw",
"timestamp": "2025-07-29 15:00:00",
"appId": "your-app-id",
"sign": "xxxxx",
"bizContent": {
"userId": 12345,
"amount": 500,
"bankCard": "6214***********"
}
}
16. Spring AOP 和 AspectJ
Spring AOP 是基于“代理机制”的运行时 AOP,必须被spring管理。
AspectJ 是基于“编译期字节码增强”的静态 AOP。支持在任何类、任何方法上织入增强,即使不是 Spring 管理的对象。
对比项
Spring AOP
AspectJ
实现方式
运行时代理(JDK / CGLIB)
编译期 / 加载期字节码增强
支持对象
仅容器内 Bean
所有 Java 类
切点能力
方法级别
方法、字段、构造函数等全方位
性能
低频切面开销不大
高性能(无代理),适合高频调用
易用性
✅ 极易上手,Spring Boot 无缝支持
❌ 配置复杂,需要特殊编译器或加载器
调试维护
✅ 容易断点调试
❌ 增强不可见,调试复杂
实际使用率
✅ 占主流,90%+ 项目足够
❌ 少数场景才用(如性能优化、字节码探针)
17. Spring AOP 支持 5 种类型的通知
通常顺序是:@Around → @Before → [目标方法] → @AfterReturning / @AfterThrowing → @After
通知类型
注解
执行时机
前置通知
@Before
方法执行前
返回通知
@AfterReturning
方法正常返回后
异常通知
@AfterThrowing
方法抛出异常后
后置通知
@After
无论是否异常都会执行
环绕通知
@Around
最强大的,可包裹整个方法执行过程
18. Spring BeanPostProcessor 解耦扩展逻辑
BeanPostProcessor 是 Spring 容器提供的一个 扩展接口,允许你在 Spring 完成对 Bean 的实例化、依赖注入后,但在 Bean 被正式使用前,对 Bean 进行额外加工和处理。
简单说,BeanPostProcessor 就像一个“Bean 的加工厂”或“拦截器链”,Spring 会在 Bean 初始化的前后阶段调用它的方法,让你对 Bean 做增强、包装、修改、替换。
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
postProcessBeforeInitialization:在 Bean 的初始化方法(比如 @PostConstruct、InitializingBean.afterPropertiesSet()、init-method)调用之前执行。作用是:给你机会在初始化前对 Bean 进行加工,比如修改属性、替换 Bean、做某些预处理。
postProcessAfterInitialization:在初始化方法执行完毕后调用,Spring AOP 就是利用这个时机,给目标 Bean 包装代理对象,返回代理替代原始 Bean。
为何@PostConstruct 里面用不了aop?
答:他一个在before里搞的,怎么用在after里面搞的AOP嘛。
19. 加速maven打包
maven默认会执行测试代码,以下命令可以不编译不执行测试代码。加快速度。
.\mvnw clean package "-Dmaven.test.skip=true" -T 1C -s D:\mavenrepository\setting\xxx\settings.xml
20. 表名一般用名词还是用动词?
数据库表名应使用“名词”或“名词性短语”表示“实体集合”,不要用动词。
因为表是“数据的集合”,不是“行为的动作”。动词表示行为,行为应出现在代码逻辑或服务层中,而不是存储结构中。
动词命名
问题
create_user
像是某个 API 或函数
update_status
看起来像操作,不像存储
insure_config
"insure" 是动词,看起来像“去配置”而不是“配置项”
正确命名风格应该是“名词 + 修饰语”:
表名
说明
user
用户表
user_profile
用户档案
user_insurance_strategy
用户的保险策略配置项(策略是名词)
insurance_policy_record
保单记录(全是名词)
position_user_assignment
职位-用户关系绑定表