码匠笔记

用心雕琢

把《阿里巴巴Java开发手册》读薄

访问

描述

最近读了一下《阿里巴巴Java开发手册》,下面是一些应该注意问题的整理。其实读这个手册最重要明白了两个问题
第一点,一个开发团队一定要有一个自己的编程规约。
第二点,优化是从每一个小的细节入手。

编程命名

命名规则

1.【强制】所有编程相关的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
反例:_name / __name / $Object / name_ / name$ / Object$ 2.【强制】类名使用UpperCamelCase风格,以下情形例外:DO / BO / DTO / VO / AO
3.【强制】常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
4.【强制】抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以它要测试的类名开始,以Test结尾。
5.【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例:应用工具类包名为com.alibaba.aone.force.util;类名为MessageUtils(此规则参考spring的框架结构) 6.【强制】杜绝完全不规范的缩写,集团认可的缩写请参考附2。
反例:<某业务代码>AbstractClass“缩写”命名成AbsClass;condition“缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。
7.【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达。
正例:在JDK中,对某个对象引用的volatile字段进行原子更新的类名为:AtomicReferenceFieldUpdater。 反例:常见的方法内变量为int a; 的定义方式。
8.【推荐】如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。
正例:public class OrderFactory;
    public class LoginProxy;     public class ResourceObserver;
9.【参考】枚举类名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
正例:枚举名字:ProcessStatusEnum;成员名称:SUCCESS / UNKNOWN_REASON。
10.【参考】各层命名规约:
A) Service/DAO层方法命名规约
1) 获取单个对象的方法用get作前缀。
2) 获取多个对象的方法用list作前缀。
3) 获取统计值的方法用count作前缀。
4) 插入的方法用save/insert作前缀。
5) 删除的方法用remove/delete作前缀。
6) 修改的方法用update作前缀。

常量定义

1.【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
反例:String key = “Id#taobao_” + tradeId;
    cache.put(key, value);
  本例中同学A定义了缓存的key,然后缓存提取的同学B使用了Id#taobao来提取,少了下划线,导致故障。
2.【强制】long或者Long初始赋值时,使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解。
说明:Long a = 2l; 写的是数字的21,还是Long型的2?
3.【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
正例:缓存相关的常量放在类CacheConsts下;系统配置相关的常量放在类ConfigConsts下。
说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解,也不利于维护。
4.【推荐】如果变量值仅在一个固定范围内变化用enum类型来定义。
说明:如果存在名称之外的延伸属性使用enum类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。
正例:

1
2
3
4
5
6
7
8
public enum SeasonEnum {
    SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);

    private int seq;
    SeasonEnum(int seq) {
        this.seq = seq;
    }
}

格式规约

1.【强制】注释的双斜线与注释内容之间有且仅有一个空格。
正例:

1
2
// 这是示例注释,请注意在双斜线之后有一个空格
String ygb = new String();

2.【强制】单行字符数不超过120个,超出则需要换行,换行时遵循如下原则:
 1) 第二行相对第一行缩进4个空格,从第三行开始,不再继续缩进,参考示例。
 2) 运算符与下文一起换行。
 3) 方法调用的点符号与下文一起换行。
 4) 方法调用中的多个参数需要换行时,在逗号后进行。
 5) 在括号前不要换行,见反例。
正例:

1
2
3
4
5
6
StringBuffer sb = new StringBuffer();
// 超过120个字符的情况下,换行缩进4个空格,并且方法前的点号一起换行
sb.append("zi").append("xin")...
    .append("huang")...
    .append("huang")...
    .append("huang");

OOP规约

1.【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
2.【强制】不能使用过时的类或方法。
说明:java.net.URLDecoder 中的方法decode(String encodeStr) 这个方法已经过时,应该使用双参数decode(String source, String encode) 。接口提供方既然明确是过时接口,那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。
3.【强制】Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
正例:”test”.equals(object);
反例:object.equals(“test”);
说明:推荐使用java.util.Objects#equals (JDK7引入的工具类)
4.【强制】所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。
说明:对于Integer var=?在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
5.关于基本数据类型与包装数据类型的使用标准如下:
1) 【强制】所有的 POJO 类属性必须使用包装数据类型。
2) 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
3) 【推荐】所有的局部变量使用基本数据类型。
说明:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE 问题,或者入库检查,都由使用者来保证。正例:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。反例:比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
6.【强制】定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
反例:POJO 类的 gmtCreate 默认值为 new Date();但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。
7.【强制】POJO 类必须写 toString 方法。使用 IDE 中的工具:source> generate toString时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。
说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。

