随意的世界 - Java
type: channel
link: http://1.117.140.145/Java
items: LongAdder实现原理
items: 有栈协程与无栈协程
items: Java ConcurrentSkipListMap
items: Apache Calcite
items: sleep、synchronized、LockSupport的线程阻塞有何区别？
items: Mybatis工厂模式
items: SOFA
items: SpringMVC父子容器
items: java9模块化
items: Log4j，Slf4j，Logback之间的区别

LongAdder实现原理
type: item
date: 2024-04-10T11:23:28+08:00
description: LongAdder实现原理Java并发工具类- LongAdder的实现原理过程 - 知乎 (zhihu.com)(4 封私信 / 80 条消息) 2020-10-22：谈谈java中LongAdder和LongAccumulator相同点和不同点？ - 知乎 (zhihu.com)Longadder的功能和atomicLong实际的作用其实是一样的，都是一个线程安全的元子类。其区别在于，AtomicLong的value是一个。而LongAdder将value分成了多个，分散了线程竞争，将本来对一个变量的多线程竞争，分散到多个变量上。AtomicLong中有个内部变量value保存着实际的long值，所有的操作都是针对该变量进行。也就是说，高并发环境下，value变量其实是一个热点，也就是N个线程竞争一个热点。LongAdder的基本思路就是分散热点，将value值分散到一个数组中，不同线程会命中到数组的不同槽中，各个线程只对自己槽中的那个值进行CAS操作，这样热点就被分散了，冲突的概率就小很多。如果要获取真正的long值，只要将各个槽中的变量值累加返回。组成先来看看LongAdder类中的重要属性继承了public class LongAdder extends Striped64 implements SerializableStriped64是什么？代表着64字节，即正好是一个CPU缓存行的大小。通常对intel来说，CPU读取内存的数据，会一次性读取64个字节，即正好是一个缓存行的大小，并且是一个原子性操作，不会有多个CPU同时读取这64个字节。Striped64类中的主要属性Ceel类Cell类上有一个@Contended的注解，这个注解的意思就是Cell代表着一个64字节的类@sun.misc.Contended static final class Cell { volatile long value; Cell(long x) { value = x; } final boolean cas(long cmp, long val) { return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long valueOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> ak = Cell.class; valueOffset = UNSAFE.objectFieldOffset (ak.getDeclaredField("value")); } catch (Exception e) { throw new Error(e); } } }base变量：当没有线程竞争时，首先会对base进行增加。Cells数组：当检测有base变量在被线程占有时，就会尝试去cell数组的变量加。总和为base变量+cells数组中的value /** * Table of cells. When non-null, size is a power of 2. */ transient volatile Cell[] cells; /** * Base value, used mainly when there is no contention, but also as * a fallback during table initialization races. Updated via CAS. */ transient volatile long base; ADD具体过程add方法/** * Adds the given value. * * @param x the value to add */ public void add(long x) { Cell[] as; long b, v; int m; Cell a; if ((as = cells) != null //当cells！=null，说明可能，那么就尝试对base变量的CAS || !casBase(b = base, b + x)) {//如果CAS base变量失败 就进入下面代码 boolean uncontended = true; //表示没有竞争 if (as == null || (m = as.length - 1) < 0 ||//情况1 cell数组中没有初始化 (a = as[getProbe() & m]) ==…

有栈协程与无栈协程
type: item
date: 2024-02-28T11:05:00+08:00
description: 有栈协程与无栈协程浅谈有栈协程与无栈协程 - 知乎 (zhihu.com)相关Java 虚拟线程和 Kotlin 协程走的两个方向。Java 是有栈协程，协程上下文保存在与执行处独立的栈上，所以异步函数可以直接像正常函数那样写，程序执行到阻塞部分时 JVM 内部自动挂起；Kotlin 是无栈协程，因为干预不了 JVM，所以协程上下文必须保存在堆上，同时得显式声明函数是可挂起的，否则不知道什么函数支持挂起（suspend 在转译为字节码时会添加 Continuation 参数）。干预不了 JVM，既是 Kotlin 协程的优势，也是它的劣势。因为不需要 JVM 出手，这个特性可以在旧版本 JVM 上实现，也可以在不支持有栈协程的平台上实现，比如 WASM。又因为必须标记可挂起函数，Kotlin 协程也暴露出典型的函数染色问题。如今虽不敢说协程已经是红的发紫，但确实是越来越受到了大家的重视。Golang中的已经是只有goroutine，以至于很多go程序员是只知有协程，不知有线程了。就连C++也在最新的C++20中原生支持协程。更不用说很多活跃的语言如python，java等，也都是支持协程的。尽管这些协程可能名称不同，甚至用法也不同，但它们都可以被划分为两大类，一类是有(stackful) 协程，例如 goroutine，libco；一类是无栈 (stackless) 协程，例如C++的协程。这里我们想说的一点是所谓的有栈，无栈并不是说这个协程运行的时候有没有栈，而是说协程之间是否存在调用栈（callbackStack）。其实仔细一想即可，但凡是个正在运行的程序，不管你是协程也好，线程也好，怎么可能在运行的时候不使用栈空间呢，调用参数往哪搁，局部变量往哪搁。我们知道的所有主流语言在调用另外一个函数的时候都存在一个调用栈，我们来解释一下调用栈这个词。函数的栈帧指esp和ebp之间的一块地址。拿图上来说ebp存储着Frame Pointer指向的地址，Return Address当然就是我们执行完最新的栈帧以后下一步要执行的指令地址。esp当然就是当前指向栈顶的指针了。有栈协程函数运行在调用栈上，把函数作为一个协程，那么这个协程的上下文就是这个函数及其嵌套函数的（连续的）栈帧存储的值，以及此时寄存器存储的值。如果我们调度协程，也就是保存当前正在运行的协程上下文，然后回复下个将要运行的协程上下文。这样我们就轻松完成了协程的调度。并且因为保存的上下文和普通函数执行的上下文是一样的，所有协程可以在任意嵌套函数中挂起（无协程不行）。有栈协程的优点在易用性上，通常只需要调用对应的方法，就可以切换上下文挂起协程。在有栈协程调度时，需要频繁切换上下文，开销较大。单从实现上看，有栈协程更接近于内核级线程，都需要为每个线程保存单独上下文（寄存器、栈等），区别在于油站协程的调度由应用程序自行实现，对内核是透明的，而内核级线程的调度由系统内核完成，是抢占式的。列入Golang在进行协程调度时会保存栈寄存器和程序计数器（汇编支持，再进行调度）。很多地方又把协程称为subroutine，subroutine是什么，就是函数。上古时期的计算机科学家早就给出了概念，coroutinue就是可以中断并恢复执行的subroutine，从这个角度来看协程拥有调用栈并不是一个奇怪的事情。我们再来在思考coroutinue与subroutine相比有什么区别，你会发现区别仅有一个，就是coroutinue可以中断并恢复，对应的操作就是yield、resume，这样看来subroutinue只不过是coroutinue的一个子集罢了。也就是说把携程当做一个特殊的函数使用，有栈协程就是我们理想中协程该有的模样。既然把其当做一个特殊的函数去调用，对我们来说最严峻的挑战就是如何像切换函数一样去切换协程，难点在于除了像函数一样切换出去，还要在满足某种条件的时候切换回来，我们的做法可以是在协程内部存储自身的上下文，并在需要切换的时候把上下文切换就可以了，我们知道其实上下文本质就计算器，所以保存上下文实际上就是把寄存器的值保存下来，有俩种方法，一种是使用汇编，libco就使用了这种方法。另一种是ucontext.h，这个封装好的库也可以帮助我们完成相关工作。汇编的话我们来看一看libco中对于32位机器的上下文切换操作是如何完成的：// 获取第一个参数 movl 4(%esp), %eax // 参数的类型我们暂且理解为一个拥有八个指针的数组，即regs | regs[7] | | regs[6] | | regs[5] | | regs[4] | | regs[3] | | regs[2] | | regs[1] | | regs[0] | -------------- <---EA…

Java ConcurrentSkipListMap
type: item
date: 2024-01-29T16:08:38+08:00
description: Java ConcurrentSkipListMap线程安全的跳表实现——Java ConcurrentSkipListMap 原理解析 - 知乎 (zhihu.com)前言LevelDB、RockDB以及Pebble等高性能键值对存储引擎都采用SkipList来作为自己的内存数据结构。相比使用红黑树，SkipList具有以下优点数据结构比红黑树更简单具备更好的并发优势。红黑树在插入式可能涉及到整颗树的rebalance，而SkipList可以仅在某个局部进行操作，要锁定的节点更少，从而实现更好的性能。本文基于JAVA JUC中实现的ConcurrentSkipList，来介绍一种线程安全的SkipList的无锁实现跳表基础为了更好地了解 ConcurrentSkipListMap 的实现，我们将它的核心逻辑抽离出来，先理解它的跳表部分实现，再看它在并发访问上做了哪些优化。此处示例代码结构尽可能保持与 java.util.concurrent.ConcurrentSkipListMap 一致，仅对部分代码命名做出修改，以便后续对并发部分进行讲解，因此可能会包含一些不符合常见代码规范的部分。ConcurrentSkipListMap 整体的数据结构如下图所示：ConcurrentSkipList中定义了以下三种节点数据节点Node：实际保存数据的节点，包含Map的Key/Value，并使用next指针串成一个链表节点索引节点Index：用于快速查找，内部包含数据节点的应用。Right指针指向下一个索引节点，down指向下一层的索引节点头结点HeadIndex：Index类的子类，相比Index多了一个Level字段，标识当前的SkipList的层数，具体代码定义如下// 此处简化了一些，要求 Key 实现了 Comparable 接口。 // 实际的 ConcurrentSkipListMap 允许传入自定义的 Key Comparator。 @NotThreadSafe public class SkipListMap<K extends Comparable<K>, V> { private static class Node<K, V> { final K key; private Object value; private Node<K, V> next; ... } private static class Index<K, V> { Node<K, V> node; Index<K, V> down; Index<K, V> right; ... } private static class HeadIndex<K, V> extends Index<K, V> { int level; ... } }初始化在初始化时，会初始化跳表的头节点，形成如下图所示的数据结构对应代码如下private HeadIndex<K, V> head; private final static Object BASE_HEADER = new Object(); /** * 初始化数据节点：Key=null, Value=BASE_HEADER，初始化 level=1 * 其他指针全部初始化为 null */ public SkipListMap() { head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null), null, null, 1); }SkipList的头结点不直接存储数据，仅作为跳表的头使用。插入数据的插入我们以最上面已经插入过 7 条数据的跳表为例。现有的数据 Key 分别为 {1, 3, 5, 7, 9, 11, 13}，我们要插入一条 Key 为 10 的记录。寻找插入位置，插入数据节点在寻找插入位时，需要先从head节点开始，找到离待插入位置最近的索引，逻辑如下向右查找：如果当前节点的right节点不为空且right节点的key小于插入数据的key则向右查找向下查找：如果不能向右查找（right位null或者right.node.key大于待插入的key），则向下或向右移动一层当无法向下或者向右移动时，返回该索引对应数据节点Node如上图所示，在访问到第3部的Index节点时，有由于它的右侧节点的值为11>10，无法向右侧移动；同时它的down节点为null，无法向下移动，因此返回它对应的数据节点，即key=7的节点（图中标红部分）注意：因为Index节点内部包含的是Node节点的引用，而不是使用down指针指向Node节点，因此在第3步对应的节点的down指针为null。对应代码如下public Node<K, V> findPrede…

Apache Calcite
type: item
date: 2024-01-18T11:44:25+08:00
description: Apache CalciteApache Calcite 为什么能这么流行 - 知乎 (zhihu.com)简介途中是使用apache calcite或至少关联的项目。那 Apache Calcite 究竟是干嘛的，又为什么能这么流行呢？首先，摆一个应该没多少人会反对的共识：SQL 是编程领域最流行的语言。有 MySQL、Oracle 之类使用 SQL 作为交互语言的数据库然后有 JDBC、ODBC 之类和各种数据库交互的标准接口有大量数据科学家和数据分析师等不太会编程语言但又要使用数据的人第一代大数据计算引擎 MapReduce 被 Hive SQL 很大程度上替代新一代大数据计算引擎 Spark 很快就推出了 Spark SQL最近几年大热的流处理引擎 Flink 很快也推出了 Flink SQL......比如要给MongoDB套上一个SQL的壳子，或者想要直接用SQL查一堆CSV文件，可怕没多少人能顺利实现。Apache Calcite的出现，让你能够很容易的给你的系统套上一个SQL的壳子，并且提供足够高效的查询性能优化。足够简答和focus的定位查询语言查询优化查询执行数据管理数据存储通常我们可以把数据库管理系统分为上面五个组件。Calcite在设计之初九确定了自己只关注和实现图中上面三个部分，而把数据管理和数据存储留给了外部系统、计算引擎。通常数据管理和数据存储，尤其是后者是很复杂的，也会由于数据本身的特性导致是线上的多样性。Calcite抛弃了这俩部分，而是专注于上层更加通用的模块。另一方面，Calcite 没有去重复造各种轮子，在该用现成的东西的时候，就直接用能找到的最合适的。这是个非常好的习惯，但也是非常容易被程序员抵触的方法。比如，作为一个SQL解决方案，关键的SQL解析这一步，Calcite没有选择造轮子，而是直接使用了开源JavaCC，来将SQL语句转换为JAVA代码，然后转换成一颗AST供下一阶段使用。javaCC全称为Java Compiler compiler，它是一个生成器，用于生成词法分析和语法分析器。它可以通过读取一个词法和语法描述文件，来生成一个java程序，这个java程序就包括了词法分析器和语法分析器。接着就可以用生成的词法分析器和语法分析器来对我们的输入进行判断，判断是否符合我们要求的语法规则JAVACC使用 - 知乎 (zhihu.com)所以总的来说JAVACC其实就是一个解释器的生成器，它的关键在于识别模板文件来生成分析程序。另外一个例子，为了支持后面会提到的灵活元数据功能，Calcite需要支持运行时编译java代码。默认的javaC太重，需要一个更轻量级的编译器，Calcite同样没有选择重复造轮子，而是使用了开源的Janino方案。足够简单和focus定位，不重复造轮子，使得Calcite的实现足够简单和稳定。当apache Spark、Flink、Presto为了加速数据处理速度，而使用代码生成技术时，都不约而同的选择了这个开源库JaninoJanino：轻巧的Java动态编译器 - 知乎 (zhihu.com)Jaino主要有三大功能Parser：java代码的解析器。可以解析完整的模块函数、或者是表达式等UnParser是Parser的逆操作。也就是把抽象语法树（AST）变为Java程序。Evaluator：最后，到了用的最多的Evaluator执行环节了。比如：为了加速表达式的执行，那我可以先使用Janino来编译表达式为JVM的bytecode，并在后续多次调用这个编译好的表达式来计算。架构灵活可插拔的架构上面的图是 Calcite 官方给出的架构图。一方面印证了我们上面提到的，Calcite 足够简单，没有做自己不该做的事；另一方面，也是更重要的，Calcite 被设计的足够模块化和可插拔。JDBC Driver 这个模块用来支持使用 JDBC client 的应用；SQL Parser and Validator 模块用来做 SQL 解析和校验；Expressions Builder 用来支持自己做 SQL 解析和校验的框架对接；Operator Expressions 这个模块用来处理关系表达式；Metadata Providers 用来支持外部自定义元数据；Pluggable Rules 用来定义优化规则；最核心的 Query Optimizer 则专注查询优化。功能模块的划分足够合理，足够独立，使得不用完整集成，而是可以只选择其中的一部分使用；而基本上每个模块都支持自定义，也使得用户能更多地定制系统。上图展示里 10多种框架对Calcite的集成情况。我们看一看到像Hive就做了SQL解析，只使用了Calcite的查询优化功能；而Flink则解析到优化…

sleep、synchronized、LockSupport的线程阻塞有何区别？
type: item
date: 2024-01-18T10:32:00+08:00
description: sleep、synchronized、LockSupport的线程阻塞有何区别？让面试官心服口服：Thread.sleep、synchronized、LockSupport.park的线程阻塞有何区别？ - tera - 博客园 (cnblogs.com)前言在日常编码的过程中，我们经常会使用Thread.sleep、LockSupport.park()主动阻塞线程，或者使用synchronized和Object.wait来阻塞线程保证并发安全。此时我们会发现，对于Thread.sleep和Object.wait方法是会抛出InterruptedException，而LockSupport.park()和synchronized则不会。而当我们调用Thread.interrupt方法时，除了synchronized，其他线程阻塞的方式都会被唤醒。于是本文就来探究一下Thread.sleep、LockSupport.park()、synchronized和Object.wait的线程阻塞的原理以及InterruptedException的本质本文主要分为以下几个部分1.Thread.sleep的原理2.LockSupport.park()的原理3.synchronized线程阻塞的原理4.ParkEvent和parker对象的原理5.Thread.interrupt的原理6.对于synchronized打断原理的扩展1.Thread.sleep的原理Thread.java首先还是从java入手，查看sleep方法，可以发现它直接就是一个native方法：public static native void sleep(long millis) throws InterruptedException;为了查看native方法的具体逻辑，我们就需要下载openjdk和hotspot的源码了，下载地址：http://hg.openjdk.java.net/jdk8查看Thread.c：jdk源码目录src/java.base/share/native/libjava可以看到对应的jvm方法是JVM_Sleep：static JNINativeMethod methods[] = { ... {"sleep", "(J)V", (void *)&JVM_Sleep}, ... };查看jvm.cpp，hotspot目录src/share/vm/prims找到JVM_Sleep方法，我们关注其重点逻辑：方法的逻辑中，首先会做2个校验，分别是睡眠时间和线程的打断标记。其实这2个数据的校验都是可以放到java层，不过jvm的设计者将其放到了jvm的逻辑中去判断。如果睡眠的时间为0，那么会调用系统级别的睡眠方法os::sleep()，睡眠时间为最小时间间隔。在睡眠之前会保存线程当前的状态，并将其设置为SLEEPING。在睡眠结束之后恢复线程状态。接着就是sleep方法的重点，如果睡眠时间不为0，同样需要保存和恢复线程的状态，并调用系统级别的睡眠方法os::sleep()。当然睡眠的时间会变成指定的毫秒数。最重要的区别是，此时会判断os::sleep()的返回值，如果是打断状态，那么就会抛出一个InterruptException！这里其实就是InterruptException产生的源头JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis)) JVMWrapper("JVM_Sleep"); //如果睡眠的时间小于0，则抛出异常。这里数据的校验在jvm层逻辑中校验 if (millis < 0) { THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative"); } //如果线程已经被打断了，那么也抛出异常 if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) { THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted"); } ... //这里允许睡眠时间为0 if (millis == 0) { ...{ //获取并保存线程的旧状态 ThreadState old_state = thread->osthread()->get_state(); //将线程的状态设置为SLEEPING thread->osthread()->set_state(SLEEPING); //…

Mybatis工厂模式
type: item
date: 2024-01-09T14:12:26+08:00
description: Mybatis工厂模式https://blog.csdn.net/u013412772/article/details/73648537工厂模式概念在Mybatis中⽐如SqlSessionFactory使⽤的是⼯⼚模式，该⼯⼚没有那么复杂的逻辑，是⼀个简单⼯⼚模式。简单工厂模式：该魔术不属于23种经典设计模式，是一种很常见且简单的创建对象的方式。又称为静态工厂方法模式。（Static Factory Methods）例如spring中创建对象可以传入class对象然后按照类型进行注入,这就是简单工厂模式.简单工厂模式的实质是由一个工厂类根据传入的参数，动态决定应该创建哪一个产品类（这些产品类继承自一个父类或接口）的实例。Mybatis的设计mybatis的持久化解决方案是将用户从原始的JDBC访问中解放出来，用户只需要定义需要操作的SQL语句,无须关注底层的JDBC操作,就可以以面向对象的方式来进行持久化层操作.底层数据库连接的获取,数据访问的实现,事务控制等都无须用户关心,从而将应用层从底层的JDBC/JTA API抽取出来.通过配置文件管理JDBC连接,让MyBatis解决持久化的实现.在MyBatis中的常见对象有SqlSessionFactory和SqlSession.本文这种介绍一下两者的概念和使用.SqlSessionFactorySqlSessionFactory是Mybatis的关键对象，它是单个数据映射关系经过编译后的内存镜像。SqlSessionFactory对象实例可以通过SqlSessionFactoryBuilder对象获得，而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例。每一个Mybatis的应用程序都以一个SqlSessionFacotry对象的实例为核心。同时SqlSessionFacotry也是线程安全的，SqlSessionFacotry一旦被创建，应该在应用执行期间都存在，在应用运行期间不需要多次创建，建议使用单例模式。SqlSessionFactory是创建SqlSession的工厂。//SqlSessionFactory接口源码如下所示: package org.apache.ibatis.session; import java.sql.Connection; public interface SqlSessionFactory { SqlSession openSession();//这个方法最经常用,用来创建SqlSession对象. SqlSession openSession(boolean autoCommit); SqlSession openSession(Connection connection); SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType); SqlSession openSession(ExecutorType execType, boolean autoCommit); SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }SqlSessionSqlSession是Mybatis关联对象，是持久化操作的独享，类似于JDBC的Connection。它是应用程序与持久层之间执行交互操作的一个单线程对象，也是Mybatis执行持久化操作的关键对象。SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法，它底层封装了JDBC连接，可以用SqlSession实例来直接执行被映射的SQL语句。每个线程都有它自己的SqlSession实例。SqlSession的实例不能被共享，同时SqlSession也是线程不安全的，决不能将SqlSession实例的引用放在一个类的静态字段甚至是实例字段中。也决不能将SqlSession实例的引用放在任何类型的管理范围中，比如Servlet当中的HttpSession对象中，使用完SqlSession之后关闭Session很重要。应该确保使用finally块来关闭它。//SqlSession接口源码如下所示: package org…

SOFA
type: item
date: 2024-01-09T09:55:00+08:00
description: SOFASOFA-ARKsofastack/sofa-ark: SOFAArk is a light-weight，java based classloader isolation framework. (github.com)背景SOFAArk 最初的场景是解决 Java 开发常常会遇到的包依赖冲突的问题，尤其当工程应用变得臃肿庞大，包冲突的问题也会变得更加棘手，导致各种各样的报错，例如LinkageError, NoSuchMethodError等。实际开发中，可以采用多种方法来解决包冲突问题，比较常见的是类似 SpringBoot 的做法，统一管理应用所有依赖包的版本，保证这些三方包不存在依赖冲突。这种做法只能有效避免包冲突的问题，不能根本上解决包冲突的问题。如果某个应用的确需要在运行时使用两个相互冲突的包，例如 protobuf2 和 protobuf3，那么类似 SpringBoot 的做法依然解决不了问题。为了彻底解决包冲突的问题，我们需要借助类隔离机制，使用不同的ClassLoader加载不同的第三方依赖，进而隔离包冲突的问题。OSGI作为业内最出名的类隔离框架，自然是可以被用于解决上述包冲突的问题，但是OSGI框架太过臃肿，功能繁杂。为了解决包冲突问题，引入OSGI框架有杀鸡用牛刀之嫌，反而使工程变得更加复杂，不利于开发。SOFAArk 则采用较为轻量级的类隔离方案来解决日常经常遇到的包冲突问题，在蚂蚁金服内部服务于整个 SOFABoot 技术体系，弥补 SpringBoot 没有的类隔离能力。实际上，SOFAArk 是一个通用的轻量级类隔离框架，并不限于 SpringBoot 应用，也可以和其他的 Java 开发框架集成。原理SOFAArk 框架包含有三个概念，Ark Container, Ark Plugin 和 Ark Biz; 运行时逻辑结构图如下：在介绍这三个概念之前，为了统一术语，有必要先说一下所谓的Ark包：Ark包是满足特定目录格式要求的Executed Fat Jar，使用官方提供的 Maven 插件 sofa-ark-maven-plugin可以将工程应用打包成一个标准格式的 Ark 包；使用命令 java -jar application.jar即可在 Ark 容器之上启动应用；Ark 包 通常包含 Ark Container、Ark Plugin、 Ark Biz；以下我们针对这三个概念简单做下名词解释：Ark Container: Ark 容器，负责整个运行时的管理；Ark Plugin 和 Ark Biz 运行在 Ark 容器之上；容器具备管理多插件、多应用的功能；容器启动成功后，会自动解析 classpath 包含的 Ark Plugin 和 Ark Biz 依赖，完成隔离加载并按优先级依次启动之；Ark Plugin: Ark 插件，满足特定目录格式要求的 Fat Jar，使用官方提供的 Maven 插件 sofa-ark-plugin-maven-plugin 可以将一个或多个普通的 Java Jar 包打包成一个标准格式的 Ark Plugin； Ark Plugin 会包含一份配置文件，通常包括插件类导入导出配置、插件启动优先级等；运行时，Ark 容器会使用独立的 PluginClassLoader 加载插件，并根据插件配置构建类加载索引表，从而使插件与插件、插件与应用之间相互隔离；Ark Biz: Ark 业务模块，满足特定目录格式要求的 Fat Jar ，使用官方提供的 Maven 插件 sofa-ark-maven-plugin 可以将工程应用打包成一个标准格式的 Ark-Biz 包；是工程应用模块及其依赖包的组织单元，包含应用启动所需的所有依赖和配置；在运行时，Ark Container 优先启动，自动解析 classpath 包含的 Ark Plugin 和 Ark Biz，并读取他们的配置，构建类加载索引关系；然后使用独立的 ClassLoader 加载他们并按优先级配置依次启动；需要指出的是，Ark Plugin 优先 Ark Biz 被加载启动；Ark Plugin 之间是双向类索引关系，即可以相互委托对方加载所需的类；Ark Plugin 和 Ark Biz 是单向类索引关系，即只允许 Ark Biz 索引 Ark Plugin 加载的类，反之则不允许。场景包冲突SOFAArk初衷是为了解决包冲突问题，那什么情况下可以使用 SOFAArk 以及如何使用呢？ 假设如下场景，如果工程需要引入两个三方包：A 和 B，但是 A 需要依赖版本号为 0.1 的 C 包，而恰好 B 需要依赖版本号为 0.2 的 C 包，且 C 包的这两个版本无法兼容:此时，即可使用 S…

SpringMVC父子容器
type: item
date: 2023-12-29T14:45:00+08:00
description: SpringMVC父子容器spring与springmvc父子容器_Spring教程_田守枝Java技术博客 (tianshouzhi.com)父子容器是什么父子容器并非SpringMVC的专利，在普通Spring环境下，Spring就已经设计出具有层次结构的容器了，这种设计方式也并非Spring独创，起工作方式和ClassLoader很相似，每个容器有一个自己容器的父容器，但是与ClassLoader不同的是，通过容器查找bean时是优先从子容器里查找，如果找不到才会从父容器里查找。当应用中存在多个容器时，这种设计方式可以将公共的bean放到父容器中，如果父容器中的bean不适用，之日起还可以覆盖父容器的bean。Spring的容器有俩种，一种是常用的ApplicationContext，父子容器相关的层次结构如下。BeanFactory最终的视线是DefaultListableBeanFactory，其查找bean的部分源码如下，从中可以看出Spring是优先从子容器中查找bean的，如果查找不到会再次从父类容器中找。案例系统中有2个模块：module1和module2，两个模块是独立开发的，module2会使用到module1中的一些类，module1会将自己打包为jar提供给module2使用，我们来看一下这2个模块的代码。放在module1包中，有3个类Service1package com.javacode2018.lesson002.demo17.module1; import org.springframework.stereotype.Component; @Component public class Service1 { public String m1() { return "我是module1中的Servce1中的m1方法"; } } service2package com.javacode2018.lesson002.demo17.module1; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class Service2 { @Autowired private com.javacode2018.lesson002.demo17.module1.Service1 service1; //@1 public String m1() { //@2 return this.service1.m1(); } } 上面2个类，都标注了@Compontent注解，会被spring注册到容器中。@1：Service2中需要用到Service1，标注了@Autowired注解，会通过spring容器注入进来@2：Service2中有个m1方法，内部会调用service的m1方法。再来看模块2放在module2包中，也是有3个类，和模块1中的有点类似。Service1模块2中也定义了一个Service1，内部提供了一个m2方法，如下：package com.javacode2018.lesson002.demo17.module2; import org.springframework.stereotype.Component; @Component public class Service1 { public String m2() { return "我是module2中的Servce1中的m2方法"; } } Service3package com.javacode2018.lesson002.demo17.module2; import com.javacode2018.lesson002.demo17.module1.Service2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class Service3 { //使用模块2中的Service1 @Autowired private com.javacode2018.lesson002.demo17.module2.Service1 service1; //@1 //使用模块1中的Service2 @Autowired private com.javacode2018.lesson002.demo17.module1.Servic…

java9模块化
type: item
date: 2023-12-19T11:39:00+08:00
description: java9模块化定义java9模块模块是代码、数据和资源的集合。它是一组相关包和类型（类、抽象类、接口等），包含代码、数据文件和一些静态资源。例如，模块描述符module-info.java是Java9模块中的资源之一。每个模块质保函一组相关代码和数据，以支持单一责任原则（SRP）：一个类改变的原因不应超过一个。简单的说法是模块=代码+数据基本模块和可靠配置目前java9有98个模块，但它仍在继续发展。Oracle将jdk jar和java se规范分为俩组模块。所有JDK和用户定义模块的默认模块都是基本模块java.base文件。它是一个独立的模块，不依赖于任何其他模块。java.base文件被称为java9模块之母该图是一个依赖关系图。每个框表示一个模块，如果一个模块有一个指向另一个模块的箭头，则表示该箭头所指向的模块需要它所指向的模块来执行其功能。正如你在图片中看到的， java.base 文件是其他模块所依赖的模块。这是因为它包含Java对象、整数、字符串等基本类。没有这些类，代码就不能工作。我可以使依赖关系更加明确：java8时的模块rt.jarrt.jar代表runtime JAR，并且包含在引导类（bootstrap classes0）来自Core API Java的所有类。Windows和Linux中，rt.jar位于JRE的lib目录下。JDK1.7之前MacOSX中叫classes.jar。从java7开始也该位rt.jar。很多开发者尝试把自己的类放进rt.jar中，但最好不要这样做，因为rt.jar包含了JVM新人的class文件，jvm加载时不会对其他class文件进行严格的安全检查。rt.jar代表runtime，包含所有核心java运行环境的已编译class文件必须在类路径中包含rt.jar，否则您无权访问核心类，例如 java.lang.String，java.lang.Thread，java.util.ArrayList或java.io.InputStream等，以及所有Java API中的其他类。 可以使用IDE打开并查看rt.jar中的内容， 它不仅包含所有Java API，还包含com包中指定的内部类。在windows中，rt.jar位于$ JAVA_HOME / jre / lib下。 即使没有安装JDK并且只安装JRE，也会在完全相同的位置看到它，在$ JAVA_HOME / lib目录中找不到rt.jar。 顺便提一句，在MacOSX上，它被称为classes.jarand，位于/ System / Library / Frameworks // Classes目录下。关于rt.jar一个很重要的事情就是，JVM知道这个jar文件中的所有类，这意味着JVM在任何位置加载都不会执行检查。之所以这样做是由于各种性能原因，这就是为什么这些class由bootstrap或primodial类加载器加载的原因。所以最好不要尝试将自己类文件包含在rt.jar中，java官方也不建议这样做。核心模块需要知道，核心jar包通常包含了Java平台的基础类库，如java.lang、java.util、java.io等等。这些类库提供了Java程序运行所需要的核心功能。非核心jar包则是指那些不是Java应用程序或框架必需的 jar包，这些jar包提供了一些额外的功能和工具类库，比如数据库驱动、日志管理、ORM框架等等。它们通常是可选的，可以根据需要引入。在Java应用程序或框架中，通常会使用一些第三方依赖库，这些依赖库会以jar包的形式提供。这些jar包可以是核心的，也可以是非核心的。在应用程序或框架的构建和部署过程中，需要将这些jar包一并打包，以便于程序的使用和运行。在安装java包时，会遇到两次路径选择，第一次时选择jdk的路径，第二次是选择JRE的路径，如果所选择的jdk安装路径和jre的安装路径相同，那么jre包中的内容会覆盖掉jdk中的内容，因此，在你安装完成之后，会发现找不到tools.jar和dt.jar包模块化简介什么是模块系统，官方的定义是A uniquely named, reusable group of related packages, as well as resources (such as images and XML files) and a module descriptor.（一组唯一命名、可重复使用的相关包，以及资源（如图像和XML文件）和模块描述符。）如图模块的载体是jar文件，一个模块就是一个jar文件，但相比于传统的jar文件，模块的根目录下多了一个module-info.class文件，也即（module descriptor）包含以下信息模块名称依赖…

Log4j，Slf4j，Logback之间的区别
type: item
date: 2023-12-14T10:40:48+08:00
description: Log4j，Slf4j，Logback之间的区别不同日志框架大多数情况下，不管是开源框架或是我们平时工作编码都离不开一种框架，它就是日志框架。Commons Longingcommon-logging是apache提供的一个通用的日志接口，在common-loging中，有一个simple logger的简单实现，但是它功能很弱，所以使用common-logging，通常是配合着log4j来使用的Commons Logging 定义了一个自己的接口 org.apache.commons.logging.Log，以屏蔽不同日志框架的 API 差异，这里用到了 Adapter Pattern（适配器模式）。SLF4J使用过Log4J和LogBack的同学肯定能发现，这两个框架的设计理念极为相似，使用方法也如出一辙。其实这个两个框架的作者都是一个人，Ceki Gülcü，俄罗斯程序员。Log4J 最初是基于Java开发的日志框架，发展一段时间后，作者Ceki Gülcü将Log4j捐献给了Apache软件基金会，使之成为了Apache日志服务的一个子项目。 又由于Log4J出色的表现，后续又被孵化出了支持C, C++, C#, Perl, Python, Ruby等语言的子框架。然而，伟大的程序员好像都比较有个性。Ceki Gülcü由于不满Apache对Log4J的管理，决定不再参加Log4J的开发维护。“出走”后的Ceki Gülcü另起炉灶，开发出了LogBack这个框架（SLF4J是和LogBack一起开发出来的）。LogBack改进了很多Log4J的缺点，在性能上有了很大的提升，同时使用方式几乎和Log4J一样，许多用户开始慢慢开始使用LogBack。由于受到LogBack的冲击，Log4J开始式微。终于，2015年9月，Apache软件基金业宣布，Log4j不在维护，建议所有相关项目升级到Log4j2。Log4J2是Apache开发的一个新的日志框架，改进了很多Log4J的缺点，同时也借鉴了LogBack，号称在性能上也是完胜LogBack。性能这块后面我会仔细分析。Simple Logging facade for java（SLF4J）作用域各种日志框架（例如java.util。Logging，logback，log4j）的简单外观或抽象，允许最终用户在部署时插入所需的日志框架。要切换日志框架，只需替换类路径上的sl4j绑定。例如，要从java.util.logging切换到log4j，只需将 slf4j-jdk14-1.8.0-beta2.jar 替换为 slf4j-log4j12-1.8.0-beta2.jar。SLF4J 不依赖于任何特殊的类装载机制。 实际上，每个 SLF4J 绑定在编译时都是硬连线的，以使用一个且只有一个特定的日志记录框架。 例如，slf4j-log4j12-1.8.0-beta2.jar 绑定在编译时绑定以使用 log4j。 在您的代码中，除了slf4j-api-1.8.0-beta2.jar 之外，您只需将您选择的一个且只有一个绑定放到相应的类路径位置。 注意不要在类路径上放置多个绑定。因此，slf4j 就是众多日志接口的集合，他不负责具体的日志实现，只在编译时负责寻找合适的日志系统进行绑定。具体有哪些接口，全部都定义在 slf4j-api 中。查看 slf4j-api 源码就可以发现，里面除了 publicfinalclassLoggerFactory 类之外，都是接口定义。因此，slf4j-api 本质就是一个接口定义。总之，Slf4j 更好的兼容了各种具体日志实现的框架，如图：Log4JApache Log4J是一个非常古老的日志框架，并且是多年来最受欢迎的日志框架。它引入了现代日志框架仍在使用的基本概念，如分层日志级别和记录器。Log4j2Apache Log4j2 是对Log4j的升级，它比千盛Log4j 1.x提供了重大改进 ，并且提供了Logback中可用的许多改进，同时修复了一些Logback架构中固有的问题。与Logback一样，Log4j2提供对SLF4j的支持，自动重新加载日志配置，并支持高级过滤选项。除了这些功能外，它还允许基于lambda表达式对日志语句进行延迟评估，为低延迟系统提供异步记录器，并提供无垃圾模式以避免垃圾收集器操作引起的任何延迟。所有这些功能使Log4j2称为这三个日志框架中最快的。Logbacklogback 是由 log4j 创始人设计的又一个开源日志组件，作为流行的 log4j 项目的后续版本，从而替代 log4j。Logback 的体系结构足够通用，以便在不同情况下应用。 目前，logback 分为三个模块：logback-core，lo…