Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

Java 线程池是 Java 并发体系里的重要组成部分,不过说起线程池一般都是几个参数,以及参数的含义和逻辑,为了更适合刚开始学习的同学或者说有一些会对这么个习惯性的概括讲法有点疑惑的,我们就从零开始,从最基础的开始看起,先来看下里面用来判断线程池状态和线程数量的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;

// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

这里我们先看下 COUNT_BITS 它是 Integer.SIZE - 3Integer.SIZE 这个是 32,也就是代表了 Java 用了四个 Byte 来存一个 Integer, 那么 COUNT_BITS 就是 29,
这个 29 是用了干嘛的呢,逐步往下看,为什么先介绍 COUNT_BITS,因为第一行的 ctl 的值是通过 ctlOf(RUNNING, 0) 算出来的,而 RUNNING 又是通过等于 -1 左移 COUNT_BITS 位来得到,移动 29 位后,还剩下三位,而这三位只需要表示后面五种状态,其中只有 RUNNING 是负值,这样就巧妙的只利用了 ctl 的前三位来存储线程池的状态而后面的 29 位就是用来存储线程数量的,具体怎么取呢,我们在来看下 CAPACITY 它是 (1 << COUNT_BITS) - 1 把 1 左移 29 位后减 1,也就是 536870911,这里再补充下,左移一位就等于是乘以 2,左移 29 位就是乘以 2 的 29 次,减了 1 就是 29 位能存的最大值,代表线程池最大的线程容量,用二进制来表示就是 32 位长度的int,前三位是 0,后 29 位是 1,代表了线程池的容量,那么 runStateOfworkerCountOf 的理解就很简单了,
runStateOf 是 c 跟 CAPACITY 取反之后的值做”与”操作,CAPACITY 取反就变成了前三位是 1,后面的二十九位是 0,而对于”与”操作,后面的二十九位无论是什么都会被忽略,也就是取了前三位的值
workerCountOf 正好相反,直接跟 CAPACITY 做 “与” 操作,也就是取后二十九位的值
ctlOf 就是直接对 rswc 做了 “或” 操作
而前面也提到了只有 RUNNING 是负数,那么就可以直接做大小比较来判断线程池状态
这个可能对很多同学来说是非常简单的,但是对于一部分同学来说就是个拦路虎,希望能对这部分同学有所帮助。

题目介绍

Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise.

A subtree of a binary tree tree is a tree that consists of a node in tree and all of this node’s descendants. The tree tree could also be considered as a subtree of itself.

示例 1

Input: root = [3,4,5,1,2], subRoot = [4,1,2]
Output: true

示例 2

Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
Output: false

解析

需要判断subRoot 为根节点的这棵树是不是 root 为根节点的这个树的子树,题应该不是特别难理解,只是需要注意,这里是子树,而不是一个子结构,也就是示例 2 的判断结果是 false,因为左边子树还有个 0,只能认为是左边的一部分,但不是子树,思路分析也不难,就是递归去判断,只不过是两种维度,第一种就是找到子树的根节点,也就是在 root 树找到 subRoot 的根节点相同的节点,这个就会用到递归,如果找到了一个,就开始递归的对比所有子节点,必须完全一样

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (subRoot == null || root == null) {
return false;
}
if (root.val == subRoot.val) {
// 如果 root 节点跟 subRoot 节点相同,就开始递归对比所有的子节点
if (isSameTree(root.left, subRoot.left) && isSameTree(root.right, subRoot.right)) {
return true;
}
}
// 如果 root 节点不同,就开始递归往下找与 subRoot 相同的起始节点,这里是或的关系,因为在左右子树任一找到一个就行
return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
// 需要完全相同,也就是说不能像示例 2 那样多一个子节点这种,以及其他不同的情况,反正就是要完全一样
public boolean isSameTree(TreeNode root, TreeNode subTree) {
// 如果递归到这个节点都为空也认为是相同的
if (root == null && subTree == null) {
return true;
}
// 因为有前面的判断,这里就隐含了 subTree != null 这个条件
if (root == null) {
return false;
}
// 也因为有前面的判断,这里就隐含了 root != null 这个条件
if (subTree == null) {
return false;
}
// 两个都不为空,但是值不相同,也是 false
if (root.val != subTree.val) {
return false;
}
// 跟节点相同,就开始递归判断左右子树,必须两者都相同
return isSameTree(root.left, subTree.left) && isSameTree(root.right, subTree.right);
}

