轻量级仿 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.framework.embeded_tomcat.BaseWebMvcConfigure;
import com.ajaxjs.framework.embeded_tomcat.EmbeddedTomcatStarter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;@Configuration
@EnableWebMvc
@ComponentScan({"com.ajaxjs.base", "com.ajaxjs.framework.data_service"})
public class BaseApplication extends BaseWebMvcConfigure {public static void main(String[] args) {EmbeddedTomcatStarter.start(BaseApplication.class); // BaseApplication 为配置类}
}

BaseApplication 配置类,配置数据库、注入依赖组件等等,如下例。

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 BaseConfiguration 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。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TikTok网页版使用指南:如何登录TikTok网页版?

海外版抖音TikTok&#xff0c;已成为连接全球观众的重要平台。据统计&#xff0c;在美国&#xff0c;TikTok的用户数量已达到近1.3亿&#xff0c;并且在国外的95后用户群体中很受欢迎。 TikTok网页版也提供了一个广阔的平台&#xff0c;让品牌和创作者在电脑端与全球观众互动&…

智能语音抽油烟机:置入WTK6900L离线语音识别芯片 掌控厨房新风尚

一、抽油烟机语音识别芯片开发背景 在繁忙的现代生活中&#xff0c;人们对于家居生活的便捷性和舒适性要求越来越高。传统的抽油烟机操作方式往往需要用户手动调节风速、开关等功能&#xff0c;不仅操作繁琐&#xff0c;而且在烹饪过程中容易分散注意力&#xff0c;增加安全隐…

大数据处理引擎选型之 Hadoop vs Spark vs Flink

随着大数据时代的到来&#xff0c;处理海量数据成为了各个领域的关键挑战之一。为了应对这一挑战&#xff0c;多个大数据处理框架被开发出来&#xff0c;其中最知名的包括Hadoop、Spark和Flink。本文将对这三个大数据处理框架进行比较&#xff0c;以及在不同场景下的选择考虑。…

ubuntu篇---添加环境变量并且在pycharm中使用

ubuntu篇—添加环境变量并且在pycharm中使用 一. 添加环境变量 vim ~/.bashrc 在文件末尾加上 保存退出 source ~/.bashrc二. 在pycharm中添加环境变量 1.打开pycharm&#xff0c;并打开你的项目 2.点击菜单栏中的“Run”&#xff0c; 选择“Edit Configurations” 3.在弹…

pytorch为自己的extension backend添加profiler功能

pytorch为自己的extension backend添加profiler功能 1.参考文档2.your-extension-for-pytorch需要增加的代码3.pytorch demo及如何调整chrome trace json文件4.[可视化](https://ui.perfetto.dev/) 本文演示了pytorch如何为自己的extension backend添加profiler功能 背景介绍 …

Taro +vue3 中的微信小程序中的分享

微信小程序 右上角分享 的触发 以及配 useShareAppMessage(() > {return {title: "电影属全国通兑券",page: /pages/home/index,imageUrl: "http:///chuanshuo.jpg",};}); 置 就是Taro框架中提供的一个分享Api 封装好的

项目经理必读:三步走实现项目高效管理

一个项目的成功往往取决于项目管理能力的高低。若管理不当&#xff0c;易导致团队成员间的推诿和抱怨&#xff0c;且项目团队还可能面临成员对目标不明确、信息不透明、进度难以跟踪等问题。作为项目经理&#xff0c;掌握有效的项目管理策略至关重要。 一、精细化的目标拆解 …

数据库逻辑结构设计-实体和实体间联系的转换、关系模式的优化

一、引言 如何将数据库概念结构设计的结果&#xff0c;即用E-R模型表示的概念模型转化为关系数据库模式。 E-R模型由实体、属性以及实体间的联系三个要素组成 将E-R模型转换为关系数据库模式&#xff0c;实际上就是要将实体及实体联系转换为相应的关系模式&#xff0c;转换…

【EtherCAT】TwinCAT3通过PLC修改SDO数据

目录 1、打开twincat3, 左边PLC右键->添加新项&#xff0c;建立PLC工程 2、->References右键添加库 3、找到Tc2_EtherCAT库&#xff0c;点确定。 4、PLC程序ST语言就可以调用下面的功能块函数 5、PLC编程界面右键->输入助手 1、打开twincat3, 左边PLC右键->添…

数据恢复篇:如何恢复丢失的Android短信?

许多用户发现自己处于重要短信意外从Android手机中删除的情况。幸运的是&#xff0c;有一些行之有效的方法可以在没有root的情况下恢复已删除的短信Android&#xff0c;这可以成为救命稻草。这些技术不需要深厚的技术知识&#xff0c;也不需要损害设备的安全性。为了帮助您摆脱…

jieba--《红楼梦》章节分卷并计算TF-IDF值(超详细)

目录 大致步骤&#xff1a; 任务1&#xff1a; 将红楼梦 根据卷名 分隔成 卷文件 红楼梦txt&#xff1a; 红楼梦卷头&#xff1a; 红楼梦章节分卷&#xff1a; 任务2&#xff1a;对每个卷进行分词&#xff0c;并删除包含停用词的内容 1.遍历所有卷的内容&#xff0c;并添…

Python26 Lambda表达式

1.什么是lambda表达式 lambda 是 Python 中的一个关键字&#xff0c;用于定义简单的匿名函数。与 def 关键字定义的标准函数不同&#xff0c;lambda 函数主要用于需要一个函数对象作为参数的简短操作。lambda 函数的设计哲学是简洁&#xff0c;因此它只能包含一条表达式&#…

7km远距离WiFi实时图传模块,无人机海上无线传输方案,飞睿智能WiFi MESH自组网技术

在浩瀚无垠的海洋上&#xff0c;无人机正在开启一场前所未有的技术创新。它们不再只是天空的舞者&#xff0c;更是海洋的守望者&#xff0c;为我们带来前所未有的视野和数据。而这一切的背后&#xff0c;都离不开一项创新性的技术——飞睿智能远距离WiFi实时图传模块与无线Mesh…

java必知必会-java IO

系列文章目录 java必知必会-java环境搭建 java必知必会-java开发工具安装与使用 java必知必会-java关键字、变量和常量 java必知必会-java数据类型和变量定义 java必知必会-java类和对象 java必知必会-java面向对象的三大特性 java必知必会-java继承与重写、重载 java必知必会…

昇思25天学习打卡营第1天|快速入门-Mnist手写数字识别

学习目标&#xff1a;熟练掌握MindSpore使用方法 学习心得体会&#xff0c;记录时间 了解MindSpore总体架构 学会使用MindSpore 简单应用时间-手写数字识别 一、MindSpore总体架构 华为MindSpore为全场景深度学习框架&#xff0c;开发高效&#xff0c;全场景统一部署特点。 …

Verilog进行结构描述(三):Verilog模块实例化

目录 1.模块实例化(module instantiation)2.实例数组(Array of Instances) 微信公众号获取更多FPGA相关源码&#xff1a; 1.模块实例化(module instantiation) 模块实例化时实例必须有一个名字。使用位置映射时&#xff0c;端口次序与模块的说明相同。使用名称映射时&#x…

搭建大型分布式服务(四十)SpringBoot 整合多个kafka数据源-支持生产者

系列文章目录 文章目录 系列文章目录前言一、本文要点二、开发环境三、原项目四、修改项目五、测试一下五、小结 前言 本插件稳定运行上百个kafka项目&#xff0c;每天处理上亿级的数据的精简小插件&#xff0c;快速上手。 <dependency><groupId>io.github.vipjo…

ZYNQ学习教程?ZYNQ-FPGA实战教程!

学习 ZYNQ 比FPGA、MCU、ARM 等传统工具开发要求更高&#xff0c;想学好 ZYNQ 也不是一蹴而就的事情。 学习 ZYNQ 要具备的技能&#xff1a; 1、 软件开发人员  计算机组成原理、 C、C语言、 计算机操作系统、tcl 脚本、良好的英语基础 2、 逻辑开发人员 计算机组成原理…

关于服务器的一些知识

1. 云服务器 和 轻量应用服务器 腾讯云中的"云服务器"&#xff08;Cloud Virtual Machine, CVM&#xff09;和"轻量应用服务器"&#xff08;Lite Cloud Server&#xff09;都是提供云端计算资源的服务&#xff0c;但它们在定位、特性和使用场景上存在一些差…

LongRAG:利用长上下文大语言模型提升检索生成效果

一、前言 前面我们已经介绍了多种检索增强生成 (RAG) 技术&#xff0c;基本上在保证数据质量的前提下&#xff0c;检索增强生成&#xff08;RAG&#xff09;技术能够有效提高检索效率和质量&#xff0c;相对于大模型微调技术&#xff0c;其最大的短板还是在于有限的上下文窗口…