【菜鸟读源码】halo✍源码学习 (一)

Halo 是一款现代化的个人独立博客系统,给习惯写博客的同学一个更好的选择。据说这是一个较容易读懂的 Spring-Boot 项目,那我就希望通过这个项目学习前辈的经验。

如有帮助,不胜荣幸。如有错误,欢迎指正!

前言

最早看到这个博客的源码的时候是通过 B 站 up 主 -CodeSheep 的一个视频:Java 企业级开源项目推荐,帮助大家从学习走向实践,奈何当时自己知识有限,没有仔细的阅读源码。近日 Halo 也推出了正式版,我也就抱着学习的心态拜读一下。

首先打开工程,看到整个工程有以下两个明显的变化:

  • 配置文件由 properties 变为 yaml

可以明显的看到,在处理层级关系的时候,properties 需要使用大量的路径来描述层级(或者属性),比如 environments.dev.url 和 environments.dev.name。其次,对于较为复杂的结构,比如数组(my.servers),写起来更为复杂。而对应的 YAML 格式文件就简单很多:

  • 构建工具由 meavn 变为 gradle

因为此前有过 Android 的开发经验,所以这一改变对我的影响并不大。gradle 逐渐替代 meavn 应该是目前的趋势,但是目前大部分教学和企业采用的还是以 meavn 为主,所以此前我也未曾尝试过采用 gradle 构建项目。由此看出 Halo 还是很 Fashion 的。

Application

首先打开 Application.java 文件,看看有什么学习的地方。

@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
@EnableAsync
@EnableJpaRepositories(basePackages = "run.halo.app.repository", repositoryBaseClass = BaseRepositoryImpl.class)
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        // Customize the spring config location
        System.setProperty("spring.config.additional-location", "file:${user.home}/.halo/,file:${user.home}/halo-dev/");

        // Run application
        SpringApplication.run(Application.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        System.setProperty("spring.config.additional-location", "file:${user.home}/.halo/,file:${user.home}/halo-dev/");
        return application.sources(Application.class);
    }
}

发现与我之前 Spring-Boot 开发两点不一样的地方:

其一:继承 SpringBootServletInitializer 从而实现 configure 方法

可能找答案的姿势不对,只零星找到下面几点描述:

1、启动类继承 SpringBootServletInitializer 类,重写 configure (SpringApplicationBuilder builder) 方法

 2、因为想要用 web 容器启动项目

 3、使用外部 servlet 容器

结论:为了 Undertow 容器!

原因:

1、排除内置的 tomcat 容器 - build.gradle

exclude module: 'spring-boot-starter-tomcat

2、添加 Undertow 依赖 - build.gradle

implementation 'org.springframework.boot:spring-boot-starter-undertow

3、配置服务器

server:
  port: 8090
  use-forward-headers: true
  undertow:
    io-threads: 2
    worker-threads: 36
    buffer-size: 1024
    directBuffers: true

 结合此前在别人基础上修改的网盘项目得到了相同的印证。

 其二:System.setProperty ("spring.config.additional-location", "...");

开发者给了以下注释:Customize the spring config location(定制 spring 配置文件的位置)

存在以下三个疑问:

1、System.setProperty 有何用?

 setProperty (String prop, String value); 1、 设置指定键对值的系统属性,其中 prop:系统属性的名称,value:系统属性的值。注:这里的 system,系统指的是 JRE (runtime) system,不是指 OS。 2、System.setProperty 相当于一个静态变量,存在内存里面,可以在项目的任何一个地方,通过 System.getProperty ("变量") 来获得

2、为何要设定这个变量?

加载外部配置文件。打包 jar 运行也不方便修改 jar 内部数据,通过设置环境变量 spring.config.location。因为博客中也有很多配置选项,所以猜想需要从外部读取某些会改变的配置,需要继续阅读源码验证猜想。

3、${user.home} 从何而来?

如从前所示 user.home 应该是一个静态变量,尝试打印:

System.out.println(System.getProperty("user.home"));
//打印出:C:\Users\74472 验证猜想成功
变量 含义
java.version Java 运行时环境版本
java.vendor Java 运行时环境供应商
java.vendor.url Java 供应商的 URL
java.home Java 安装目录
java.vm.specification.version Java 虚拟机规范版本
java.vm.specification.vendor Java 虚拟机规范供应商
java.vm.specification.name Java 虚拟机规范名称
java.vm.version Java 虚拟机实现版本
java.vm.vendor Java 虚拟机实现供应商
java.vm.name Java 虚拟机实现名称
java.specification.version Java 运行时环境规范版本
java.specification.vendor Java 运行时环境规范供应商
java.specification.name Java 运行时环境规范名称
java.class.version Java 类格式版本号
java.class.path Java 类路径
java.library.path 加载库时搜索的路径列表
java.io.tmpdir 默认的临时文件路径
java.compiler 要使用的 JIT 编译器的名称
java.ext.dirs 一个或多个扩展目录的路径
os.name 操作系统的名称
os.arch 操作系统的架构
os.version 操作系统的版本
file.separator 文件分隔符(在 UNIX 系统中是 “/”)
path.separator 路径分隔符(在 UNIX 系统中是 “:”)
line.separator 行分隔符(在 UNIX 系统中是 “/n”)
user.name 用户的账户名称
user.home 用户的主目录
user.dir 用户的当前工作目录

测试运行

Spring-Boot 的项目运行起来还是比较简单的。页面也很美观
Halo博客

Controller 学习

Spring-boot 的项目通常都是从 Controller 读起,发现一个共同的特点,就是代码都是类似这样一个结构:

private final PostService postService;

private final OptionService optionService;

... .... // 其他的Service

private final ThemeService themeService;

public ContentIndexController(PostService postService,
                              OptionService optionService,
                              ThemeService themeService) {
    this.postService = postService;
    this.optionService = optionService;
    ... .... // 其他的Service
    this.themeService = themeService;
}

@GetMapping
public String index(Model model) {
    return this.index(model, 1, Sort.by(DESC, "topPriority").and(Sort.by(DESC, "createTime")));
}

 @GetMapping(value = "page/{page}")
public String index(Model model,
                    @PathVariable(value = "page") Integer page,
                    @SortDefault.SortDefaults({
                        @SortDefault(sort = "topPriority", direction = DESC),
                        @SortDefault(sort = "createTime", direction = DESC)
                    }) Sort sort) {
    ...//省略   
}

 学习收获一:使用构造器注入需要用到的 Service

学习收获二:使用多态处理请求

真正起作用的是后者,前者只是为用户添加了参数后调用后者。

return this.index(model, 1, Sort.by(DESC, "topPriority").and(Sort.by(DESC, "createTime")));

 学习收获三:RESTful 的 api 设计

讨论数量: 2

自己顶自己 :+1: :+1:

2周前 评论

前辈,

file

源码启动服务,一直卡在这,有遇到过吗?

点击关闭后就报错出现:

file

1周前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!