集合处理

  1. 【强制】关于 hashCode 和 equals 的处理,遵循如下规则:
    1) 只要重写 equals,就必须重写 hashCode。
    2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
    3) 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。
    说明:String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象作为 key 来使用。
  2. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。
    正例:
1
2
3
4
5
List list = new ArrayList(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);

3.【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

1
2
String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);

第一种情况:list.add(“yangguanbao”); 运行时异常。
第二种情况:str[0] = “gujin”; 那么 list.get(0)也会随之修改。
4.【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
正例:

1
2
3
4
5
6
7
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (删除元素的条件) {
        iterator.remove();
    }
}

5.【推荐】集合初始化时,指定集合初始值大小。
说明:HashMap 使用 HashMap(int initialCapacity) 初始化,正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loaderfactor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。
反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容量 7 次被迫扩大,resize 需要重建 hash 表,严重影响性能。
6.【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是一个 Set 集合对象;entrySet()返回的是 K-V 值组合集合。

并发处理

1.【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
2.【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
3.【推荐】使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至 await 方法,直到超时才返回结果。说明:注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。
4.【参考】volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。

控制语句

1.【强制】在高并发场景中,避免使用”等于”判断作为中断或退出的条件。
说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间 判断条件来代替。
反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变 成了负数,这样的话,活动无法终止。

注释规约

1.【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用// xxx 方式。 说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注释;在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率 。
2.【强制】所有的类都必须添加创建者和创建日期。
3.【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。
4.【推荐】与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。
5.【参考】好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。
反例:

1
2
// put elephant into fridge
put(elephant, fridge);

方法名 put,加上两个有意义的变量名 elephant 和 fridge,已经说明了这是在干什么,语义清晰的代码不需要额外的注释。

异常日志

1.【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。
说明:本手册明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null 的情况。
2. 【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
1)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
2) 数据库的查询结果可能为 null。
3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
5) 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
正例:使用 JDK8 的 Optional 类来防止 NPE 问题。
3.【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

1
2
3
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);

4.【强制】对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。 说明:logger.debug(“Processing trade with id: ” + id + “ and symbol: ” + symbol);如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
正例:(条件)

1
2
3
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}

正例:(占位符)
logger.debug(“Processing trade with id: {} and symbol : {} ”, id, symbol);
5.【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
正例:

其他

1.【强制】velocity 调用 POJO 类的属性时,建议直接使用属性名取值即可,模板引擎会自动按规范调用 POJO 的 getXxx(),如果是 boolean 基本数据类型变量(boolean 命名不需要加 is前缀),会自动调用 isXxx() 方法。说明:注意如果是 Boolean 包装类对象,优先调用 getXxx()的方法。

安全规约

1.【强制】隶属于用户个人的页面或者功能必须进行权限控制校验。
说明:防止没有做水平权限校验就可随意访问、修改、删除别人的数据,比如查看他人的私信内容、修改他人的订单。
2.【强制】用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
说明:个人手机号码显示为:158****9119,隐藏中间 4 位,防止隐私泄露。
3.【强制】用户请求传入的任何参数必须做有效性验证。
说明:忽略参数校验可能导致:
 page size 过大导致内存溢出
 恶意 order by 导致数据库慢查询
 任意重定向
 SQL 注入
 反序列化注入
 正则输入源串拒绝服务 ReDoS

MySQL 数据库

建表规约

1.【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint( 1 表示是,0 表示否)。
说明:任何字段如果为非负数,必须是 unsigned。
正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。
2.【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库
名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
正例:aliyun_admin,rdc_config,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name
3.【强制】表名不使用复数名词。
说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数 形式,符合表达习惯。
4.【强制】禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
5.【强制】主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。
6.【强制】小数类型为 decimal,禁止使用 float 和 double。
说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不 正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
7.【强制】如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
8.【强制】表必备三字段:id, gmt_create, gmt_modified。
说明:其中 id 必为主键,类型为 unsigned bigint、单表时自增、步长为 1。gmt_create, gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被 动更新。
9.【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
10.【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检 索速度。
正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。
对象 年龄区间 类型 字节 表示范围
人 150 岁之内 unsigned tinyint 1 无符号值:0 到 255
龟 数百岁 unsigned smallint 2 无符号值:0 到 65535
恐龙化石 数千万年 unsigned int 4 无符号值:0 到约 42.9 亿
太阳 约 50 亿年 unsigned bigint 8 无符号值:0 到约 10 的 19 次方

