轻量级仿 SpringBoot 程序

但凡 Java 程序,想必就是 Spring 程序;但凡 Spring 程序,想必就是 SpringBoot 程序——且慢,当今尚有不是 SpringBoot 即 SpringMVC 的程序不?有——老旧的遗留系统不就是嘛~——不,其实只要稍加“调教”,SpringMVC 即可原地变为非常近似 SpringBoot 的形态,——不信?且待笔者慢慢道来。

早在若干年前笔者已经热衷于无 XML 文件配置的 SpringMVC,全部用注解来配置(参见博文)。不过仍然依赖独立的 Tomcat 来运行。后来笔者更进一步,成功使用嵌入式的 Tomcat 来运行,使之形态更相似 SpringBoot,参见博文《轻量级仿 Spring Boot=嵌入式 Tomcat+Spring MVC》。当前方案在多个现实中项目中使用,可以说跟 SpringBoot 非常地相似了。最近笔者比较有时间,于是重构了这个技术点,独立发布一个工程在 Github 上,并发布 Maven 中央库,同时也想回顾一下个中的原理。

这个技术点取名 lightweight-springboot,开源,可通过 Maven 依赖到你的项目中启动,与 SpringBoot 用法大体一致。

安装与使用

安装 lightweight-springboot

源码:https://github.com/lightweight-component/aj-lightweight-springboot。
Java Documents: https://dev.ajaxjs.com/docs/javadoc/aj-lightweight-springboot/。

需要 Java 1.8+, Maven:

<dependency>
    <groupId>com.ajaxjs</groupId>
    <artifactId>aj-lightweight-springboot</artifactId>
    <version>1.1</version>
</dependency>

使用方式

在资源目录中安排 application.yml 文件,内容如下:

server:
    port: 8888 # 端口号
    context-path: /foo  # 项目名,如果不设定,默认是 /
    localFileUpload: true # 是否支持本地文件上传

代码结构按照惯常开发的模式即可。必须要有启动类和相关的配置类。

在这里插入图片描述

启动的main()函数内的start()必须传入配置类参数;指定@ComponentScan扫描包的范围。

import com.ajaxjs.embeded_tomcat.EmbeddedTomcatStarter;
import com.ajaxjs.framework.BaseWebMvcConfigure;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ComponentScan({"com.foo"})
public class FooApplication extends BaseWebMvcConfigure {
    public static void main(String[] args) {
        EmbeddedTomcatStarter.start(FooApplication.class); // FooApplication 本身为配置类
    }
}

另外有一个专门配置类FooConfigFooApplicationFooConfig两者皆实现WebMvcConfigurer接口),在包com.foo下,配置数据库、注入依赖组件等等,如下例。

import com.ajaxjs.data.jdbc_helper.JdbcConn;
import com.ajaxjs.data.jdbc_helper.JdbcWriter;
import com.ajaxjs.iam.resource_server.UserInterceptor;
import com.ajaxjs.util.logger.LogHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.sql.DataSource;

/**
    * 程序配置
    */
@Configuration
public class FooConfig implements WebMvcConfigurer {
    @Value("${db.url}")
    private String url;

    @Value("${db.user}")
    private String user;

    @Value("${db.psw}")
    private String psw;

    @Bean(value = "dataSource", destroyMethod = "close")
    DataSource getDs() {
        return JdbcConn.setupJdbcPool("com.mysql.cj.jdbc.Driver", url, user, psw);
    }
}

Demo 程序

为方便大家观摩了解,可以第一时间领略该项目,笔者准备了一个现成的演示程序,也可以作为创建程序的样板或者说“脚手架(Scaffolding)”,开发者可以在此基础上进行进一步的开发,而不必从头开始编写所有的代码。

https://github.com/lightweight-component/aj-framework-demo-service

启动流程

加载配置

当执行 Java 程序的main()函数时候,lightweight-springboot 会执行什么呢?首先是加载 yaml 配置文件,参见EmbeddedTomcatStarter类下面的start()方法。
在这里插入图片描述
解析 yaml 借助第三方组件 SnakeYaml,它手动加载配置的方法如下:

