大家好,欢迎来到IT知识分享网。
《Java 开发手册》是 Java 社区爱好者的集体智慧结晶和经验总结,经历了多次大规模一线实战的检验及不断完善,整理成册后,众多社区开发者踊跃参与打磨完善,系统化地整理成册,当前的最新版本是黄山版。现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点,其它维度的知识点也会影响到软件的最终交付质量。比如:五花八门的错误码会人地增加排查问题的难度;数据库的表结构和索引设计缺陷带来的系统架构缺陷或性能风险;工程结构混乱导致后续项目维护艰难;没有鉴权的漏洞代码容易被黑客攻击等。所以本手册以 Java 开发者为中心视角,划分为编程规约、异常日志、单元测试、安全规约、MySQL 数据库、工程结构、设计规约七个维度,再根据内容特征,细分成若干二级子目录。此外,依据约束力强弱及故障敏感性,规约依次分为【强制】、【推荐】、【参考】三大类。在延伸的信息中,“说明”对规约做了适当扩展和解释;“正例”提倡什么样的编码和实现方式;“反例”说明需要提防的雷区,以及真实的错误案例。
一、编程规约
1.1、命名风格
1.所有编程相关的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。解释:保证变量、类、方法命名的规范性,不允许下划线与美元等特殊字符可能具有特定的含义或者被语言本身保留使用,不利于代码的一致性与可读性。
2.所有的编程命名不允许中英文混合,一般使用纯英文,一些国际通用的纯中文也可以视为纯英文,比如:alibaba
3.代码中的命名以及注释中都不允许出现带有侮辱或者种族歧视的词语。
4.类名使用大驼峰命名规则,例如:ForceCode,以下情形例外:DO / PO / DTO / BO / VO / UID 等,例如:UserDO
5.方法名、参数名、成员变量、局部变量都统一使用 小驼峰命名风格,例如:localValue / getHttpMessage()
6.常量命名应该全部大写,单词间用下划线隔开,例如:MAX_STOCK_COUNT
7.抽象类命名使用 Abstract 或 Base 开头(抽象类用来被继承);异常类命名使用 Exception 结尾,测试类命名以它要测试的类的名称开始,以 Test 结尾。
8.类型与中括号紧挨相连来定义数组。例如:int[] a 尽量不要写成 int a[]
9.POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。注意:需要在<resultMap>设置从 is_xxx 到 xxx 的映射关系。
12.杜绝完全不规范的英文缩写,避免望文不知义,使用完整的英文单词。
13.为了达到代码自解释的目标,任何自定义编程元素在命名时,使用完整的单词组合来表达。
14.在常量与变量命名时,表示类型的名词放在词尾,以提升辨识度,例如:startTime/workQueue/nameList/TERMINATED_THREAD_COUNT
18.枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
例如:枚举类 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON
19.服务端各层命名规约:
注:POJO(Plain Old Java Object)是简单 Java 对象,POJO 可以作为基础数据模型,它没有任何继承特定类或实现特定接口的要求,仅包含属性字段及其 getter 和 setter 方法。
DO (Data Object) :数据对象/持久化对象。与数据库表结构直接对应,用于表示数据库中存储的数据,通常包含了实体的完整信息,主要用于数据访问层(DAO),用于数据的持久化操作。
DTO (Data Transfer Object):数据传输对象,用于 service 层,主要目的是为了在系统之间传递数据,特别是跨进程或网络服务调用时,减少数据传输量或者隐藏内部实现细节。
BO (Business Object):业务对象,BO 封装了一定业务逻辑,可能是对多个持久化对象(DO)的组合,包含复杂的业务规则和操作,并可能负责协调多个 DAO 或服务之间的交互。
VO (Value Object) :值对象/视图对象。主要是针对界面展示而设计的对象,其属性通常映射到前端显示所需的数据项上,也可能根据展示需求重新组织数据结构。
1.2、常量定义
1.不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。例如下面的变量定义不允许出现:String key = “Id#taobao_” + tradeId;
2.long 或 Long 赋值时,数值后使用大写 L,不能是小写 l,小写容易跟数字混淆,造成误解。例如:public static final Long NUM = 2L;
3.浮点数类型的数值后缀统一为大写的 D 或 F。例如:
4.不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。例如:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 SystemConfigConsts 下。
6.如果变量值仅在一个固定范围内变化用 enum 类型来定义。
1.3、代码格式
3.if / for / while / switch / do 等保留字与左右括号之间都必须加空格。
4.任何二目、三目运算符的左右两边都需要加一个空格。
5.采用 4 个空格缩进,禁止使用 Tab 字符。
6.注释的双斜线与注释内容之间有且仅有一个空格。
7.在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。例如:double first = 3.2D; int second = (int)first + 2;
9.方法参数在定义和传入时,多个参数逗号后面必须加空格。例如:method(args1, args2, args3);
上面 method 方法 args1 的逗号后面需要加一个空格。
11.单个方法的总行数不超过 80 行。主干代码保留,修饰性的共性代码抽取出来方便复用和维护。
12.没有必要增加若干空格来使变量的赋值等号与上一行对应位置的等号对齐。
13.不同逻辑、不同语义、不同业务的代码之间插入一个空行,分隔开来以提升可读性。
1.4、OOP 规约
2.所有的覆写方法,必须加 @Override 注解,可以判断是否覆盖成功,加上注解覆盖失败会编译报错。
3.相同参数类型,相同业务含义,才可以使用的可变参数,参数类型避免定义为 Object。可变参数必须放置在参数列表的最后。(建议开发者尽量不用可变参数编程)
例如:public List<User> listUsers(String type, Long… ids) {…}
5.不能使用过时的类或方法。
6.Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
例如:“test”.equals(param); 其中”test”与参数 param 的位置不能互换。
例如:
8.任何货币金额,均以最小货币单位且为整型类型进行存储。
1)指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。
2)使用 BigDecimal 来定义值,再进行浮点数的运算操作。
10.BigDecimal 的等值比较应使用 compareTo() 方法,而不是 equals() 方法。equals() 方法会比较值和精度(1.0 与 1.00 返回结果为 false),而 compareTo() 则会忽略精度。
11.定义数据对象 DO 类时,属性类型要与数据库字段类型相匹配。例:数据库字段的 bigint 必须与类属性的 Long 类型相对应。
应该用如下方式:
14.定义 DO / PO / DTO / VO 等 POJO 类时,不要设定任何属性默认值。
16.构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
17.POJO 类必须写 toString 方法。出现问题便于排查。
23.循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行 append 操作,最后通过 toString() 返回 String 对象,造成内存资源浪费。
25.慎用 Object 的 clone 方法来拷贝对象。说明:对象 clone 方法默认是浅拷贝,若想实现深拷贝需覆写 clone 方法实现域对象的深度遍历式拷贝。
1.5、日期时间
4.不允许在程序任何地方中使用: 1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp。
5.禁止在程序中写死一年为 365 天,避免在公历闰年时出现日期转换错误或程序逻辑错误。
6.避免公历闰年 2 月问题。闰年的 2 月份有 29 天,一年后的那一天不可能是 2 月 29 日。
1.6、集合处理
解释:在 Java 8 中,java.util.stream.Collectors类提供了许多用于从 Stream 转换为集合、Map 或其他复杂数据结构的方法。当使用toMap()方法将 Stream 转换为 Map 时,如果源 Stream 中有多个元素具有相同的键(key),那么如果没有提供合并函数(merge function),就会抛出IllegalStateException异常,提示“Duplicate key”。
// 反例 String[] departments = new String[]{
"RDC", "RDC", "KKB"}; // 抛出 IllegalStateException 异常 Map<Integer, String> map = Arrays.stream(departments) .collect(Collectors.toMap(String::hashCode, str -> str));
// 正例 List<Pair<String, Double>> pairArrayList = new ArrayList<>(3); pairArrayList.add(new Pair<>("version", 12.10)); pairArrayList.add(new Pair<>("version", 12.19)); pairArrayList.add(new Pair<>("version", 6.28)); // key相同,保留第二个 Map<String, Double> map = pairArrayList.stream() .collect(Collectors.toMap(Pair::getKey, Pair::getValue,(v1,v2)->v2)) ; for(Map.Entry m : map.entrySet()){
// version 6.28 System.out.println(m.getKey() + " " + m.getValue()); }
解释:
ArrayList.subList(int fromIndex, int toIndex) 方法返回的是 ArrayList 的一个内部类实例,名为 SubList,它实现了 List 接口,并且继承自 AbstractList 类。虽然 SubList 行为上看起来像是一个独立的列表,但实际上它是一个原列表(即调用 subList() 方法的 ArrayList)的视图或切片。
SubList 不是 ArrayList 类型,而是 RandomAccessSubList 类型,这是 ArrayList 为了支持快速随机访问而专门为其子列表实现的一个内部类,它继承了 SubList 并实现了 RandomAccess 接口,表明它支持快速随机访问元素。
List<String> nameList = new ArrayList<>() ; nameList.add("John") ; nameList.add("Steve") ; nameList.add("maria") ; List<String> subList = nameList.subList(0, 1); // 先对父列表进行插入操作 nameList.add("wang") ; // 对父类元素进行删除操作 nameList.remove(0) ; // 遍历子列表:抛出java.util.ConcurrentModificationException for(String key : subList){
System.out.println(key); }
List<String> nameList = new ArrayList<>() ; nameList.add("John") ; nameList.add("Steve") ; nameList.add("maria") ; // 等于0,动态创建于size相同的数组,性能最好 String[] strings = nameList.toArray(new String[0]);
List<String> lists = new ArrayList<>(2) ; List<String> list = null; // 抛出java.lang.NullPointerException lists.addAll(list) ;
String [] strings = {
"wang", "yang", "li"} ; // 数组转换成list List<String> list = Arrays.asList(strings); // 抛异常:java.lang.UnsupportedOperationException list.add("sun") ;
解释:<? extends T>表示上界通配符,具体表示可以接收类型为 T 及 T 的子类对象。
<? super T>表示的接收的一个未知的具体类型 T 的超类型,即 T 或 T 的父类型。
List<String> generics = null; List notGenerics = new ArrayList(10); notGenerics.add(new Object()); notGenerics.add(new Integer(1)); generics = notGenerics; // 此处抛出 ClassCastException 异常 String string = generics.get(0);
反例:
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); for(String s : list){
if ("2".equals(s)) {
// 抛出java.util.ConcurrentModificationException list.remove(s) ; } }
解释:
这段 Java 代码会抛出java.util.ConcurrentModificationException异常,这是因为你在遍历集合(List)的同时尝试修改它。在 Java 的 ArrayList(以及其他非线程安全集合类如 LinkedList 等)中,当迭代器遍历集合时,它期望集合在其遍历期间不会发生结构上的改变,即不会添加或移除元素。
当你在 for-each 循环内部调用list.remove(s)时,实际上是在尝试从正在遍历的列表中删除元素,这违反了迭代器对集合不变性的假设,因此抛出了ConcurrentModificationException异常。
正例:
使用迭代器本身的 api 在遍历过程中进行插入与删除操作:
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) {
String item = iterator.next(); if ("2".equals(item)) {
iterator.remove(); } }
// diamond 方式,即<> HashMap<String, String> userCache = new HashMap<>(16); // 全省略方式 ArrayList<String> users = new ArrayList(10);
// diamond 方式,即<> HashMap<String, String> userCache = new HashMap<>(16);
// diamond 方式,即<> Map<String, String> userCache = new HashMap<>(16); userCache.put("1", "liu") ; userCache.put("2", "wang") ; userCache.put("3", "hu") ; // for循环遍历 for(Map.Entry entry : userCache.entrySet()){
System.out.println(entry.getKey() + " " + entry.getValue()); } // Map.forEach()方法遍历 userCache.forEach((key, value)->{
System.out.println(key + " " + value); });
19.高度注意 Map 类集合 K / V 能不能存储 null 值的情况
1.7、并发处理
2.创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
解释:直接使用Executors类提供的默认配置可能会导致一些潜在的问题:
- 资源耗尽的风险:例如,
Executors.newFixedThreadPool(int nThreads)创建的线程池,其内部工作队列通常是一个无界的 LinkedBlockingQueue,这意味着如果提交的任务数量远大于线程池的最大线程数且这些任务不能及时完成时,工作队列会无限增长,最终可能导致内存溢出(OOM)。 - 不明确的运行规则:
Executors中的预定义线程池配置可能并不符合每个应用的具体需求。例如,默认配置可能没有设置合理的拒绝策略或空闲线程存活时间,这在特定场景下可能会导致性能问题或者异常处理不当。
因此,推荐的做法是直接通过构造ThreadPoolExecutor对象并显式地指定核心线程数、最大线程数、工作队列类型与容量、线程存活时间、以及拒绝策略等参数来创建线程池。这样开发者可以更精确地控制线程池的行为,从而避免资源耗尽的风险,并且更好地适应不同应用场景的需求。
Lock lock = new ReentrantLock(); // 加琐方法放到try块的外面 lock.lock(); // 这个地方不允许出现任何可能抛出异常的方法 try {
doSomething(); doOthers(); } finally {
lock.unlock(); }
Lock lock = new ReentrantLock(); // 加琐方法放到try块的外面 boolean isLock = lock.tryLock(); // 这个地方不允许出现任何可能抛出异常的方法 if(isLock) {
// 进入业务代码之前需要先去判断该线程是否持有琐 try {
doSomething(); doOthers(); } finally {
lock.unlock(); } }
解释:CountDownLatch通常用于一个或多个线程等待其他线程完成其工作的情况,通过计数器来协调这些线程。
- 每个线程退出前必须调用 countDown 方法:当一个线程完成了自己的任务部分后,它应该调用
countDown()方法将计数器减一。这样可以告知等待的线程(如主线程)有一个任务已完成。 - 线程执行代码注意 catch 异常:为了避免由于子线程内部抛出异常导致的未正确调用
countDown()方法,从而使得主线程无法正确地等待所有子线程完成,应在子线程的任务代码中妥善处理异常,确保即使发生异常也能调用countDown()方法。 - 确保 countDown 方法被执行到:这是因为主线程会调用
await()方法阻塞直到计数器归零,如果任何子线程没有成功调用countDown(),主线程可能会一直等待下去,或者达到设定的超时时间后返回结果,这都不是期望的行为。 - 子线程抛出异常堆栈,不能在主线程 try-catch 到:这意味着主线程无法直接捕获和处理从子线程中抛出的异常。要获取并处理子线程中的异常,需要在线程内部捕获异常,并通过某种方式(例如通过共享变量、Future.get() 或者 UncaughtExceptionHandler 等机制)传递给主线程。
因此,在使用 CountDownLatch 的场景下,开发者应当确保无论何时何地,只要子线程完成任务就应该减少计数,同时处理好子线程可能抛出的异常,以保证整个程序逻辑的正常进行。
解释:饿汉模式延迟实例化需要将对象用 volatile 修饰,因为 JVM 为了性能优化,会进行指令重排序,这可能会导致 NPE 问题,故需要使用 volatile 关键字进行禁止指令重排序。
class Helper{
// 由于编译器和处理器优化可能导致指令重排序,使得即使使用了双重检查锁定也可能出现问题 private volatile Helper helper = null; public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper(); } } } return helper; } }
AtomicInteger count = new AtomicInteger(); count.addAndGet(1) ;
如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
LongAdder longAdder = new LongAdder() ; // longAdder.add(1); longAdder.increment(); System.out.println(longAdder);
18.HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在开发过程中注意规避此风险。
解释:HashMap 在容量不够进行 resize(即扩容)时,如果并发环境下有多个线程同时尝试进行 resize 操作,确实有可能导致死链问题。这是因为当HashMap的大小发生变化时,它需要重新分配一个新的数组,并将旧数组中的元素迁移到新数组中。这个过程中涉及到大量的节点移动和指针重置,如果没有合适的同步控制,在多线程下可能会出现循环引用,形成死链(也称为“死循环”或“循环链表”),从而导致 CPU 使用率飙升。对于并发环境下的 HashMap 使用,最佳实践是选择线程安全的数据结构(如 ConcurrentHashMap),并在设计程序时充分考虑到并发访问可能带来的各种问题。
1.8、控制语句
public static void test(String s){
switch (s){
case "a": System.out.println("a"); break; case "b": System.out.println("b"); break; default: System.out.println("others"); } }
2.当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null 判断。
解释:不允许传入 null 给 switch,否则会报 NPE 问题。
Integer a = 1; Integer b = 2; Integer c = null; Boolean flag = false; // a*b 的结果是 int 类型,那么 c 会强制拆箱成 int 类型,抛出 NPE 异常 Integer result = (flag ? a * b : c);
1.9、注释规约
/ * @author nuist__NJUPT * @ClassName Main * @description: 阿里巴巴Java开发手册 * @date 2024/02/22 */ public class Main {
} / * * @param a 第一个数字 * @param b 第一个数字 * @return 求和 */ public static Integer getSum(Integer a, Integer b){
return a + b ; }
/ * @author nuist__NJUPT * @ClassName Main * @description: 阿里巴巴Java开发手册 * @date 2024/02/22 */
5.所有的枚举类型字段必须要有注释,说明每个数据项的用途。
8.在类中删除未使用的任何字段和方法、内部类;在方法中删除未使用的参数声明与内部变量。
1.10、前后端规约
这句话是在阐述服务器端重定向的两种处理方式及其潜在问题:
总结来说,该句话强调了在实现服务器端重定向时,应根据实际场景选择合适的方法,内部转发保证无缝且安全的内部处理,而外部重定向则需借助专门工具来确保 HTTPS 安全性和 URL 的一致性及可维护性。
13.前后端的时间格式统一为”yyyy-MM-dd HH:mm:ss”,统一为 GMT。
1.11、其它
解释:
当程序每次调用Pattern.compile()时,它都会消耗一定的时间去解析正则表达式语法,并构建相应的状态机或确定匹配算法的数据结构。如果同一个正则表达式在程序运行过程中需要反复使用,每次都重新编译显然是不必要的性能开销。因此,建议的做法是在类初始化、方法初始化或者程序启动阶段就预先编译好常用的正则表达式,然后将编译后的Pattern对象保存起来复用,而不是在频繁执行的方法体内每次都重新编译。
解释:这句话是关于 Velocity 模板引擎如何处理 Java POJO(Plain Old Java Object,普通 Java 对象)类的属性访问机制。Velocity 是一个用于生成动态内容的模板引擎,它可以方便地将变量、表达式嵌入到文本中进行输出。
Random random = new Random(); int anInt = random.nextInt(); long aLong = random.nextLong();
6.枚举 enum(括号内)的属性字段必须是私有且不可变。
8.任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。
二、日常日志
2.1、错误码
3.全部正常,但不得不填充错误码时返回五个零:00000。
8.错误码之外的业务信息由 error_message 来承载,而不是让错误码本身涵盖过多具体业务属性。
2.2、异常处理
5.事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务。
9.在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常使用 Throwable 类进行拦截。
解释:Throwable是 Java 中所有错误和异常的超类,它有两个主要子类:Error和Exception。其中Error通常表示系统级错误或严重故障,而Exception则涵盖了程序运行时可能出现的可捕获问题。
当我们在上述场景中使用try-catch语句来捕获异常时,直接捕获Throwable可以在一个地方处理所有预期之外的问题。
2.3、日志规约
三、单元测试
注:在实际的 IntelliJ IDEA 开发环境中,可以使用集成的代码覆盖率工具来评估和查看单元测试的覆盖率。
四、安全规约
解释:强调了在编写数据库操作代码时,应采取安全措施来防止 SQL 注入攻击。SQL 注入是一种常见的安全漏洞,攻击者通过输入恶意构造的 SQL 语句片段,改变原本 SQL 命令的逻辑,以达到非法获取数据、修改或删除记录等目的,具体来说,为了防止 SQL 注入,应该避免直接将用户输入的内容拼接到 SQL 查询字符串中,而是推荐采用参数绑定或者基于元数据(Metadata)字段值限定的方式来处理动态 SQL 条件。
4.用户请求传入的任何参数必须做有效性验证。
5.禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
CSRF 攻击场景举例: 假设银行网站有一个转账功能,URL 可能是https://bank.example/transfer?to_account=12345&amount=10000。攻击者可以构造一个 HTML 表单或者 JavaScript 脚本,嵌入到自己的网页中,当受害者在不知情的情况下访问该页面并触发表单提交或 AJAX 请求时,其浏览器会自动带上受害者在银行网站上的有效 session cookie,导致服务器误以为这是受害者自己发起的转账请求,并完成转账操作。
10.配置文件中的密码需要加密。
五、MySQL 数据库
5.1、建表规约
4.禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
解释:在使用 MySQL 数据库时,有一些特定的关键词是被 MySQL 系统保留用来执行特定功能或表示特殊含义的,这些关键词不能作为表名、列名或其他标识符来使用,否则会导致语法错误。
7.如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
12.库名与应用名称尽量一致。
13.如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
5.2、索引规约
注:墨菲定律强调了在设计、规划和执行任务时应考虑到最糟糕情况的可能性,并通过严谨的工作流程和备份机制来减少错误发生的概率。
正例说明: 在 SQL 查询语句 where a = ? and b = ? order by c 中,假设有一个组合索引是 a_b_c。这意味着索引按照字段 a、b、c 的顺序存储了数据,并且每个记录在索引树中的位置反映了这三个字段值的排序情况。
由于查询条件同时包含了 a 和 b 字段的等值匹配(即 WHERE 子句),并且 order by 子句要求对字段 c 进行排序,而 c 恰好是该组合索引的一部分,并且位于索引定义的最后。因此,在这种情况下,MySQL 可以利用这个组合索引来直接获取已经按 c 字段排序的数据,无需额外的文件排序操作(filesort),从而提高查询性能。
5.3、SQL 语句
5.代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。
6.不得使用外键与级联,一切外键概念必须在应用层解决。
7.禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
解释:IN 操作符用于在 SQL 查询语句中指定一个条件列表,例如:WHERE id IN (1, 2, 3, ..., n),它允许查询结果集中包含与列表中任何一个值相匹配的数据行。在设计 SQL 查询时要关注IN操作符的使用,尽量避免大集合带来的性能问题,并在必要时寻找替代方案以提高查询效率。
5.4、ORM 映射
4.sql.xml 配置参数使用:#{},#param# 不要使用 ${} 此种方式容易出现 SQL 注入。
7.更新数据表记录时,必须同时更新记录对应的 update_time 字段值为当前时间。
六、工程结构
6.1、应用分层
根据业务架构实践,结合业界分层规范与流行技术框架分析
6.2、二方库依赖
解释:
- SNAPSHOT 版本通常是指开发过程中的不稳定版本,它可能包含未完成的功能、修复中的 bug 或者是尚未经过充分测试的新特性。上线环境使用 SNAPSHOT 版本会带来不稳定性风险,因为 SNAPSHOT 版本可能会在任何时间点进行更新,导致线上应用的行为不可预测。
- 安全包除外是因为安全相关的更新往往需要快速响应,即使它们可能是 SNAPSHOT 版本,也必须尽快部署以修补已知的安全漏洞。
- 一旦一个 RELEASE 版本发布,不应该用新的内容直接覆盖原有的版本号,而应递增版本号来表示更新。这有助于维护版本历史清晰,避免因重复版本号造成混乱,并且使得依赖于特定版本的应用能够选择正确的版本进行更新。
- 不依赖 SNAPSHOT 版本有利于保证应用部署的幂等性,即多次部署相同的版本会产生相同的结果,这对于持续集成/持续部署(CI/CD)流程至关重要。
- 同时,由于 RELEASE 版本相对固定,不需要每次构建时都去检查是否有 SNAPSHOT 版本的更新,因此在编译时查找和下载依赖的速度更快,从而加快了打包构建的进程。
8.禁止在子项目的 pom 依赖中出现相同的 GroupId,相同的 ArtifactId,但是不同的 Version。
9.底层基础技术框架、核心数据管理平台、或近硬件端系统谨慎引入第三方实现。
6.3、服务器
七、设计规约
2.在需求分析阶段,如果与系统交互的 User 超过一类并且相关的 UseCase 超过 5 个,使用用例图来表达更加清晰的结构化需求。
八、其它
8.1、版本历史
2017/02/09 正式版 1.0.0->详尽版本 1.4.0->华山版 1.5.0->泰山版 1.6.0->嵩山版 1.7.0->2022/02/03 黄山版 1.7.1
8.2、专用名词解释
- POJO(Plain Ordinary Java Object):在本规约中,POJO 专指只有 setter / getter / toString 的简单类,包括 DO / DTO / BO / VO 等。
- DO(Data Object):阿里巴巴专指数据库表一 一对应的 POJO 类。 此对象与数据库表结构一 一对应,通过 DAO 层向上传输数据源对象。
- PO(Persistent Object):也指数据库表一 一对应的 POJO 类。 此对象与数据库表结构一 一对应,通过 DAO 层向上传输数据源对象。
- DTO(Data Transfer Object ):数据传输对象,Service 或 Manager 向外传输的对象。
- BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。
- Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。
- VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
- CAS(Compare And Swap) :解决多线程并行情况下使用锁造成性能损耗的一种机制,这是硬件实现的原子操作。CAS 操作包含三个操作数:内存位置、预期原值和新值。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
- GAV(GroupId、ArtifactId、Version):Maven 坐标,是用来唯一标识 jar 包。
- OOP(Object Oriented Programming):本文泛指类、对象的编程处理方式。
- AQS(AbstractQueuedSynchronizer):利用先进先出队列实现的底层同步工具类,它是很多上层同步实现类的基础,比如: ReentrantLock、CountDownLatch、 Semaphore 等,它们通过继承 AQS 实现其模版方法,然后将 AQS 子类作为同步组件的内部类,通常命名为 Sync。
- ORM(Object Relation Mapping):对象关系映射,对象领域模型与底层数据之间的转换,本文泛指 iBATIS,mybatis 等框架。
- NPE(java.lang.NullPointerException):空指针异常。
- OOM(Out Of Memory):源于 java.lang.OutOfMemoryError,当 JVM 没有足够的内存来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况。
- GMT(Greenwich Mean Time):指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。地球每天的自转是有些不规则的,而且正在缓慢减速,现在的标准时间是协调世界时(UTC),它由原子钟提供。
- 一方库:本工程内部子项目模块依赖的库(jar 包)。
- 二方库:公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar 包)。
- 三方库:公司之外的开源库(jar 包)。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/110570.html