代码篇 23
2.1 使用了并发工具类库,并不等于就没有线程安全问题了 23
2.1.1 没有意识到线程重用导致用户信息错乱的bug 23
2.1.2 使用了线程安全的并发工具,并不代表解决了所有线程安全问题 25
2.1.3 没有充分了解并发工具的特性,从而无法发挥其威力 28
2.1.4 没有认清并发工具的使用场景,因而导致性能问题 30
2.1.5 小结 32
2.1.6 思考与讨论 32
2.2 代码加锁:不要让锁成为烦心事 33
2.2.1 加锁前要清楚锁和被保护的对象是不是一个层面的 35
2.2.2 加锁要考虑锁的粒度和场景问题 36
2.2.3 多把锁要小心死锁问题 37
2.2.4 小结 40
2.2.5 思考与讨论 40
2.3 线程池:业务代码中最常用也最容易犯错的组件 41
2.3.1 线程池的声明需要手动进行 41
2.3.2 线程池线程管理策略详解 43
2.3.3 务必确认清楚线程池本身是不是复用的 47
2.3.4 需要仔细斟酌线程池的混用策略 48
2.3.5 小结 51
2.3.6 思考与讨论 51
2.3.7 扩展阅读 52
2.4 连接池:别让连接池帮了倒忙 54
2.4.1 注意鉴别客户端SDK是否基于连接池 55
2.4.2 使用连接池务必确保复用 60
2.4.3 连接池的配置不是一成不变的 64
2.4.4 小结 67
2.4.5 思考与讨论 67
2.5 HTTP调用:你考虑超时、重试、并发了吗 68
2.5.1 配置连接超时和读取超时参数的学问 69
2.5.2 Feign和Ribbon配合使用,你知道怎么配置超时吗 70
2.5.3 你知道Ribbon会自动重试请求吗 73
2.5.4 并发限制了爬虫的抓取能力 75
2.5.5 小结 77
2.5.6 思考与讨论 78
2.5.7 扩展阅读 78
2.6 20%的业务代码的Spring声明式事务可能都没处理正确 80
2.6.1 小心Spring的事务可能没有生效 80
2.6.2 事务即便生效也不一定能回滚 84
2.6.3 请确认事务传播配置是否符合自己的业务逻辑 86
2.6.4 小结 89
2.6.5 思考与讨论 90
2.6.6 扩展阅读 93
2.7 数据库索引:索引不是万能药 94
2.7.1 InnoDB是如何存储数据的 95
2.7.2 聚簇索引和二级索引 96
2.7.3 考虑额外创建二级索引的代价 97
2.7.4 不是所有针对索引列的查询都能用上索引 99
2.7.5 数据库基于成本决定是否走索引 101
2.7.6 小结 104
2.7.7 思考与讨论 104
2.8 判等问题:程序里如何确定你就是你 105
2.8.1 注意equals和==的区别 106
2.8.2 实现一个equals没有这么简单 110
2.8.3 hashCode和equals要配对实现 112
2.8.4 注意compareTo和equals的逻辑一致性 114
2.8.5 小心Lombok生成代码的坑 115
2.8.6 小结 117
2.8.7 思考与讨论 117
2.8.8 扩展阅读 118
2.9 数值计算:注意精度、舍入和溢出问题 119
2.9.1 “危险”的Double 120
2.9.2 考虑浮点数舍入和格式化的方式 121
2.9.3 用equals做判等,就一定是对的吗 122
2.9.4 小心数值溢出问题 123
2.9.5 小结 125
2.9.6 思考与讨论 125
2.9.7 扩展阅读 126
2.10 集合类:坑满地的List列表操作 127
2.10.1 使用Arrays.asList把数据转换为List的3个坑 127
2.10.2 使用List.subList进行切片操作居然会导致OOM 129
2.10.3 一定要让合适的数据结构做合适的事情 132
2.10.4 小结 136
2.10.5 思考与讨论 137
2.11 空值处理:分不清楚的null和恼人的空指针 138
2.11.1 修复和定位恼人的空指针问题 138
2.11.2 POJO中属性的null到底代表了什么 142
2.11.3 小心MySQL中有关NULL的3个坑 146
2.11.4 小结 147
2.11.5 思考与讨论 147
2.12 异常处理:别让自己在出问题的时候变为盲人 149
2.12.1 捕获和处理异常容易犯的错 149
2.12.2 小心finally中的异常 153
2.12.3 需要注意JVM针对异常性能优化导致栈信息丢失的坑 155
2.12.4 千万别把异常定义为静态变量 157
2.12.5 提交线程池的任务出了异常会怎样 158
2.12.6 小结 161
2.12.7 思考与讨论 162
2.12.8 扩展阅读 163
2.13 日志:日志记录真没你想象得那么简单 164
2.13.1 为什么我的日志会重复记录 165
2.13.2 使用异步日志改善性能的坑 169
2.13.3 使用日志占位符就不需要进行日志级别判断了吗 175
2.13.4 小结 176
2.13.5 思考与讨论 176
2.13.6 扩展阅读 178
2.14 文件I/O:实现高效正确的文件读写并非易事 180
2.14.1 文件读写需要确保字符编码一致 180
2.14.2 使用Files类静态方法进行文件操作注意释放文件句柄 182
2.14.3 注意读写文件要考虑设置缓冲区 184
2.14.4 小结 187
2.14.5 思考与讨论 187
2.14.6 扩展阅读 188
2.15 序列化:一来一回,你还是原来的你吗 190
2.15.1 序列化和反序列化需要确保算法一致 191
2.15.2 MyBatisPlus读取泛型ListJSON字段的坑 195
2.15.3 注意JacksonJSON反序列化对额外字段的处理 198
2.15.4 反序列化时要小心类的构造方法 200
2.15.5 枚举作为API接口参数或返回值的两个大坑 201
2.15.6 小结 207
2.15.7 思考与讨论 207
2.16 用好Java8的日期时间类,少踩一些“老三样”的坑 208
2.16.1 初始化日期时间 209
2.16.2 “恼人”的时区问题 209
2.16.3 日期时间格式化和解析 212
2.16.4 日期时间的计算 215
2.16.5 小结 217
2.16.6 思考与讨论 218
2.16.7 扩展阅读 219
2.17 别以为“自动挡”就不可能出现OOM 220
2.17.1 太多份相同的对象导致OOM 220
2.17.2 使用WeakHashMap不等于不会OOM 223
2.17.3 Tomcat参数配置不合理导致OOM 227
2.17.4 小结 228
2.17.5 思考与讨论 229
2.17.6 扩展阅读 230
2.18 当反射、注解和泛型遇到OOP时,会有哪些坑 231
2.18.1 反射调用方法不是以传参决定重载 231
2.18.2 泛型经过类型擦除多出桥接方法的坑 232
2.18.3 注解可以继承吗 237
2.18.4 小结 239
2.18.5 思考与讨论 239
2.18.6 扩展阅读 241
2.19 Spring框架:IoC和AOP是扩展的核心 243
2.19.1 单例的Bean如何注入Prototype的Bean 244
2.19.2 监控切面因为顺序问题导致Spring事务失效 247
2.19.3 小结 255
2.19.4 思考与讨论 255
2.19.5 知识扩展:同样注意枚举是单例的问题 256
2.20 Spring框架:帮我们做了很多工作也带来了复杂度 258
2.20.1 FeignAOP切不到的诡异案例 258
2.20.2 Spring程序配置的优先级问题 264
2.20.3 小结 273
2.20.4 思考与讨论 273
2.20.5 扩展阅读 275
第3章 系统设计 281
3.1 代码重复:搞定代码重复的3个绝招 281
3.1.1 利用“工厂模式+模板方法模式”,消除if...else...和重复代码 281
3.1.2 利用“注解+反射”消除重复代码 287
3.1.3 利用属性拷贝工具消除重复代码 291
3.1.4 小结 293
3.1.5 思考与讨论 293
3.2 接口设计:系统间对话的语言,一定要统一 294
3.2.1 接口的响应要明确表示接口的处理结果 294
3.2.2 要考虑接口变迁的版本控制策略 300
3.2.3 接口处理方式要明确同步还是异步 302
3.2.4 小结 305
3.2.5 思考与讨论 305
3.2.6 扩展阅读 307
3.3 缓存设计:缓存可以锦上添花也可以落井下石 307
3.3.1 不要把Redis当作数据库 308
3.3.2 注意缓存雪崩问题 309
3.3.3 注意缓存击穿问题 312
3.3.4 注意缓存穿透问题 314
3.3.5 注意缓存数据同步策略 316
3.3.6 小结 317
3.3.7 思考与讨论 317
3.3.8 扩展阅读 318
3.4 业务代码写完,就意味着生产就绪了吗 320
3.4.1 准备工作:配置SpringBootActuator 321
3.4.2 健康监测需要触达关键组件 322
3.4.3 对外暴露应用内部重要组件的状态 327
3.4.4 指标是快速定位问题的“金钥匙” 330
3.4.5 小结 339
3.4.6 思考与讨论 339
3.5 异步处理好用,但非常容易用错 342
3.5.1 异步处理需要消息补偿闭环 342
3.5.2 注意消息模式是广播还是工作队列 346
3.5.3 别让死信堵塞了消息队列 351
3.5.4 小结 355
3.5.5 思考与讨论 356
3.6 数据存储:NoSQL与RDBMS如何取长补短、相辅相成 358
3.6.1 取长补短之RedisvsMySQL 358
3.6.2 取长补短之InfluxDBvsMySQL 361
3.6.3 取长补短之ElasticsearchvsMySQL 364
3.6.4 结合NoSQL和MySQL应对高并发的复合数据库架构 369
3.6.5 小结 371
3.6.6 思考与讨论 371
第4章 代码安全问题 373
4.1 数据源头:任何客户端的东西都不可信任 373
4.1.1 客户端的计算不可信 373
4.1.2 客户端提交的参数需要校验 375
4.1.3 不能信任请求头里的任何内容 377
4.1.4 用户标识不能从客户端获取 378
4.1.5 小结 380
4.1.6 思考与讨论 380
4.2 安全兜底:涉及钱时,必须考虑防刷、限量和防重 381
4.2.1 开放平台资源的使用需要考虑防刷 381
4.2.2 虚拟资产并不能凭空产生无限使用 382
4.2.3 钱的进出一定要和订单挂钩并且实现幂等 384
4.2.4 小结 386
4.2.5 思考与讨论 386
4.2.6 扩展阅读 386
4.3 数据和代码:数据就是数据,代码就是代码 387
4.3.1 SQL注入能干的事情比你想象得更多 388
4.3.2 小心动态执行代码时代码注入漏洞 393
4.3.3 XSS必须全方位严防死堵 396
4.3.4 小结 403
4.3.5 思考与讨论 403
4.3.6 扩展阅读 404
4.4 如何正确地保存和传输敏感数据 405
4.4.1 如何保存用户密码 406
4.4.2 如何保存姓名和身份证号码 409
4.4.3 用一张图说清楚HTTPS 416
4.4.4 小结 418
4.4.5 思考与讨论 419
第5章 Java程序故障排查 420
5.1 定位Java应用问题的排错套路 420
5.1.1 生产问题的排查很大程度依赖监控 420
5.1.2 分析定位问题的套路 421
5.1.3 分析和定位问题需要注意的9个点 422
5.1.4 小结 424
5.1.5 思考与讨论 424
5.2 分析定位Java问题,一定要用好这些工具 425
5.2.1 使用JDK自带工具查看JVM情况 425
5.2.2 使用Wireshark分析SQL批量插入慢的问题 433
5.2.3 使用MAT分析OOM问题 438
5.2.4 使用Arthas分析高CPU问题 444
5.2.5 小结 448
5.2.6 思考与讨论 449
5.3 Java程序从虚拟机迁移到Kubernetes的一些坑 452
5.3.1 PodIP不固定带来的坑 452
5.3.2 程序因为OOM被杀进程的坑 453
5.3.3 内存和CPU资源配置不适配容器的坑 454
5.3.4 Pod重启以及重启后没有现场的坑 455
5.3.5 小结 455
5.3.6 思考与讨论 456
后记:写代码时,如何才能尽量避免踩坑 457