《阿里巴巴 Java 开发手册 》读书笔记
嗯,不知道啥时候搞的这个,无意中打开,感觉挺好,讲到了好多坑。利用这几天刷完,^_^ ^_^,加油生活!。书的PDF资源在最后。
2019.7.3
努力就是忘记自己在做什么,而只顾去做。——王小波
嗯,一些名词
RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易,RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
SOA(Service-Oriented Architecture)既面向服务的体系结构,是一个组件模型,它将应用程序员的不同功能但是(称为服务)通过定义良好的接口联系起来。SOA建立在Web服务的基础上,它可以看做是B/S模型、XML/Web Service技术之后的自然延伸。以SOA架构的系统能够更加从容的面对业务的急剧变化。是一种架构模型,面向服务架构,它可以根据需求通过网络对松散耦合的粗粒度应用组件进行分布式部署、组合和使用。服务层是SOA的基础,可以直接被应用调用,从而有效控制系统中与软件代理交互的人为依赖性。
SOA解决多服务凌乱问题,SOA架构解决数据服务的复杂程度,同时SOA又有一个名字,叫做服务治理。SOA就是帮助我们把服务之间调用的乱七八糟的关系给治理起来,然后提供一个统一的标准,
统一标准:各系统的协议、地址、交互方式。
新的交互方式:各个系统分别根据统一标准向数据总线进行注册,各子系统调用其他子系统时,我们并不关心如果找到其他子系统,我们只招数据总线,数据总线再根据统一标准找其他子系统,所以数据总线在这里充当一个只路人的作用。
OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。
内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装成受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
第一章:编程规范:
(一) 命名风格 :
- 代码中的命名均不能以下划线或美元符号 开始/结束。
- 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
- 类名使用 UpperCamelCase 风格,即单词都大写,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID 等
- 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从 驼峰形式。
- 常量命名全部大写,单词间用下划线隔开。
- 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾
- 类型与中括号紧挨相连来表示数组 : int[] arrayDemo
- POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
- 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用 单数形式,但是类名如果有复数含义,类名可以使用复数形式。
- 杜绝完全不规范的缩写,避免望文不知义,为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词 组合来表达其意 。
- 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。 说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。 正例:public class OrderFactory; public class LoginProxy; public class ResourceObserver;
- 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁 性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是 与接口方法相关,并且是整个应用的基础常量。
- 接口和实现类的命名有两套规则:对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部 的实现类用 Impl 的后缀与接口区别。 正例:CacheServiceImpl 实现 CacheService 接口。 如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形式)。 正例:AbstractTranslator 实现 Translatable 接口。
- 枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。 说明:枚举其实就是特殊的类,域成员均为常量,且构造方法被默认强制是私有。
- 各层命名规约:
- Service/DAO 层方法命名规约
- 1) 获取单个对象的方法用 get 做前缀。
- 2) 获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects。
- 3) 获取统计值的方法用 count 做前缀。
- 4) 插入的方法用 save/insert 做前缀。
- 5) 删除的方法用 remove/delete 做前缀。
- 6) 修改的方法用 update 做前缀。
- 领域模型命名规约
- 1) 数据对象:xxxDO,xxx 即为数据表名。
- 2) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
- 3) 展示对象:xxxVO,xxx 一般为网页名称。
- 4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO
- Service/DAO 层方法命名规约
(二) 常量定义 :
- 不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。 反例:String key = "Id#taobao_" + tradeId;
- 在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l。
- 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护
- 常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包 内共享常量、类内共享常量。
- 1) 跨应用共享常量:放置在二方库中,通常是 client.jar 中的 constant 目录下。
- 2) 应用内共享常量:放置在一方库中,通常是子模块中的 constant 目录下。
- 3) 子工程内部共享常量:即在当前子工程的 constant 目录下。
- 4) 包内共享常量:即在当前包下单独的 constant 目录下。
- 5) 类内共享常量:直接在类内部 private static final 定义。
- 如果变量值仅在一个固定范围内变化用 enum 类型来定义。
public enum SeasonEnum { SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4); private int seq; SeasonEnum(int seq){ this.seq = seq;
} }
(三) 代码格式
- 大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果 是非空代码块则:
- 1) 左大括号前不换行。
- 2) 左大括号后换行。
- 3) 右大括号前换行。
- 4) 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
- 左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格;而左大 括号前需要空格
- if/for/while/switch/do 等保留字与括号之间都必须加空格。
- 任何二目、三目运算符的左右两边都需要加一个空格
- 采用 4 个空格缩进,禁止使用 tab 字符
public static void main(String[] args) { // 缩进 4 个空格 String say = "hello"; // 运算符的左右必须有一个空格 int flag = 0; // 关键词 if 与括号之间必须有一个空格,括号内的 f 与左括号,0 与右括号不需要空格 if (flag == 0) { System.out.println(say); } // 左大括号前加空格且不换行;左大括号后换行 if (flag == 1) { System.out.println("world"); // 右大括号前换行,右大括号后有 else,不用换行 } else { System.out.println("ok"); // 在右大括号后直接结束,则必须换行 } }
- 注释的双斜线与注释内容之间有且仅有一个空格
- 单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
- 1) 第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
- 2) 运算符与下文一起换行。
- 3) 方法调用的点符号与下文一起换行。
- 4) 方法调用中的多个参数需要换行时,在逗号后进行。
- 5) 在括号前不要换行,见反例。
- 方法参数在定义和传入时,多个参数逗号后边必须加空格
- IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式, 不要使用 Windows 格式。
- 单个方法的总行数不超过 80 行
- 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。没有必要增加若干空格来使某一行的字符与上一行对应位置的字符对齐
(四) OOP 规约
- 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成 本,直接用类名来访问即可。
- 所有的覆写方法,必须加@Override 注解
- 相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object,尽量不用可变参数编程
- 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生 影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
- 方法声明的两个组件构成了方法签名 - 方法的名称和参数类型。
- @Deprecated ,用与表示某个元素是否以过时,当使用过时的接口和方法时会出警告。
- 不能使用过时的类或方法
- Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。 "test".equals(object);
- 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
- 关于基本数据类型与包装数据类型的使用标准如下:
- 1) 所有的 POJO 类属性必须使用包装数据类型。
- 2) RPC 方法的返回值和参数必须使用包装数据类型。
- 3) 所有的局部变量使用基本数据类型
- 定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值
- 序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如 果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
- 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
- POJO 类必须写 toString 方法。如果继承了另一个 POJO 类,注意在前面加一下 super.toString()。
- 禁止在 POJO 类中,同时存在对应属性 xxx 的 isXxx()和 getXxx()方法。
- 使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无 内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。
- 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法, 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,
- setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在 getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度。
- 循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展,
- 1,“+”号拼接,时间最长,等价于srt =new StringBuilder(srt).append(“c”).toString();
2,concat方法拼接,时间中等,每次都会新建一个String对象;
3,append方法拼接字符串,时间最快,只生成一个String对象。
- 1,“+”号拼接,时间最长,等价于srt =new StringBuilder(srt).append(“c”).toString();
- final 可以声明类、成员变量、方法、以及本地变量,下列情况使用 final 关键字:
- 1) 不允许被继承的类,如:String 类。
- 2) 不允许修改引用的域对象。
- 3) 不允许被重写的方法,如:POJO 类的 setter 方法。
- 4) 不允许运行过程中重新赋值的局部变量。
- 5) 避免上下文重复使用一个变量,使用 final 描述可以强制重新定义一个变量,方便更好 地进行重构
- 慎用 Object 的 clone 方法来拷贝对象。 :对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现域对象的 深度遍历式拷贝,即重写全部的依赖对象 的clone方法,调用父类的clone 方法。也可以使用序列化方式。
- 类成员与方法访问控制从严:
- 1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
- 2) 工具类不允许有 public 或 default 构造方法。
- 3) 类非 static 成员变量并且与子类共享,必须是 protected。
- 4) 类非 static 成员变量并且仅在本类使用,必须是 private。
- 5) 类 static 成员变量如果仅在本类使用,必须是 private。
- 6) 若是 static 成员变量,考虑是否为 final。
- 7) 类成员方法只供类内部调用,必须是 private。
- 8) 类成员方法只对继承类公开,那么限制为 protected。
(五) 集合处理 :
- 关于 hashCode 和 equals 的处理,遵循如下规则:
- 1) 只要重写 equals,就必须重写 hashCode。
- 2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的 对象必须重写这两个方法。
- 3) 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。
- 说明:String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象 作为 key 来使用。
- ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException 异常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
- 说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
- subList方法是由AbstractList实现的,它会根据是不是随机存储提供不同的实现方法,SubList返回的类也是AbstractList的子类,其所有的方法(get,add,set,remove等)都是在原始列表上操作的,它自身并没有生成一个数组或是链表,也就是子列表只是原列表的一个视图(View),所有的修改都反映在原列表上。
- 在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、 增加、删除 产生ConcurrentModificationException 异常。因为subLis取出的列表只是原列表的一个视图,原数据集修改了,但是subList取出的子列表不会重新生成一个新列表,后面的对子列表继续操作时,就会检测到修改计数器与预期的不相同,会抛出并发修改异常,subList的其他方法也会检测修改计数器,例如Set,get,add方法,如果生成子列表在操作原列表,必然会导致视图不稳定,有效的办法是通过Collections.unmodifiableList方法设置列表为只读状态当有多个字列表时,任何一个子列表就都不能修改啦。生成子列表后,原列表保持只读状态。防御式编程。
- 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全 一样的数组,大小就是 list.size(),使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配 内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[ list.size() ] 的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集 合元素个数一。致。直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它 类型数组将出现 ClassCastException 错误。
List<String> list = new ArrayList<String>(2); list.add("guan"); list.add("bao"); String[] array = new String[list.size()]; array = list.toArray(array);
- 使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方 法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。 说明:asList 的返回对象是一个 Arrays 静态内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式(是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能),只是转换接口,后台的数据仍是数组。
- String[] str = new String[] { "you", "wu" }; List list = Arrays.asList(str);
- 第一种情况:list.add("yangguanbao"); 运行时异常。
- 第二种情况:str[0] = "gujin"; 那么 list.get(0)也会随之修改。
- 泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方 法,指定上界,即指参与读操作,而<? super T>不能使用 get 方法,指定下界,只参与写操作,作为接口调用赋值时易出错。
- 说明:扩展说一下 PECS(Producer Extends Consumer Super,生产者(Producer)使用extends,消费者(Consumer)使用super。)
- 原则:第一、频繁往外读取内 容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>。
- 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
List<String> list = new ArrayList<>();list.add("1");list.add("2");//迭代器遍历Iterator<String> iterator = list.iterator();while (iterator.hasNext()){String item = iterator.next();System.out.println(item);}for (String item : list) {if ("2".equals(item)) {list.remove(item);} }//报异常 Exception in thread "main" java.util.ConcurrentModificationException
- 在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort, Collections.sort 会报 IllegalArgumentException 异常。 三个条件如下
- 1) x,y 的比较结果和 y,x 的比较结果相反。
- 2) x>y,y>z,则 x>z。
- 3) x=y,则 x,z 比较结果和 y,z 比较结果相同。
- 集合泛型定义时,在 JDK7 及以上,使用菱形泛型,即 diamond,直接使用<>来指代前边已经指定的类型。
- 集合初始化时,指定集合初始值大小。 每一次集合扩容都是一次数组的拷贝。
- HashMap 使用 HashMap(int initialCapacity) 初始化。
- :initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即loader factor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。 反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容 量 7 次被迫扩大,resize 需要重建 hash 表,严重影响性能。
- 使用 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 值组合集合。
Map<String,Integer> map = new HashMap<>(50);for (int i = 0;i < 10;i++){// map.put(i+"",i);// map.put(String.valueOf(i),i);// map.put(Integer.toString(i),i);// map.put(((Integer)i).toString(),i);map.put(new StringBuilder("").append(Integer.toString(i)).toString(),i);}Set entry = map.entrySet();for (Map.Entry<String,Integer> maps:map.entrySet()){System.out.println(maps.getKey()+":"+maps.getValue());}//java 8 map.forEach((key,value) -> System.out.println(key + ":" + value));
- 高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
- 合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和 不稳定性(unorder)带来的负面影响。
- 有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次 序是一定的。如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是 order/sort。
- 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains 方法进行遍历、对比、去重操作。
(六) 并发处理 :
- 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
- 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
- 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
- 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 Executors 返回的线程池对象的弊端如下:
- 1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
- 2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
- SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。
- 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能 锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
- 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造 成死锁
- 并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加 锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
- 避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降
- 在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优 化问题隐患(可参考 The "Double-Checked Locking is Broken" Declaration),推荐解 决方案中较为简单一种(适用于 JDK5 及以上版本),将目标属性声明为 volatile 型。
- volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题, 但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推 荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
- HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在 开发过程中可以使用其它数据结构或加锁来规避此风险。
- ThreadLocal 无法解决共享对象的更新问题,ThreadLocal 对象建议使用 static 修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享 此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只 要是这个线程内定义的)都可以操控这个变量
ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
(七) 控制语句 :
- 在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程 序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且 放在最后,即使空代码。
- 在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避免采用 单行的编码方式:if (condition) statements;
- 在高并发场景中,避免使用”等于”判断作为中断或退出的条件,如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间 判断条件来代替
- 表达异常的分支时,少用 if-else 方式,这种方式可以改写成多个if语句
- 除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将 复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性
正例: // 伪代码如下 final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... } 反例: if ((file.open(fileName, "w") != null) && (...) || (...)) { ... }
- 避免采用取反逻辑运算符
- 接口入参保护,这种场景常见的是用作批量操作的接口。
- 循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、 获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)。
- 下列情形,需要进行参数校验:
- 1) 调用频次低的方法。
- 2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参 数错误导致中间执行回退,或者错误,那得不偿失。
- 3) 需要极高稳定性和可用性的方法。
- 4) 对外提供的开放接口,不管是 RPC/API/HTTP 接口。
- 5) 敏感权限入口。
- 下列情形,不需要进行参数校验:
- 1) 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。
- 2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底 层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所 以 DAO 的参数校验,可以省略。
- 3) 被声明成 private 只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参 数已经做过检查或者肯定不会有问题,此时可以不校验参数
(八) 注释规约 :
- 类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用 // xxx 方式
- 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。
- 所有的类都必须添加创建者和创建日期:idea修改方式:https://www.cnblogs.com/gongxin/p/8034606.html
- 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释 使用/* */注释,注意与代码对齐
- 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
- 与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持 英文原文即可。
- 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑 等的修改。
- 谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。
- 对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含 义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同
天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看 的,使其能够快速接替自己的工作。 - 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的 一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担
- 特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描, 经常清理此类标记。线上故障有时候就是来源于这些标记处的代码
(九) 其它 :
- 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。 不要在方法体内定义:Pattern pattern = Pattern.compile(“规则”);
- 后台输送给页面的变量必须加$!{var}——中间的感叹号,不明白
- 注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0≤x<1(能够 取到零值,注意除零异常),如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后 取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法
- 获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime();
- 不要在视图模板中加入任何复杂的逻辑,根据 MVC 理论,视图的职责是展示,不要抢模型和控制器的活。
- 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。
- 及时清理不再使用的代码段或配置信息。
第二章、异常日志
(一) 异常处理 :
- Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。即对可发生异常块能不通过异常块解决就不通过异常块解决。
- 异常不要用来做流程控制,条件控制。
- catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。 对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理
- 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容。
- 有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务。
- finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
- 不要在 finally 块中使用 return
- 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类
- 方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分 说明什么情况下会返回 null 值。
- 防止 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 问题。
- 定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(), 更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义 过的自定义异常,如:DAOException / ServiceException 等。
- 对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出; 跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误简 短信息”:关于 RPC 方法返回方式使用 Result 方式的理由:
- 1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。
- 2)如果不加栈信息,只是 new 自定义异常,加入自己的理解的 error message,对于调用 端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输 的性能损耗也是问题。
- 避免出现重复的代码(Don’t Repeat Yourself),即 DRY 原则
(二) 日志规约 :
- 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
- 日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
- 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式: appName_logType_logName.log。 logType:日志类型,如 stats/monitor/access 等;logName:日志描述。这种命名的好处: 通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
- 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方 式。
- 说明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
- 如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,
- 如果 symbol 是对象, 会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
- 正例:(条件)建设采用如下方式 if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); }
- 正例:(占位符) logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
- 避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。正例:<logger name="com.taobao.dubbo.config" additivity="false">
- 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过 关键字 throws 往上抛出。 正例:logger.error(各类参数或者对象 toString() + "_" + e.getMessage(), e);
- 谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使 用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘 撑爆,并记得及时删除这些观察日志。
- 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适 从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。
- 尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用 中文描述即可,否则容易产生歧义。国际化团队或海外部署的服务器由于字符集问题,【强制】 使用全英文来注释和描述日志错误信息。
三、单元测试
- 好的单元测试必须遵守 AIR 原则。
- :单元测试在线上运行时,感觉像空气(AIR)一样并不存在,但在测试质量的保障上, 却是非常关键的。好的单元测试宏观上来说,具有 A:Automatic(自动化) I:Independent(独立性) R:Repeatable(可重复)特点
- 好的单元测试应该是全自动执行的,
- 并且非交互式的。测试用例通常是被定期执行的,执 行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元 测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。
- 保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间 决不能互相调用,也不能依赖执行的先后次序
- 单元测试是可以重复执行的,不能受到外界环境的影响
- 对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级 别,一般是方法级别。
- 核心业务、核心应用、核心模块的增量代码确保单元测试通过。
- 单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。
- 单元测试的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都 要达到 100%
- 编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量
- B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
- C:Correct,正确的输入,并得到预期的结果。
- D:Design,与设计文档相结合,来编写单元测试。
- E:Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得 到预期的结果。
- 对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的, 或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。
- 和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者 对单元测试产生的数据有明确的前后缀标识。
- 对于不可测的代码建议做必要的重构,使代码变得可测,避免为了达到测试要求而 书写不规范测试代码。
- 在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好 覆盖所有测试用例
- 单元测试作为一种质量保障手段,不建议项目发布后补充单元测试用例,建议在项 目提测前完成单元测试
- 为了更方便地进行单元测试,业务代码应避免以下情况:
- 构造方法中做的事情过多。
- 存在过多的全局变量和静态方法。
- 存在过多的外部依赖。
- 存在过多的条件语句
第四章、安全规约
- 隶属于用户个人的页面或者功能必须进行权限控制校验。
- 用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
- 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入, 禁止字符串拼接 SQL 访问数据库。
- 用户请求传入的任何参数必须做有效性验证。
- 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
- 表单、AJAX 提交必须执行 CSRF 安全验证。
- 说明:CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户 不知情的情况下对数据库中用户参数进行相应修改。
- 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机 制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损
- 发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过 滤等风控策略。
第五章、MySQL 数据库
(一) 建表规约 :
- 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否)
- 任何字段如果为非负数,必须是 unsigned
- POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在<resultMap>设置 从 is_xxx 到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的 命名方式是为了明确其取值含义与取值范围
- 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
- 表名不使用复数名词。
- 禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字
- 主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。
- 小数类型为 decimal,禁止使用 float 和 double。 :float 和 double 在存储的时候,存在精度损失的问题
- 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
- varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长 度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索 引效率。
- 表必备三字段:id, gmt_create, gmt_modified,其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。gmt_create, gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被 动更新。
- 库名与应用名称尽量一致。
- 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释
- 字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
- 1)不是频繁修改的字段。
- 2)不是 varchar 超长字段,更不能是 text 字段。
- 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。 说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表
- 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检 索速度
(二) 索引规约 :
- 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
- 超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时, 保证被关联的字段需要有索引。
- 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度即可
- 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
- :索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索 引。
- 如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能
- 利用覆盖索引来进行查询操作,避免回表。 如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览 一下就好,这个目录就是起到覆盖索引的作用。 正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查 询的一种效果,用 explain 的结果,extra 列会出现:using index。
- 利用延迟关联或者子查询优化超多分页场景。 MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过 特定阈值的页数进行 SQL 改写, SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
- SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
- 建组合索引的时候,区分度最高的在最左边
- 防止因字段类型不同造成的隐式转换,导致索引失效。
(三) SQL 语句 :
- 不要使用 count(列名)或 count(常量)来替代 count(*),count(*)是 SQL92 定义的 标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。 说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
- count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。
- 当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题。
- 使用 ISNULL()来判断是否为 NULL 值。
- NULL 与任何值的直接比较都为 NULL。 1) NULL<>NULL 的返回结果是 NULL,而不是 false。 2) NULL=NULL 的返回结果是 NULL,而不是 true。 3) NULL<>1 的返回结果是 NULL,而不是 true
- 在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。
- 不得使用外键与级联,一切外键概念必须在应用层解决。 : 以学生和成绩的关系为例,学生表中的student_id是主键,那么成绩表中的student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为 级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻 塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
- 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
- 数据订正(特别是删除、修改记录操作)时,要先 select,避免出现误删除,确认 无误才能执行更新语句
- in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控 制在 1000 个之内
- 如果有国际化需要,所有的字符存储与表示,均以 utf-8 编码,注意字符统计函数 的区别。
- TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。 说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
(四) ORM 映射 :
- 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。 说明:
- 1)增加查询分析器解析成本。
- 2)增减字段容易与 resultMap 配置不一致。
- 3)无用字 段增加网络消耗,尤其是 text 类型的字段
- POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行 字段与属性之间的映射。 说明:参见定义 POJO 类以及数据库字段定义规定,在<resultMap>中增加映射,是必须的。 在 MyBatis Generator 生成的代码中,需要进行对应的修改
- 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需 要定义;反过来,每一个表也必然有一个 POJO 类与之对应。 配置映射关系,使字段与 DO 类解耦,方便维护。
- sql.xml 配置参数使用:#{}, #param# 不要使用${} 此种方式容易出现 SQL 注入
- iBATIS 自带的 queryForList(String statementName,int start,int size)不推 荐使用。:其实现方式是在数据库取到statementName对应的SQL语句的所有记录,再通过subList 取 start,size 的子集合。
- 不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。 说明:resultClass=”Hashtable”,会置入字段名和属性值,但是值的类型不可控
- 更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间
- 不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字 段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。
- @Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需 要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
- <isEqual>中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带 上此条件;<isNotEmpty>表示不为空且不为 null 时执行;<isNotNull>表示不为 null 值时 执行。
第六章、工程结构
(一) 应用分层 :
- 图中默认上层依赖于下层,箭头关系表示可直接依赖,如:开放接口层可以依赖于 Web 层,也可以直接依赖于 Service 层,依此类推:
- 开放接口层:可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;进行 网关安全控制、流量控制等。
- 终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染, JSP 渲染,移动端展示等。
- Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
- Service 层:相对具体的业务逻辑服务层。
- Manager 层:通用业务处理层,它有如下特征:
- 1) 对第三方平台封装的层,预处理返回结果及转化异常信息;
- 2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;
- 3) 与 DAO 层交互,对多个 DAO 的组合复用。
- DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase 等进行数据交互。
- 外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口
- 分层异常处理规约)在 DAO 层,产生的异常类型有很多,无法用细粒度的异常进 行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印 日志,因为日志在 Manager/Service 层一定需要捕获并打印到日志文件中去,如果同台服务 器再打日志,浪费性能和存储。在 Service 层出现异常时,必须记录出错日志到磁盘,尽可能 带上参数信息,相当于保护案发现场。如果 Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如果是单独部署,则采用与 Service 一致的处理方式。Web 层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,加上用户容易理解的错误提示信息。开放接口层要将异常处理成错误码 和错误信息方式返回。
- 分层领域模型规约:
- 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 类来传输。
(二) 二方库依赖 :
- 定义 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:详细规定参考下方。
- 依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致。 说明:依赖 springframework-core,-context,-beans,它们都是同一个版本,可以定义一 个变量来保存版本:${spring.version},定义依赖的时候,引用该版本。
- 禁止在子项目的 pom 依赖中出现相同的 GroupId,相同的 ArtifactId,但是不同的 Version。 说明:在本地调试时会使用各子项目指定的版本号,但是合并成一个 war,只能有一个版本号 出现在最后的 lib 目录中。可能出现线下调试是正确的,发布到线上却出故障的问题。
- 所有 pom 文件中的依赖声明放在<dependencies>语句块中,所有版本仲裁放在 <dependencyManagement>语句块中。 说明:<dependencyManagement>里只是声明版本,并不实现引入,因此子项目需要显式的声 明依赖,version 和 scope 都读取自父 pom。而<dependencies>所有声明在主 pom 的 <dependencies>里的依赖都会自动引入,并默认被所有的子项目继承。
- 二方库不要有配置项,最低限度不要再增加配置项,
- 为避免应用二方库的依赖冲突问题,二方库发布者应当遵循以下原则:
- 1)精简可控原则。移除一切不必要的 API 和依赖,只包含 Service API、必要的领域模型对 象、Utils 类、常量、枚举等。如果依赖其它二方库,尽量是 provided 引入,让二方库使用 者去依赖具体版本号;无 log 具体实现,只依赖日志框架。
- 2)稳定可追溯原则。每个版本的变化应该被记录,二方库由谁维护,源码在哪里,都需要能 方便查到。除非用户主动升级版本,否则公共二方库的行为不应该发生变化。
(三) 服务器
- 高并发服务器建议调小 TCP 协议的 time_wait 超时时间。
操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服 务器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上 调小此等待值。 正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒): net.ipv4.tcp_fin_timeout = 30 - 调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd),基于并发考虑。
- 给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump 信息。
- 在线上生产环境,JVM 的 Xms 和 Xmx 设置一样大小的内存容量,避免在 GC 后调整堆 大小带来的压力。
- 服务器内部重定向使用 forward;外部重定向地址使用 URL 拼装工具类来生成,否则 会带来 URL 维护不一致的问题和潜在的安全风险。
………………………………。
好吧,后面的完全看不懂,现在还一点都没涉及到,感觉看了也白看,嗯,等用到在看吧 ^_^,加油生活。 2019.7.5
>>>>>>>>>>>>>>>>.
实习带我的哥发了这个给我,把剩下的部分看了一遍,生活加油 。2020.6.9
岁月如流,就如月在当空,照着我们每一个人,但是每个人的生活都不一样。
——王小波
百度网盘资源
链接:https://pan.baidu.com/s/1Lvl4A5b54heBnYFfvS3kMQ
提取码:5vga
《阿里巴巴 Java 开发手册 》读书笔记相关推荐
- 读书笔记 | 墨菲定律
1. 有些事,你现在不做,永远也不会去做. 2. 能轻易实现的梦想都不叫梦想. 3.所有的事都会比你预计的时间长.(做事要有耐心,要经得起前期的枯燥.) 4. 当我们的才华还撑不起梦想时,更要耐下心来 ...
- 读书笔记 | 墨菲定律(一)
1. 有些事,你现在不做,永远也不会去做. 2. 能轻易实现的梦想都不叫梦想. 3.所有的事都会比你预计的时间长.(做事要有耐心,要经得起前期的枯燥.) 4. 当我们的才华还撑不起梦想时,更要耐下心来 ...
- 洛克菲勒的38封信pdf下载_《洛克菲勒写给孩子的38封信》读书笔记
<洛克菲勒写给孩子的38封信>读书笔记 洛克菲勒写给孩子的38封信 第1封信:起点不决定终点 人人生而平等,但这种平等是权利与法律意义上的平等,与经济和文化优势无关 第2封信:运气靠策划 ...
- 股神大家了解多少?深度剖析股神巴菲特
股神巴菲特是金融界里的传奇,大家是否都对股神巴菲特感兴趣呢?大家对股神了解多少?小编最近在QR社区发现了<阿尔法狗与巴菲特>,里面记载了许多股神巴菲特的人生经历,今天小编简单说一说关于股神 ...
- 2014巴菲特股东大会及巴菲特创业分享
沃伦·巴菲特,这位传奇人物.在美国,巴菲特被称为"先知".在中国,他更多的被喻为"股神",巴菲特在11岁时第一次购买股票以来,白手起家缔造了一个千亿规模的 ...
- 《成为沃伦·巴菲特》笔记与感想
本文首发于微信公众帐号: 一界码农(The_hard_the_luckier) 无需授权即可转载: 甚至无需保留以上版权声明-- 沃伦·巴菲特传记的纪录片 http://www.bilibili.co ...
- 读书笔记002:托尼.巴赞之快速阅读
读书笔记002:托尼.巴赞之快速阅读 托尼.巴赞是放射性思维与思维导图的提倡者.读完他的<快速阅读>之后,我们就可以可以快速提高阅读速度,保持并改善理解嗯嗯管理,通过增进了解眼睛和大脑功能 ...
- 读书笔记001:托尼.巴赞之开动大脑
读书笔记001:托尼.巴赞之开动大脑 托尼.巴赞是放射性思维与思维导图的提倡者.读完他的<开动大脑>之后,我们就可以对我们的大脑有更多的了解:大脑可以进行比我们预期多得多的工作:我们可以最 ...
- 读书笔记003:托尼.巴赞之思维导图
读书笔记003:托尼.巴赞之思维导图 托尼.巴赞的<思维导图>一书,详细的介绍了思维发展的新概念--放射性思维:如何利用思维导图实施你的放射性思维,实现你的创造性思维,从而给出一种深刻的智 ...
- 产品读书《滚雪球:巴菲特和他的财富人生》
作者简介 艾丽斯.施罗德,曾经担任世界知名投行摩根士丹利的董事总经理,因为撰写研究报告与巴菲特相识.业务上的往来使得施罗德有更多的机会与巴菲特亲密接触,她不仅是巴菲特别的忘年交,她也是第一个向巴菲特建 ...
最新文章
- python isinstance type区别
- IE浏览器跟火狐浏览器兼容写法3
- Java并发工具类(闭锁CountDownLatch)
- 【报错笔记】做struts项目建立jsp文件老是报错
- python函数式编程-匿名函数
- ubuntu自定义安装里怎么选_中央空调到底应该怎么选?小户型也能安装中央空调?行家说实话了...
- 用户空间与内核空间数据交换的方式------seq_file
- MultiResolution研究
- 百度地图Key的设置方法
- qt linux 国际化,多语言,配置CLion管理Qt项目国际化支持
- Python 函数知识
- 【Oracle】手工建库时启动到nomount状态时错误ORA-09925,ORA-01017
- 洛谷 P2181 对角线
- 【转】临界区、互斥对象
- 集成Slice2Java插件
- Mac 修改hosts文件
- Spring 创建切面
- 微信中无法下载APP的解决办法
- pcap头文件位置 Linux,pcap文件头的组织格式
- 相机去畸变软件OCamCalib的使用方法
热门文章
- [还不会搭建博客吗?]centos7系统部署hexo博客新手入门-进阶,看这一篇就够了
- 国之重器 望穿秋水——北邮老师廖建新介绍
- 微信小程序--订阅消息推送
- MapReduce调优方案
- Spring注解驱动开发-扩展原理之005-Spring容器刷新第五步-invokeBeanFactoryPostProcessors(beanFactory)
- 程序员一般通过什么途径接私活?
- 【MM小贴士】SAP工序外协初探
- ORACLE RAC TO RAC DG搭建过程中可能遇到的问题
- win7 通过IP地址或主机名给其他电脑发送弹窗信息
- Linux DPDK UIO