上一次主要是给了一个解题方案,没有具体讲解,这次又做到了就来看下几种方案,链表转置一直是我比较疑惑的问题,特别是边界处理,而这个问题要把难度加大了
我先讲一下我一开始的思路和解题方法,首先就是写一个转置方法,就处理 k 个一组的内部转置,然后外部循环处理分组以及前后连接等问题,但是这里就涉及到一个问题,就是前后连接的话对于整个链表头,也就是第一个 k 元素组来说,头是个空的,就需要额外处理,一开始就是用判空做额外处理,然后在前后连接的时候也有一些困扰,看了自己的代码库发现其实之前也做过,而且发现这个思路跟以前做的还是一样的,只不过在处理 k 个元素内部的转置的时候有了点差异

一开始的思路是这样的,想了下其实还是比较有问题,从处理难度上来说,在 k 组内处理的时候一开始 A 节点会是悬空的,然后得处理到组内最后一个元素的时候再接上,逻辑会更复杂,而另一种思路就是直接把 A 先挪到 C 后面,这样每次只需要移动一个,可能思路上会更清晰一点

也就是这样的,这种方案就是之前发过的题解代码,上一篇

而最后这种则代码会简单一些,但是需要一定的理解成本,比较重要的一点是我们加了个虚拟的头结点,第一步会先获取下链表的总长度,然后在内循环里处理转置,重点就是分四步,
也就是图里的,第一步先把 cur 的下一个节点设置成 next 的 next 节点,这里就是图里 A 连接到 C,第二步是把 next 的 next 节点设置成虚拟头的 next节点,第三步是把 pre 的next 节点设置成 next,第四步是 next 往后移动

1
2
3
4
cur.next = next.next;
next.next = pre.next;
pre.next = next;
next = cur.next;


在备注下代码

上周因为一些事情没有更新在这里,是因为新电脑还没到,手头没有把移动硬盘里的 time machine 恢复出来的机器,所以单独更了一篇在新建的一个 cloudflare page 服务上,总体体验还可以,就是有个小点后面可以讲一下,继续完善下 Java 的这个日志,或者说主要讲的是 logback
前面讲了logback 的初始化逻辑,后面就是我最开始来看这个的逻辑,日志的滚动策略的触发逻辑
我们常规的记录日志的方式比如

1
logger.info("log some thing and biz info: {}",biz);

这里就调用了
ch.qos.logback.classic.Logger#info(java.lang.String, java.lang.Object)

1
2
3
public void info(String format, Object arg) {
filterAndLog_1(FQCN, null, Level.INFO, format, arg, null);
}

这里的 FQCN 就是这个类的全限定名

1
public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();

这个具体的 filterAndLog_1 方法还是在同一个 Logger 类里的
ch.qos.logback.classic.Logger#filterAndLog_1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void filterAndLog_1(final String localFQCN, final Marker marker, final Level level, final String msg, final Object param, final Throwable t) {

final FilterReply decision = loggerContext.getTurboFilterChainDecision_1(marker, this, level, msg, param, t);

if (decision == FilterReply.NEUTRAL) {
if (effectiveLevelInt > level.levelInt) {
return;
}
} else if (decision == FilterReply.DENY) {
return;
}

buildLoggingEventAndAppend(localFQCN, marker, level, msg, new Object[] { param }, t);
}

这里面先是走过滤器逻辑,如果是拒绝的,那就不往下执行,也就不执行下面的写日志逻辑了,如果是中性的,那就判断日志级别是否符合,如果直接是拒绝就 return 了,如果是 ACCEPT 就往下执行了

1
2
3
public enum FilterReply {
DENY, NEUTRAL, ACCEPT;
}