索引规约

1.【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明 显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必 然有脏数据产生。
2.【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索 引。
3.【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。
4.【推荐】利用延迟关联或者子查询优化超多分页场景。
说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过 特定阈值的页数进行 SQL 改写。
正例:先快速定位需要获取的 id 段,然后再关联:
SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
5. 【推荐】建组合索引的时候,区分度最高的在最左边。
正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即 可。
说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>? and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。

SQL 语句

1.【强制】不要使用 count(列名)或 count(常量)来替代 count(*),count(*)是 SQL92 定义的 标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
2.【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:以学生和成绩的关系为例,学生表中的 student_id是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为 级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻 塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
3.【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。

ORM 映射

1.【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。 说明:1)增加查询分析器解析成本。2)增减字段容易与 resultMap 配置不一致。
2.【强制】sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入。
3.【强制】更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。
4.【推荐】不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字 段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。

工程结构

1.【参考】分层领域模型规约:  DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
 DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
 BO(Business Object):业务对象。由 Service 层输出的封装业务逻辑的对象。
 AO(Application Object):应用对象。在 Web 层与 Service 层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
 VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
 Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止 使用 Map 类来传输。
2.【强制】定义 GAV 遵从以下规则:
1) GroupID 格式:com.{公司/BU }.业务线.[子业务线],最多 4 级。
说明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress 等 BU 一级;子业务线可选。
正例:com.taobao.jstorm 或 com.alibaba.dubbo.register
2) ArtifactID 格式:产品线名-模块名。语义不重复不遗漏,先到中央仓库去查证一下。
正例:dubbo-client / fastjson-api / jstorm-tool
3) Version:详细规定参考下方。
3.【强制】二方库版本号命名方式:主版本号.次版本号.修订号
1) 主版本号:产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级。
2) 次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改。
3) 修订号:保持完全兼容性,修复 BUG、新增次要功能特性等。
说明:注意起始版本号必须为:1.0.0,而不是 0.0.1 正式发布的类库必须先去中央仓库进 行查证,使版本号有延续性,正式版本号不允许覆盖升级。如当前版本:1.3.3,那么下一个 合理的版本号:1.3.4 或 1.4.0 或 2.0.0
4.【强制】依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致。
说明:依赖 springframework-core,-context,-beans,它们都是同一个版本,可以定义一 个变量来保存版本:${spring.version},定义依赖的时候,引用该版本。
5.【推荐】高并发服务器建议调小 TCP 协议的 time_wait 超时时间。 说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服 务器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上 调小此等待值。
正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒): net.ipv4.tcp_fin_timeout = 30
6.【推荐】调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)。
说明:主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对 应于一个 fd。主流的 linux 服务器默认所支持最大 fd 数量为 1024,当并发连接数很大时很容易因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。 建议将 linux 服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。
7.【推荐】给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump 信息。
说明:OOM 的发生是有概率的,甚至有规律地相隔数月才出现一例,出现时的现场信息对查错 非常有价值。
8.【推荐】在线上生产环境,JVM 的 Xms 和 Xmx 设置一样大小的内存容量,避免在 GC 后调整堆 大小带来的压力。

阿里巴巴Java开发手册(纪念版).pdf

扩展阅读

  1. JUC系列:ThreadPoolExecutor
  2. 细说 Java hashCode
  3. 从 Spring 集成 Mybatis 到 浅析Java动态代理
  4. 使用 Idea 创建 Spring Boot 项目
  5. 优雅的使用 ThreadLocal 传递参数
  6. 构建 Java 应用内存级缓存

作者

本文作者麻酱,欢迎讨论,指正和转载,转载请注明出处。
原文地址:把《阿里巴巴Java开发手册》读薄
如果兴趣可以关注作者微信订阅号:码匠笔记
majiangbiji

评论