/**
 * 从类路径下的 application.yml 文件中获取服务器配置。
 *
 * @return 服务器配置的 Map 对象,如果文件不存在或读取失败,则返回null
 */
@SuppressWarnings("unchecked")
static Map<String, Object> getServerConfig() {
    ClassPathResource resource = new ClassPathResource("application.yml");

    if (!resource.exists())
        return null;

    try {
        Map<String, Object> yamlMap = new Yaml().load(resource.getInputStream());

        return (Map<String, Object>) yamlMap.get("server");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
} 

配置都返回为 Map,这就非常好用了。

初始化 Tomcat 对象

得到配置后就创建EmbeddedTomcatStarter实例并执行start(),包含以下流程:

initTomcat();
initConnector();
initContext();
runTomcat();

主要围绕 Tomcat 进行配置和启动。这里和 SpringBoot 不同:SpringBootmain()函数执行后,是先启动 Spring 自身的各项功能,几乎最后才启动 Web 容器的(如 Tomcat)。lightweight-springboot 则是先启动 Tomcat 再通知 Spring 初始化,与原来的 SpringMVC 启动方式差不多,也就是把原来webx.xml里面的配置,改为 Java 语言命令语句(Programmatically)去调用。当然,你必须了解 Tomcat 的 API 怎么调用才行。

Web 容器的一般配置,无非端口、ContextPath、BaseDir 诸如此类的配置,我们安排了一个 POJO TomcatConfig来管理这些配置。
在这里插入图片描述

Tomcat Connector

另外是对 Tomcat Connector 的配置。Tomcat Connector 是 Tomcat 服务器中负责接收请求、处理协议、与 Servlet 容器交互的桥梁,是 Tomcat 架构中的重要组成部分。通过合理配置和优化 Connector,可以改善 Web 程序并发、线程等的问题。另外还有用于监控的 JMX 也是通过 Connector 来配置的。

Tomcat Context

接着对 Tomcat Context 的配置。Tomcat Context 是 Tomcat 服务器中用于定义特定 Web 应用程序的环境和配置的组件,为 Web 应用提供了一个隔离的环境,使得不同的 Web 应用可以在同一台服务器上独立运行,互不干扰。通过合理配置 Context,可以优化 Web 应用的性能和安全性。不过我们的程序一个 jar 包就是一个 context,不存在多个 context。

比较相关的是设置 JSP 页面。一般来讲此类的工程都不是为 Web 页面服务的,都是做 API 接口的,故所以一般不需要 JSP 表示层渲染,而且禁止 JSP 及其他资源的扫描(Servlet 3 的新特性),可以大大加快程序的启动。

启动 Tomcat

最后,执行runTomcat()启动 Tomcat。

启动 Spring

咦~上述过程怎么没有提及如何启动 Spring 的?稍安勿躁,这不就讲讲如何启动 Spring,它是专门规划到一个类EmbeddedTomcatStarter,上述的流程只是针对 Tomcat 的,是为TomcatStarter类,然后EmbeddedTomcatStarter继承于TomcatStarter,——这样规划比较清晰。

我们看看EmbeddedTomcatStarter.onContextReady()源码:在这里插入图片描述

可见是在 Context 就绪之后才能初始化 Spring。之前笔者尝试不用异步/事件的方法调用,但结构上不太理想,还是按照 Tomcat API 标准的写法去弄。

注意这里的初始化 Spring 语句,其实与web.xml的非常像,就是换了个写法。此时org.springframework.web.context.support.AnnotationConfigWebApplicationContext会创建一个ApplicationContext,这是 Spring 应用程序的核心,用于管理所有的 Spring 组件。

接下来 Spring 就会进行其自身的相关初始化工作:首先遍历所有的 Bean 定义,包括 @Component@Service@Repository@Controller 等注解的类,并将它们注册到 Spring 容器中;然后根据 Bean 定义创建 Bean 实例,并进行依赖注入;如果 Bean 实现了InitializingBean接口或通过@PostConstruct注解标记的方法,Spring 将调用这些方法进行初始化。最后注册所有的事件监听器,并在ApplicationContext初始化完成后触发ApplicationEvent

// 配置 ServletContext 参数,指定使用的上下文类。
ctx.setInitParameter("contextClass", "org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
ctx.addListener(new ContextLoaderListener()); // 添加 ContextLoaderListener 监听器,用于初始化和销毁 Spring Web 上下文
ctx.setAttribute("ctx", ctx.getContextPath()); // 设置上下文路径到 ServletContext 属性,以便在 JSP 中使用

// 绑定 Servlet,配置 Spring MVC 的 DispatcherServlet,并设置其加载优先级
ServletRegistration.Dynamic registration = ctx.addServlet("dispatcher", new DispatcherServlet(ac));
registration.setLoadOnStartup(1);// 设置 Tomcat 启动立即加载 Servlet
registration.addMapping("/"); // 浏览器访问 uri。注意不要设置 /*

// 配置字符过滤器,确保请求和响应的字符编码正确
FilterRegistration.Dynamic filterReg = ctx.addFilter("InitMvcRequest", new UTF8CharsetFilter());
filterReg.addMappingForUrlPatterns(null, true, "/*");

回到启动这里,值得一提的是下一步的绑定 Servlet:配置 SpringMVC 的 DispatcherServlet,并设置其加载优先级。我们知道,SpringMVC 是通过 DispatcherServlet 来分发处理请求,在 SpringBoot 出现之前,都是需要在web.xml里配置,来实现请求的拦截。

而在 Servlet 3.0 之后,规则中新增了 Dynamic Servlet、Dynamic Filter 这些概念, 可以在运行时动态添加/注册组件到 Context 中。这里就是 Dynamic Servlet、Dynamic Filter 的应用。

ServletContainerInitializer

上述过程是使用 Java 语言“硬编码”调用 Spring 的,其实还有一种方法只要配置不用编码就可以启动 Spring,那就是 SCI(ServletContainerInitializer)的应用。SCI 不是 Spring 的特性,而是 Servlet 3.0 标准的特性,用于接收 Web 应用在启动阶段通知的接口,再根据通知进行一些编程式的处理,比如触发 Spring 启动、动态注册Servlet、Filter 等。

也就是说,当你希望 WebApp 启动时候,也启动某些组件,就可以使用这个 SCI。组件具体可以是你的 jar 包。容器在启动应用的时候,会扫描当前应用每一个 jar 包里面META-INF/services/javax.servlet.ServletContainerInitializer指定的实现类,启动并运行这个实现类的onStartup方法。这种称为可插拔性(Pluggability)。

Jar 文件的 META-INF 中 services 中包含一个 SCI 的声明。在这里插入图片描述
SpringBoot 也是这样被点燃的

public void onStartup(ServletContext servletContext) throws ServletException {
    this.logger = LogFactory.getLog(this.getClass());
    WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
    if(rootAppContext != null) {
        servletContext.addListener(new ContextLoaderListener(rootAppContext) {
            public void contextInitialized(ServletContextEvent event) {
            }
        });
    } else {
        this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
    }

}

参考文章:《Tomcat 是怎样处理 SpringBoot 应用的?》、《Tomcat 中的可插拔以及 SCI 的实现原理》。

SPI 机制

顺带说说与 SCI 相仿的 SPI。我们知道 SpringBoot 相较于 Spring 的一大特性就是自动装配,那么自动装配是怎么具体实现的呢?其实在实现自动装配上 SpringBoot 采用了多种方案结合的,比如基于 Spring 的扩展点的自动属性注入等,还有提供了一套 SPI 机制让程序自动可插拔的装配。

SPI(Service Provider Interface)是 JDK 内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,它允许开发者编写一个服务接口,然后通过在项目中使用服务提供者实现该接口的方式,实现对应的服务功能。

SPI 机制通过在 Classpath 中的META-INF/services目录下,创建以服务接口全限定名命名的文件,文件的内容为实现该接口的具体实现类。当应用程序需要使用该服务时,JDK 会自动加载并实例化配置文件中列出的实现类,并提供给应用程序使用。

参见《深入剖析 Spring Boot 的 SPI 机制 》、《SpringBoot(二):SpringBoot自动装配之 SPI 机制》。

扩展

单纯启动 Tomcat/Spring 没几个类,lightweight-springboot 核心就这几个类:
在这里插入图片描述
filter 包下面的是扩展的过滤器,或者说拦截器。当前默认加载的有:

  • UTF8CharsetFilter,避免中文乱码
  • FileUploadHelper,Servlet 3 自带的文件上传功能
  • ShowControllerInterceptor拦截器,获得 Controller 方法名、请求参数和注解信息,打印出来,以方便调试。这个要 Spring 配置来启动。

lightweight-springboot 作为提供底层功能的库,更多的扩展或配置是放在上层去调用的。我们知道,SpringBoot 一般是实现WebMvcConfigurer接口来配置的,——在 lightweight-springboot 同样如是。比如说配置统一返回 JSON 格式、全局异常拦截、相关通用 Bean 注入的等等。这属于上层框架配置的职责,每一个项目可能都不一样,就没有放在 lightweight-springboot 了。

具体可以看看笔者框架的实现:BaseWebMvcConfigure https://github.com/lightweight-component/aj-framework/blob/main/src/main/java/com/ajaxjs/framework/BaseWebMvcConfigure.java。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/759249.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot漫画网站系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

目前常见的几款企业级im即时通讯软件有哪些?

在目前的市场上&#xff0c;有几款常见的企业级即时通讯软件广受企业青睐。以下是其中几款主流企业级即时通讯软件的介绍和特点。 1. 微软Teams 微软Teams是一款集即时通讯、协作和视频会议功能于一体的企业级通讯软件。它提供了实时聊天、语音通话、视频会议、文件共享和团队…

零刻SER8 8845HS设置RAID磁盘阵列教程

SER8内置两个NVme硬盘位&#xff0c;支持硬盘RAID功能&#xff0c;目前支持以下几种RAID模式。 Volume&#xff08;卷&#xff09;&#xff1a;将多块硬盘拼接成一个大硬盘&#xff0c;也称为JBOD&#xff08;Just a Bunch Of Disks&#xff09;。它不要求硬盘容量相同&#xf…

软件构造 | 期末查缺补漏

软件构造 | 期末查缺补漏 总体观 软件构造的三维度八度图是由软件工程师Steve McConnell提出的概念&#xff0c;用于描述软件构建过程中的三个关键维度和八个要素。这些维度和要素可以帮助软件开发团队全面考虑软件构建的方方面面&#xff0c;从而提高软件质量和开发效率。 下…

What does the error ‘module ‘langchain‘ has no attribute ‘verbose‘ refer to?

题意&#xff1a;错误 module langchain has no attribute verbose 指的是什么意思&#xff1f; 问题背景&#xff1a; Kind of new to Langchain/Qdrant but Im building a recommendation engine to recommend users based on the contents of their associated PDF files, …

学习阳明心学,需要下真功夫,持续用功

阳明心学是功夫之学&#xff0c;看到善的就发扬光大&#xff0c;看到恶的就立即改正&#xff0c;这才是真功夫

LeetCode 算法:二叉搜索树中第K小的元素 c++

原题链接&#x1f517;&#xff1a;二叉搜索树中第K小的元素 难度&#xff1a;中等⭐️⭐️ 题目 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从1开始计数&#xff09;。 示例 1&#xff1a;…

【web】2、集成插件

1、element-plus 官网地址:设计 | Element Plus 安装 plus 及 icon 图标库 1.1 官网提供plus安装方法&#xff1a; 1.2 官网提供 icon 安装方法 1.3 安装 pnpm install element-plus element-plus/icons-vue main.ts全局安装element-plus,element-plus默认支持语言英语设…

Navicat 外网连接 mysql (1、通过SSH方式内网访问 2、对外开放3306端口)

1、通过SSH方式内网访问 直接常规方式使用IP、账号密码连接&#xff0c;失败 SSH方式&#xff1a; 常规 选项卡中&#xff1a;localhost录入数据库账号密码 SSH 选项卡中&#xff1a;勾选使用SSH&#xff0c;输入服务器IP、账号、密码 如果出现该错误&#xff0c;可能是服务器…

51个图表,完美展示数据分布关系!

本节介绍seaborn展示数据分布关系的图表&#xff08;Distribution plots&#xff09;的实现&#xff0c;该类图表用于展示数据集的分布规律&#xff0c;帮助快速获取数据多方面信息&#xff0c;例如&#xff0c;观测值的范围、中心趋势、是否存在某个方向上严重偏斜、是否存在双…

10大内网安全管理系统!企业内网安全必备系统

内网安全管理系统对于维护企业网络安全至关重要&#xff0c;它们帮助监控、管理内部网络资源&#xff0c;防止数据泄露和安全威胁。以下是十款知名的内网安全管理系统。 1. 安企神终端安全管理系统 详细介绍&#xff1a; 安企神是针对企业内网安全需求设计的一款综合管理系统&…

在大数据盛行的今天,为什么需要使用图数据库?

分类 性能 可扩展性 灵活性 复杂性 键值存储数据库 高 高 高 无 文档数据库 高 可变 高 低 列存储数据库 高 可变 一般 低 图数据库 可变 高 高 高 关系型数据库 可变 可变 低 一般 表1&#xff1a;5类主流数据库产品分析 对于深度数据的分析和…

数值分析笔记(四)数值微积分

牛顿-科茨公式 ∫ a b f ( x ) d x ≈ ( b − a ) ∑ k 0 n C k ( n ) f ( a k h ) \int_a^bf(x) \mathrm{d}x\approx(b-a)\sum_{k0}^nC_k^{(n)}f(akh) ∫ab​f(x)dx≈(b−a)k0∑n​Ck(n)​f(akh) 其中&#xff0c; C k ( n ) C_k^{(n)} Ck(n)​为科茨系数。 n1时&#xff…

Drag Select Compose:实现多平台图片多选功能的利器

Drag Select Compose:实现多平台图片多选功能的利器 在现代移动应用开发中,图片多选功能是一个常见且实用的需求。而实现这种功能可能涉及到复杂的手势处理和状态管理。今天,我将介绍一款强大的Compose多平台库——Drag Select Compose,它能够轻松实现类似于Google Photos…

Qt开发 | 无边框窗口 | 自定义标题栏 | 拖拽拉伸 | 窗口阴影 | 圆角窗口

文章目录 一、QWidget类介绍二、无边框窗口的基本实现三、自定义标题栏并实现拖拽拉伸四、设计一个无边框窗口公共类五、标题栏qss美化、关闭、最小化、最大化六、实现窗口阴影七、圆角窗口八、一个自定义标题栏带圆角阴影的窗口 一、QWidget类介绍 QWidget 是 Qt 框架中的一个…

SpringBoot整合MongoDB JPA使用

一、整合MongoDB SpringDataMongoDB是 SpringData家族成员之一&#xff0c;MongoDB的持久层框架&#xff0c;底层封装了 mongodb-driver。mongodb-driver 是 MongoDB官方推出的 Java连接 MongoDB的驱动包&#xff0c;相当于JDBC驱动。 SpringBoot整合 MongoDB&#xff0c;引入…

【MySQL】数据库——备份与恢复,日志管理1

一、数据备份的重要性 1.备份的主要目的是灾难恢复 在生产环境中&#xff0c;数据的安全性至关重要 任何数据的丢失都可能产生严重的后果造成数据丢失的原因&#xff1a; 程序错误人为,操作错误运算错误磁盘故障灾难&#xff08;如火灾、地震&#xff09;和盗窃 2.数据库备份…

pcap包常见拆分方法

文章目录 Wireshark 拆分流量包SplitCap使用简介魔数报错示例结果 在进行流量分析时&#xff0c;经常需要分析pcap流量包。但是体积过大的流量包不容易直接分析&#xff0c;经常需要按照一定的规则把它拆分成小的数据包。 这里统一选择cic数据集里的Thursday-WorkingHours.pcap…

【Oracle篇】逻辑备份工具expdp(exp)/impdp(imp)和物理备份工具rman的区别和各自的使用场景总汇(第八篇,总共八篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

基于局域网下的服务器连接、文件传输以及内网穿透教程 | 服务器连接ssh | 服务器文件传输scp | 内网穿透frp | 研究生入学必备 | 深度学习必备

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本篇博客分享的是基于局域网下的服务器连接&#x1f517;、文件传输以及内网穿透教程&#xff0c;内容非常完备✨&#xff0c;涵盖了在服务器上做深度学…