然后继续调用同样的类的方法
ch.qos.logback.classic.Logger#buildLoggingEventAndAppend

1
2
3
4
5
6
private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
final Throwable t) {
LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
le.setMarker(marker);
callAppenders(le);
}

先把日志包装成 LoggingEvent ,然后调用
ch.qos.logback.classic.Logger#callAppenders

1
2
3
4
5
6
7
8
9
10
11
12
13
public void callAppenders(ILoggingEvent event) {
int writes = 0;
for (Logger l = this; l != null; l = l.parent) {
writes += l.appendLoopOnAppenders(event);
if (!l.additive) {
break;
}
}
// No appenders in hierarchy
if (writes == 0) {
loggerContext.noAppenderDefinedWarning(this);
}
}

这里会按 Logger 调用 appendLoopOnAppenders

1
2
3
4
5
6
private int appendLoopOnAppenders(ILoggingEvent event) {
if (aai != null) {
return aai.appendLoopOnAppenders(event);
} else {
return 0;
}

继续调用事件的
ch.qos.logback.core.spi.AppenderAttachableImpl#appendLoopOnAppenders

1
2
3
4
5
6
7
8
9
10
public int appendLoopOnAppenders(E e) {
int size = 0;
final Appender<E>[] appenderArray = appenderList.asTypedArray();
final int len = appenderArray.length;
for (int i = 0; i < len; i++) {
appenderArray[i].doAppend(e);
size++;
}
return size;
}

因为 Logger 里有关联 appender,就具体调用关联的 appender 来添加日志
然后就接着调用
ch.qos.logback.core.UnsynchronizedAppenderBase#doAppend 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void doAppend(E eventObject) {
// WARNING: The guard check MUST be the first statement in the
// doAppend() method.

// prevent re-entry.
if (Boolean.TRUE.equals(guard.get())) {
return;
}

try {
guard.set(Boolean.TRUE);

if (!this.started) {
if (statusRepeatCount++ < ALLOWED_REPEATS) {
addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
}
return;
}

if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
return;
}

// ok, we now invoke derived class' implementation of append
this.append(eventObject);

} catch (Exception e) {
if (exceptionCount++ < ALLOWED_REPEATS) {
addError("Appender [" + name + "] failed to append.", e);
}
} finally {
guard.set(Boolean.FALSE);
}
}

这里先判断了 guard 的状态,guard 是个 ThreadLocal,是为了拦截异常的无限循环调用,这里有些疑惑的,看了一圈可能相关的是前面的 Logger 会往父级查找,然后如果不同层级的 Logger 关联了相同的 appender 的话,或者这个 doAppend 又被其他 appender 调用了,就可能会出现循环调用,然后会在开始实际的 append的之前先设置 guard 状态,
然后再执行 append,这里会走到
ch.qos.logback.core.OutputStreamAppender#append

1
2
3
4
5
6
7
8
@Override
protected void append(E eventObject) {
if (!isStarted()) {
return;
}

subAppend(eventObject);
}

忘了补充下,我们这里使用的是
ch.qos.logback.core.rolling.RollingFileAppender
接下去就是调用的 RollingFileAppendersubAppend 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void subAppend(E event) {
// The roll-over check must precede actual writing. This is the
// only correct behavior for time driven triggers.

// We need to synchronize on triggeringPolicy so that only one rollover
// occurs at a time
synchronized (triggeringPolicy) {
if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) {
rollover();
}
}

super.subAppend(event);
}

这里就会先判断是否触发事件,
而这里就是主要的逻辑,看是否需要走 rollover,也就是具体的滚动逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Override
public boolean isTriggeringEvent(File activeFile, final E event) {

long time = getCurrentTime();

// first check for roll-over based on time
if (time >= nextCheck) {
Date dateInElapsedPeriod = dateInCurrentPeriod;
elapsedPeriodsFileName = tbrp.fileNamePatternWithoutCompSuffix.convertMultipleArguments(dateInElapsedPeriod, currentPeriodsCounter);
currentPeriodsCounter = 0;
setDateInCurrentPeriod(time);
computeNextCheck();
return true;
}

// next check for roll-over based on size
if (invocationGate.isTooSoon(time)) {
return false;
}

if (activeFile == null) {
addWarn("activeFile == null");
return false;
}
if (maxFileSize == null) {
addWarn("maxFileSize = null");
return false;
}
if (activeFile.length() >= maxFileSize.getSize()) {

elapsedPeriodsFileName = tbrp.fileNamePatternWithoutCompSuffix.convertMultipleArguments(dateInCurrentPeriod, currentPeriodsCounter);
currentPeriodsCounter++;
return true;
}

return false;
}

