# Halo项目代码学习
> 版本提示:Java:1.8+,Spring6.0
>
> 仓库地址:https://github.com/halo-dev/halo.git
>
> 详情:请看2022年11月3日的提交记录。
## 一、源码
```java
/**
* Counter meter handler for {@link Counter}.
* 计数器的计数器仪表处理程序
* @author guqing
* @since 2.0.0
*/
@Slf4j
@Component
public class CounterMeterHandler implements SmartLifecycle {
private volatile boolean started = false;
private final ReactiveExtensionClient client;
private final MeterRegistry meterRegistry;
public CounterMeterHandler(ReactiveExtensionClient client, MeterRegistry meterRegistry) {
this.client = client;
this.meterRegistry = meterRegistry;
}
/**
* Synchronize counter meters from {@link Counter}.
*
* @param event application ready event
*/
@EventListener(ApplicationReadyEvent.class)
public Mono<Void> onApplicationReady(ApplicationReadyEvent event) {
return client.list(Counter.class, null, null)
.map(counter -> {
String name = counter.getMetadata().getName();
// visit counter
io.micrometer.core.instrument.Counter visitCounter =
MeterUtils.visitCounter(meterRegistry, name);
visitCounter.increment(nullSafe(counter.getVisit()));
// upvote counter
io.micrometer.core.instrument.Counter upvoteCounter =
MeterUtils.upvoteCounter(meterRegistry, name);
upvoteCounter.increment(nullSafe(counter.getUpvote()));
// downvote counter
io.micrometer.core.instrument.Counter downvoteCounter =
MeterUtils.downvoteCounter(meterRegistry, name);
downvoteCounter.increment(nullSafe(counter.getDownvote()));
// total comment counter
io.micrometer.core.instrument.Counter totalCommentCounter =
MeterUtils.totalCommentCounter(meterRegistry, name);
totalCommentCounter.increment(nullSafe(counter.getTotalComment()));
// approved comment counter
io.micrometer.core.instrument.Counter approvedCommentCounter =
MeterUtils.approvedCommentCounter(meterRegistry, name);
approvedCommentCounter.increment(nullSafe(counter.getApprovedComment()));
return counter;
})
.then();
}
int nullSafe(Integer value) {
return Objects.requireNonNullElse(value, 0);
}
/**
* Synchronize memory counter meter to the database every minute.
*/
@Scheduled(cron = "0 0/1 * * * ?")
public void counterPersistenceTask() {
log.debug("Regularly synchronize counter meters to the database.");
save().block();
}
Mono<Void> save() {
Map<String, List<Meter>> nameMeters = meterRegistry.getMeters().stream()
.filter(meter -> meter instanceof io.micrometer.core.instrument.Counter)
.filter(counter -> {
Meter.Id id = counter.getId();
return id.getTag(MeterUtils.METRICS_COMMON_TAG.getKey()) != null;
})
.collect(Collectors.groupingBy(meter -> meter.getId().getName()));
Stream<Mono<Counter>> monoStream = nameMeters.entrySet().stream()
.map(entry -> {
String name = entry.getKey();
List<Meter> meters = entry.getValue();
return client.fetch(Counter.class, name)
.switchIfEmpty(Mono.defer(() -> {
Counter counter = emptyCounter(name);
return client.create(counter);
}))
.flatMap(counter -> {
Counter oldCounter = JsonUtils.deepCopy(counter);
counter.populateFrom(meters);
if (oldCounter.equals(counter)) {
return Mono.empty();
}
return Mono.just(counter);
})
.flatMap(client::update);
});
return Flux.fromStream(monoStream)
.flatMap(Function.identity())
.then();
}
static Counter emptyCounter(String name) {
Counter counter = new Counter();
counter.setMetadata(new Metadata());
counter.getMetadata().setName(name);
counter.setUpvote(0);
counter.setTotalComment(0);
counter.setApprovedComment(0);
counter.setVisit(0);
return counter;
}
@Override
public void start() {
this.started = true;
}
@Override
public void stop() {
log.debug("Persist counter meters to database before destroy...");
try {
save().block();
} catch (Exception e) {
log.error("Persist counter meters to database failed.", e);
}
this.started = false;
}
@Override
public boolean isRunning() {
return started;
}
}
```
**说明**:`CounterMeterHandler`类实现了`SmartLifecycle`接口,共计有3个属性(域),一个显示构造方法,以及其他11个方法。
**属性:** `started`、` ReactiveExtensionClient`、`MeterRegistry`
```
# 程序线程是否已经开始
private volatile boolean started = false;
# 反应式扩展相关类客户端
private final ReactiveExtensionClient client;
# 仪表注册
private final MeterRegistry meterRegistry;
```
**方法:**
- `onApplicationReady` 从计数器同步计数器仪表;
- ` save` 将计数器的中的数据更新存储到数据库;
- `counterPersistenceTask` 每分钟定时任务将数据同步到数据库;
- `nullSafe` 确保参数空安全;
- `emptyCounter` 创建一个空数据的计数器;
- `start` 标记开始,启动;
- `stop` 停止;
- `isRuning` 是否在运行
## Lifecycle接口
### SmartLifecycle接口 (智能生命周期回调钩子)
在这里特别介绍`SmartLifecycle`接口,该接口又实现了`Lifecycle`和`Phased`接口。下面是接口注释的中文翻译。
> 注:org.springframework.context;篇幅限制,不展示源码。如要查看,请看附录。
生命周期接口的扩展,用于需要在应用程序上下文刷新和/或关闭时按特定顺序启动的对象。
isAutoStartup() 返回值指示是否应在上下文刷新时启动此对象。回调接受 stop(Runnable) 方法对于具有异步关闭进程的对象很有用。此接口的任何实现都必须在关闭完成后调用回调的 run() 方法,以避免在整个 ApplicationContext 关闭中出现不必要的延迟。
此接口扩展了 Phased,getPhase() 方法的返回值指示应在其中启动和停止此生命周期组件的阶段。启动过程从最低相位值开始,以最高相位值结束(Integer.MIN_VALUE是可能的最低值,Integer.MAX_VALUE 是可能的最高值)。关机过程将应用相反的顺序。任何具有相同值的组件将在同一阶段内任意排序。
示例:如果组件 B 依赖于组件 A 已启动,则组件 A 的相位值应低于组件 B。在关闭过程中,组件 B 将在组件 A 之前停止。
任何显式的“依赖”关系都将优先于阶段顺序,这样依赖 Bean 总是在其依赖关系之后启动,并且总是在其依赖关系之前停止。上下文中未实现 SmartLifecycle 的任何生命周期组件都将被视为阶段值为 0。
如果 SmartLifecycle 组件具有负相位值,则允许 SmartLifecycle 组件在这些生命周期组件之前启动,或者如果 SmartLifecycle 组件具有正相位值,则 SmartLifecycle 组件可以在这些生命周期组件之后启动。
请注意,由于 SmartLifecycle 中的自动启动支持,在任何情况下,SmartLifecycle Bean 实例通常会在应用程序上下文启动时进行初始化。因此,Bean 定义 lazy-init 标志对 SmartLifecycle Bean 的实际影响非常有限。
### Lifecycle接口 (生命周期回调钩子)
**接口注释说明:**
定义启动停止生命周期控制方法的通用接口。其典型用例是控制异步处理。注意:此接口并不意味着特定的自动启动语义。考虑为此目的实施智能生命周期。
可以由组件(通常是在 Spring 上下文中定义的 Spring Bean)和容器(通常是 Spring ApplicationContext 本身)实现。容器会将启动停止信号传播到每个容器内应用的所有组件,例如,用于运行时的停止重启场景。可用于直接调用或通过 JMX 进行管理操作。在后一种情况org.springframework.jmx.export.MBeanExporter 通常使用 org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler 定义,从而将活动控制组件的可见性限制为生命周期界面。
请注意,当前的生命周期接口仅在顶级单例 Bean 上受支持。在任何其他组件上,生命周期接口将保持未检测到状态,因此被忽略。另请注意,扩展的 SmartLifecycle 接口提供了与应用程序上下文的启动和关闭阶段的复杂集成。
```java
public interface Lifecycle {
/**
* 启动当前组件
* 如果组件已经在运行,不应该抛出异常
* 在容器的情况下,这会将 开始信号 传播到应用的所有组件中去。
*/
void start();
/**
* (1)通常以同步方式停止该组件,当该方法执行完成后,该组件会被完全停止。当需要异步停
* 止行为时,考虑实现SmartLifecycle 和它的 stop(Runnable) 方法变体。
* 注意,此停止通知在销毁前不能保证到达:在*常规关闭时,{@code Lifecycle} bean将首先收到一个停止通知,然后才传播*常规销毁回调;然而,在*上下文的生命周期内的热刷新或中止的刷新尝试上,只调用销毁方法
* 对于容器,这将把停止信号传播到应用的所有组件
*/
void stop();
/**
* 检查此组件是否正在运行。
* 1. 只有该方法返回false时,start方法才会被执行。
* 2. 只有该方法返回true时,stop(Runnable callback)或stop()方法才会被执行。
*/
boolean isRunning();
}
```
### SmartLifecycle接口 (智能生命周期回调钩子)
```
public interface SmartLifecycle extends Lifecycle, Phased {
int DEFAULT_PHASE = Integer.MAX_VALUE;
default boolean isAutoStartup() {
return true;
}
// 停止此组件,通常以同步方式停止,以便在此方法返回时完全停止组件。考虑在需要异步停止行为时实现SmartLifecycle及其stop(Runnable)变体。注意,这个停止通知不保证在销毁之前出现:在常规关闭时,Lifecycle bean将首先在传播常规销毁回调之前接收到一个停止通知;然而,在上下文生命周期内的热刷新或失败的刷新尝试中,给定bean的destroy方法将被调用,而不需要预先考虑停止信号。如果组件未运行(尚未启动),则不应抛出异常。在容器的情况下,这将传播停止信号到所有应用的组件
default void stop(Runnable callback) {
stop();
callback.run();
}
@Override
default int getPhase() {
return DEFAULT_PHASE;
}
```
### LifecycleProcessor 生命周期处理器
经过资料查询,不得不提的到**生命周期处理器**。
请注意,`LifecycleProcessor`本身就是`LifeCycle`接口的扩展。它还添加了另外两个方法来响应spring容器上下文的**刷新**(`onRefresh`)和**关闭**(`close`)。
```
public interface LifecycleProcessor extends Lifecycle {
/**
* 响应Spring容器上下文 refresh
*/
void onRefresh();
/**
* 响应Spring容器上下文 close
*/
void onClose();
}
```
Spring中有一个默认的实现的生命周期处理器。`DefaultLifecycleProcessor`,它实现类了`LifecycleProcessor`接口,它在`AbstractApplicationContext`中被创建。如果没有自定义的`LifecycleProcessor`,就自动创建初始化默认的`DefaultLifecycleProcessor`。看一看,源码是如何实现的呢?
```java
/**
* Initialize the LifecycleProcessor.
* Uses DefaultLifecycleProcessor if none defined in the context.
* @see org.springframework.context.support.DefaultLifecycleProcessor
*/
protected void initLifecycleProcessor() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {
this.lifecycleProcessor =
beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
if (logger.isTraceEnabled()) {
logger.trace("Using LifecycleProcessor [" + this.lifecycleProcessor + "]");
}
}
// 如果没有自定义实现LifecycleProcessor,就创建DefaultLifecycleProcessor。
else {
DefaultLifecycleProcessor defaultProcessor = new DefaultLifecycleProcessor();
defaultProcessor.setBeanFactory(beanFactory);
this.lifecycleProcessor = defaultProcessor;
beanFactory.registerSingleton(LIFECYCLE_PROCESSOR_BEAN_NAME, this.lifecycleProcessor);
if (logger.isTraceEnabled()) {
logger.trace("No '" + LIFECYCLE_PROCESSOR_BEAN_NAME + "' bean, using " +
"[" + this.lifecycleProcessor.getClass().getSimpleName() + "]");
}
}
}
```
我们可以简单的实现一个案例,例如。下面是一个实现`Lifecycle`接口实现类。
```java
@Slf4j
@Component
public class DemoLifecycle implements Lifecycle {
private volatile boolean started = false;
public DemoLifecycle() {
System.out.println("Constructing DemoLifecycle ...");
}
public void doSomething() {
System.out.println("doing something");
}
@Override
public void start() {
System.out.println(" DemoLifecycle starts");
this.started = true;
}
@Override
public void stop() {
System.out.println(" DemoLifecycle stops");
this.started = false;
}
@Override
public boolean isRunning() {
return this.started;
}
}
```
下面是测试类和测试结果。
```java
public class AppApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(DemoLifecycle.class);
DemoLifecycle demoLifecycle = applicationContext.getBean(DemoLifecycle.class);
demoLifecycle.start();
demoLifecycle.doSomething();
if (demoLifecycle.isRunning()) {
System.out.println(" demoLifecycle is running!");
}
demoLifecycle.doSomething();
demoLifecycle.stop();
demoLifecycle.doSomething();
}
```
打印结果:
```
二月 06, 2023 5:44:40 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@20ad9418: startup date [Mon Feb 06 17:44:40 CST 2023]; root of context hierarchy
Constructing DemoLifecycle ...
DemoLifecycle starts
doing something
demoLifecycle is running!
doing something
DemoLifecycle stops
doing something
```
## git提交记录
在学习该项目的时候,可以看到git提交记录,修复Bug。
fix: cannot stop thread when system is interrupted (#2639)
###### What type of PR is this?
/kind bug /kind improvement /area core /milestone 2.0
###### What this PR does / why we need it:
修复 Halo 异常停止时日志服务线程无法中断的问题
###### Special notes for your reviewer: how to test it?
使用 mysql 启动 halo 但不启动 mysql,此时 halo 会无法链接 mysql 期望现象:Halo 服务终止,异常现象:Halo 抛异常但 Netty 服务器不会停止 /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change?
```release-note 修复 Halo 异常停止时日志服务线程无法中断的问题 ```
![image-20230206175328257](https://s2.loli.net/2023/02/06/lobRudKpMer6X5Q.png)
![image-20230206175529601](https://s2.loli.net/2023/02/06/tLNZI1AkwebMTqc.png)
从这个提交记录可以得知,新的实现,为修复:**系统阻塞时线程不能停止**Bug。请注意到,这个类之前,实现的是`DisposableBean`接口。看来更改接口实现,解决系统阻塞,线程相关的问题。从刚才对`SmartLifecycle`接口的简单分析。
先请看`DisposableBean`接口。仅有一个方法,用于销毁bean。该方法**在销毁bean时由包含bean的BeanFactory调用**。
```java
public interface DisposableBean {
/**
* Invoked by the containing {@code BeanFactory} on destruction of a bean.
* @throws Exception in case of shutdown errors. Exceptions will get logged
* but not rethrown to allow other beans to release their resources as well.
* 在销毁bean时由包含bean的BeanFactory调用。抛出:异常-在关机错误的情况下。异常将被记录下来,但不会被重新抛出,以允许其他 * bean也释放它们的资源。
*/
void destroy() throws Exception;
}
```
翻译成白话就是:“ 销毁方法,在bean准备销毁的时候执行。”
让我们简单实现一个案例,测试一下`DisposableBean`的作用。下面是实现了`DisposableBean`的MyBean实现类。
```
@Configuration
public class MyBean implements DisposableBean {
public MyBean() {
System.out.println("Is Constructing");
}
@Override
public void destroy() throws Exception {
System.out.println("Before destroying,do something");
}
public void doSomething() {
System.out.println("doing something");
}
}
```
让我们简单测试一下吧。
```java
public class AppApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBean.class);
MyBean myBean = context.getBean(MyBean.class);
myBean.doSomething();
// 销毁 Bean ,Bean 销毁并不意味着 Bean 垃圾回收了
myBean.destroy();
// 强制 GC
System.gc();
// 等待一段时间
Thread.sleep(1000000L);
// 再强制 GC
System.gc();
}
}
```
打印结果如下:(遗憾的是,这也看不粗来有什么变化。我们还是不知道,MyBean到底有没有被销毁。即使强制垃圾回收,MyBean大概率依旧存在。MyBean销毁不意味着,立刻被垃圾回收。)
```
二月 07, 2023 2:37:14 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@20ad9418: startup date [Tue Feb 07 02:37:14 CST 2023]; root of context hierarchy
Is Constructing
doing something
Before destroying,do something
Process finished with exit code 0
```
Spring官方不建议使用`DisposableBean`接口,
## 总结
不管是`SmartLifecycle`接口,还是`DisposableBean`都是用于管理Bean的生命周期。请看这部分代码。
```
@Override
public void stop() {
log.debug("Persist counter meters to database before destroy...");
try {
save().block();
} catch (Exception e) {
log.error("Persist counter meters to database failed.", e);
}
this.started = false;
}
```
相比于之前的代码,` this.started = false;`主要标记该类被停止,将会被相应处理类销毁。关键字volatile解决的是变量在多个线程之间的可见性。确保多个线程之间,能够“接收到通知”,直到这个类要被销毁了。确保日志线程及时停止。
最后,学习Spring还需要看更多源码,分析源码的功能。Spring的设计很好地体现了设计模式实现原则,尤其是,本文部分,正好体现《Effective Java》中第4章类和接口内容的设计原则。
其中,令人印象深刻的是,Spring的源码很好地体现了“复合优先于继承”、“接口优先于抽象类”。《Effective Java》真的是,Java设计与开发的“内功真经"。
## 参考
Spring.io : Spring核心框架部分文档。
halo.run github仓库。
值得一提的是,halo项目采用的是Flux响应式Spring。
Halo项目代码学习