这里主要就用到了 nextCheck 判断,如果通过判断就可以执行滚动了,然后就是后面的判断,是否太快了,也就是 isTooSoon 然后再看当前的活跃日志以及最大文件大小是否设置,然后如果当前的活跃日志的大小超过了配置也会触发滚动

log 初始化过程,首先是在启动类里会获取 logger

1
private static final Log logger = LogFactory.getLog(SpringApplication.class);

然后是

1
2
3
public static Log getLog(Class clazz) throws LogConfigurationException {
return getFactory().getInstance(clazz);
}

继续下去是
org.apache.commons.logging.impl.SLF4JLogFactory#getInstance(java.lang.Class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Log getInstance(Class clazz) throws LogConfigurationException {
return this.getInstance(clazz.getName());
}
public Log getInstance(String name) throws LogConfigurationException {
Log instance = (Log)this.loggerMap.get(name);
if (instance != null) {
return instance;
} else {
Logger slf4jLogger = LoggerFactory.getLogger(name);
Object newInstance;
if (slf4jLogger instanceof LocationAwareLogger) {
newInstance = new SLF4JLocationAwareLog((LocationAwareLogger)slf4jLogger);
} else {
newInstance = new SLF4JLog(slf4jLogger);
}

Log oldInstance = (Log)this.loggerMap.putIfAbsent(name, newInstance);
return (Log)(oldInstance == null ? newInstance : oldInstance);
}
}

然后再往下处理就是 LoggerFactory.getLogger(name); 了,跟普通的获取 logger 一样

1
2
3
4
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}

继续下去会先判断是否已初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}

第一次调用的话就会走到 org.slf4j.LoggerFactory#performInitialization 里面是先调用绑定,这也是 slf4j 门面模式的特点

1
2
3
4
5
6
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}

非安卓环境会进入第一个 if 逻辑,先要去找static_logger_binder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}

上面的 STATIC_LOGGER_BINDER_PATH
就是 org/slf4j/impl/StaticLoggerBinder.class 如果找不到在外层方法就会抛出 NoSuchMethodError 错误,然后就是通过 StaticLoggerBinder.getSingleton(); 获取 StaticLoggerBinder 实例了

1
2
3
4
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

比较重要的是这里的 static 代码块还有一个逻辑,就是下面这个初始化逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static {
SINGLETON.init();
}
void init() {
try {
try {
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Exception t) { // see LOGBACK-1159
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}

上面的 defaultLoggerContext 也是通过静态代码块处理的

1
private LoggerContext defaultLoggerContext = new LoggerContext();

然后是调用 ch.qos.logback.classic.util.ContextInitializer#autoConfig 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void autoConfig() throws JoranException {
StatusListenerConfigHelper.installIfAsked(loggerContext);
URL url = findURLOfDefaultConfigurationFile(true);
if (url != null) {
configureByResource(url);
} else {
Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
if (c != null) {
try {
c.setContext(loggerContext);
c.configure(loggerContext);
} catch (Exception e) {
throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
.getCanonicalName() : "null"), e);
}
} else {
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}

前面会先查找配置文件路径
URL url = findURLOfDefaultConfigurationFile(true); 还是调用这个类内的 ch.qos.logback.classic.util.ContextInitializer#findURLOfDefaultConfigurationFile 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}

url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}

url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}

return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}

才发现原来这里也是硬编码了文件名

1
2
3
4
5
6
7
public class ContextInitializer {

final public static String GROOVY_AUTOCONFIG_FILE = "logback.groovy";
final public static String AUTOCONFIG_FILE = "logback.xml";
final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
final public static String CONFIG_FILE_PROPERTY = "logback.configurationFile";

如果不为空就调用了
ch.qos.logback.classic.util.ContextInitializer#configureByResource 这边是根据后缀判断处理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void configureByResource(URL url) throws JoranException {
if (url == null) {
throw new IllegalArgumentException("URL argument cannot be null");
}
final String urlString = url.toString();
if (urlString.endsWith("groovy")) {
if (EnvUtil.isGroovyAvailable()) {
// avoid directly referring to GafferConfigurator so as to avoid
// loading groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
} else {
StatusManager sm = loggerContext.getStatusManager();
sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.", loggerContext));
}
} else if (urlString.endsWith("xml")) {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(loggerContext);
configurator.doConfigure(url);
} else {
throw new LogbackException("Unexpected filename extension of file [" + url.toString() + "]. Should be either .groovy or .xml");
}
}

因为我们找到的是 logback.xml,所以走的是
ch.qos.logback.classic.joran.JoranConfigurator
然后调用了
ch.qos.logback.core.joran.GenericConfigurator#doConfigure(java.net.URL)
这个 GenericConfiguratorJoranConfigurator 的父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final void doConfigure(URL url) throws JoranException {
InputStream in = null;
try {
informContextOfURLUsedForConfiguration(getContext(), url);
URLConnection urlConnection = url.openConnection();
// per http://jira.qos.ch/browse/LBCORE-105
// per http://jira.qos.ch/browse/LBCORE-127
urlConnection.setUseCaches(false);

in = urlConnection.getInputStream();
doConfigure(in, url.toExternalForm());
} catch (IOException ioe) {
String errMsg = "Could not open URL [" + url + "].";
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ioe) {
String errMsg = "Could not close input stream";
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
}
}
}
}

然后继续调用
ch.qos.logback.core.joran.GenericConfigurator#doConfigure(java.io.InputStream, java.lang.String) 处理具体的内容,前面处理了内容获取,因为如果是走的 url 文件就需要从网络获取文件流,

1
2
3
4
5
public final void doConfigure(InputStream inputStream, String systemId) throws JoranException {
InputSource inputSource = new InputSource(inputStream);
inputSource.setSystemId(systemId);
doConfigure(inputSource);
}

而实际的处理 xml 内容则是在
ch.qos.logback.core.joran.GenericConfigurator#doConfigure(org.xml.sax.InputSource)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final void doConfigure(final InputSource inputSource) throws JoranException {

long threshold = System.currentTimeMillis();
// if (!ConfigurationWatchListUtil.wasConfigurationWatchListReset(context)) {
// informContextOfURLUsedForConfiguration(getContext(), null);
// }
SaxEventRecorder recorder = new SaxEventRecorder(context);
recorder.recordEvents(inputSource);
doConfigure(recorder.saxEventList);
// no exceptions a this level
StatusUtil statusUtil = new StatusUtil(context);
if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
addInfo("Registering current configuration as safe fallback point");
registerSafeConfiguration(recorder.saxEventList);
}
}

需要解析 xml 了,先是调用 buildInterpreter 构建了 Interpreter

1
2
3
4
5
6
7
public void doConfigure(final List<SaxEvent> eventList) throws JoranException {
buildInterpreter();
// disallow simultaneous configurations of the same context
synchronized (context.getConfigurationLock()) {
interpreter.getEventPlayer().play(eventList);
}
}

build 里还有一些逻辑,就是匹配后面要处理的规则集,就是要确认那些标签要处理

1
2
3
4
5
6
7
8
9
protected void buildInterpreter() {
RuleStore rs = new SimpleRuleStore(context);
addInstanceRules(rs);
this.interpreter = new Interpreter(context, rs, initialElementPath());
InterpretationContext interpretationContext = interpreter.getInterpretationContext();
interpretationContext.setContext(context);
addImplicitRules(interpreter);
addDefaultNestedComponentRegistryRules(interpretationContext.getDefaultNestedComponentRegistry());
}

第一步就是先 new 一个 ch.qos.logback.core.joran.spi.SimpleRuleStore 然后调用了 ch.qos.logback.classic.joran.JoranConfigurator#addInstanceRules 来添加规则,就是下面的,可以看到对于 logger,appender 这些的配置,其中前面点的父类也又一些规则 ,ch.qos.logback.core.joran.JoranConfiguratorBase 逻辑类似就不展开了,这里的规则就对应了后面的各种 Action,就是对应的处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public void addInstanceRules(RuleStore rs) {
// parent rules already added
super.addInstanceRules(rs);

rs.addRule(new ElementSelector("configuration"), new ConfigurationAction());

rs.addRule(new ElementSelector("configuration/contextName"), new ContextNameAction());
rs.addRule(new ElementSelector("configuration/contextListener"), new LoggerContextListenerAction());
rs.addRule(new ElementSelector("configuration/insertFromJNDI"), new InsertFromJNDIAction());
rs.addRule(new ElementSelector("configuration/evaluator"), new EvaluatorAction());

rs.addRule(new ElementSelector("configuration/appender/sift"), new SiftAction());
rs.addRule(new ElementSelector("configuration/appender/sift/*"), new NOPAction());

rs.addRule(new ElementSelector("configuration/logger"), new LoggerAction());
rs.addRule(new ElementSelector("configuration/logger/level"), new LevelAction());

rs.addRule(new ElementSelector("configuration/root"), new RootLoggerAction());
rs.addRule(new ElementSelector("configuration/root/level"), new LevelAction());
rs.addRule(new ElementSelector("configuration/logger/appender-ref"), new AppenderRefAction<ILoggingEvent>());
rs.addRule(new ElementSelector("configuration/root/appender-ref"), new AppenderRefAction<ILoggingEvent>());

// add if-then-else support
rs.addRule(new ElementSelector("*/if"), new IfAction());
rs.addRule(new ElementSelector("*/if/then"), new ThenAction());
rs.addRule(new ElementSelector("*/if/then/*"), new NOPAction());
rs.addRule(new ElementSelector("*/if/else"), new ElseAction());
rs.addRule(new ElementSelector("*/if/else/*"), new NOPAction());

// add jmxConfigurator only if we have JMX available.
// If running under JDK 1.4 (retrotranslateed logback) then we
// might not have JMX.
if (PlatformInfo.hasJMXObjectName()) {
rs.addRule(new ElementSelector("configuration/jmxConfigurator"), new JMXConfiguratorAction());
}
rs.addRule(new ElementSelector("configuration/include"), new IncludeAction());

rs.addRule(new ElementSelector("configuration/consolePlugin"), new ConsolePluginAction());

rs.addRule(new ElementSelector("configuration/receiver"), new ReceiverAction());

}

然后调用了 interpreter 的 eventPlayer 处理 xml 的各个标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void play(List<SaxEvent> aSaxEventList) {
eventList = aSaxEventList;
SaxEvent se;
for (currentIndex = 0; currentIndex < eventList.size(); currentIndex++) {
se = eventList.get(currentIndex);

if (se instanceof StartEvent) {
interpreter.startElement((StartEvent) se);
// invoke fireInPlay after startElement processing
interpreter.getInterpretationContext().fireInPlay(se);
}
if (se instanceof BodyEvent) {
// invoke fireInPlay before characters processing
interpreter.getInterpretationContext().fireInPlay(se);
interpreter.characters((BodyEvent) se);
}
if (se instanceof EndEvent) {
// invoke fireInPlay before endElement processing
interpreter.getInterpretationContext().fireInPlay(se);
interpreter.endElement((EndEvent) se);
}

}
}

接下去就在 startElement 中调用会先获取 getApplicableActionList 适配的 Action, callBeginAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {

String tagName = getTagName(localName, qName);
elementPath.push(tagName);

if (skip != null) {
// every startElement pushes an action list
pushEmptyActionList();
return;
}

List<Action> applicableActionList = getApplicableActionList(elementPath, atts);
if (applicableActionList != null) {
actionListStack.add(applicableActionList);
callBeginAction(applicableActionList, tagName, atts);
} else {
// every startElement pushes an action list
pushEmptyActionList();
String errMsg = "no applicable action for [" + tagName + "], current ElementPath is [" + elementPath + "]";
cai.addError(errMsg);
}
}

里面就是用了 ruleStore 来判断是否适配,并且取出对应的 Action

1
2
3
4
5
6
7
8
9
10
List<Action> getApplicableActionList(ElementPath elementPath, Attributes attributes) {
List<Action> applicableActionList = ruleStore.matchActions(elementPath);

// logger.debug("set of applicable patterns: " + applicableActionList);
if (applicableActionList == null) {
applicableActionList = lookupImplicitAction(elementPath, attributes, interpretationContext);
}

return applicableActionList;
}

比如我这边往下处理,通过规则匹配拿到了 LoggerAction, 然后调用 Action.begin 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void callBeginAction(List<Action> applicableActionList, String tagName, Attributes atts) {
if (applicableActionList == null) {
return;
}

Iterator<Action> i = applicableActionList.iterator();
while (i.hasNext()) {
Action action = (Action) i.next();
// now let us invoke the action. We catch and report any eventual
// exceptions
try {
action.begin(interpretationContext, tagName, atts);
} catch (ActionException e) {
skip = elementPath.duplicate();
cai.addError("ActionException in Action for tag [" + tagName + "]", e);
} catch (RuntimeException e) {
skip = elementPath.duplicate();
cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
}
}
}

就是下面这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public void begin(InterpretationContext ec, String name, Attributes attributes) {
// Let us forget about previous errors (in this object)
inError = false;
logger = null;

LoggerContext loggerContext = (LoggerContext) this.context;

String loggerName = ec.subst(attributes.getValue(NAME_ATTRIBUTE));

if (OptionHelper.isEmpty(loggerName)) {
inError = true;
String aroundLine = getLineColStr(ec);
String errorMsg = "No 'name' attribute in element " + name + ", around " + aroundLine;
addError(errorMsg);
return;
}

logger = loggerContext.getLogger(loggerName);

String levelStr = ec.subst(attributes.getValue(LEVEL_ATTRIBUTE));

if (!OptionHelper.isEmpty(levelStr)) {
if (ActionConst.INHERITED.equalsIgnoreCase(levelStr) || ActionConst.NULL.equalsIgnoreCase(levelStr)) {
addInfo("Setting level of logger [" + loggerName + "] to null, i.e. INHERITED");
logger.setLevel(null);
} else {
Level level = Level.toLevel(levelStr);
addInfo("Setting level of logger [" + loggerName + "] to " + level);
logger.setLevel(level);
}
}

String additivityStr = ec.subst(attributes.getValue(ActionConst.ADDITIVITY_ATTRIBUTE));
if (!OptionHelper.isEmpty(additivityStr)) {
boolean additive = OptionHelper.toBoolean(additivityStr, true);
addInfo("Setting additivity of logger [" + loggerName + "] to " + additive);
logger.setAdditive(additive);
}
ec.pushObject(logger);
}

这里就会处理 logger 获取逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public final Logger getLogger(final String name) {

if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
}

// if we are asking for the root logger, then let us return it without
// wasting time
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
}

int i = 0;
Logger logger = root;

// check if the desired logger exists, if it does, return it
// without further ado.
Logger childLogger = (Logger) loggerCache.get(name);
// if we have the child, then let us return it without wasting time
if (childLogger != null) {
return childLogger;
}

// if the desired logger does not exist, them create all the loggers
// in between as well (if they don't already exist)
String childName;
while (true) {
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -1) {
childName = name;
} else {
childName = name.substring(0, h);
}
// move i left of the last point
i = h + 1;
synchronized (logger) {
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
childLogger = logger.createChildByName(childName);
loggerCache.put(childName, childLogger);
incSize();
}
}
logger = childLogger;
if (h == -1) {
return childLogger;
}
}
}

如果从 loggerCache 中能取到就直接获取,如果不能就会去创建

0%