走进Spring Boot源码学习之路和浅谈入门
yuyutoo 2024-10-26 16:09 10 浏览 0 评论
Spring Boot浅聊入门
Spring Boot官网地址:https://spring.io/projects/spring-boot/
Spring Boot可以轻松创建独立的、基于Spring的产品级应用程序“直接运行”。
作为笔者见解,Spring Boot 不算是一个全新的框架,Spring Boot 底层还是大量依赖于Spring Framework,而Spring Framework很早以前版本就已提供基于注解、Java Config而不仅是XML配置编程;Spring Boot采用约定大于配置方式替代xml配置,是Spring Framework一个大升级版本,整合很多自动装配组件,让开发者开箱即用,接下来我们一起来聊聊Spring Boot三个重要的特性和starter开发简易剖析
Spring Boot源码编译
使用最新Releases版本v2.5.3
目前Spring boot和Spring Framework项目都是使用Gradle自动化构建工具,编译直接查看项目根目录下README.adoc文件即可,有时网络不好可以多次编译重试
零配置
Spring Web MVC简述
Spring Web Mvc官网地址** **https://docs.spring.io/spring-framework/docs/current/reference/html/web.html
Spring Web MVC是基于Servlet API的原始Web框架,从一开始就包含在Spring框架中。“Spring Web MVC”的正式名称来自其源模块的名称(Spring -webmvc),但它通常被称为“Spring MVC”
零配置实现
Spring Web MVC官网开篇最前面第一章第一小节也即是1.1 DispatcherServlet中就重点引用一下这六句代码实现原来我们Spring MVC 工程web.xml里面配置功能,仅通过下面这一小段代码就初始化了Spring根容器和Web子容器,这种方式也是官方推荐使用的
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
?
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
而我们非常熟悉的Spring Web Mvc工程web.xml配置示例注册和初始化DispatcherServlet:
<web-app>
?
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
?
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
?
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
?
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
?
</web-app>
父子容器
个人猜测Spring Boot基于内嵌容器特性会渐渐淡化父子容器的概念,最终只是一个大容器
内嵌容器
概述
Spring Boot 内嵌像Tomcat、Jetty、Undertow等实现Servlet规范的容器,如果有研究过Tomcat这只三脚猫logo中间件(后续有时间我们再分享一下Tomcat源码和原理)也知道Tomcat也是一个用Java语言编写的强大中间件,在Spring Boot通过New Tomcat方式来配置和启动web容器。
Spring Framework源码编译
使用最新Release版本v5.2.16.RELEASE
同样找到项目根目录下README.md,从githubSpring Framework源码** wiki找到编译源码说明**
实现
根据Servlet从3.0版本之后的规范,所有实现Servlet规范的Web容器都会扫描jar包下面META-INF/services下的文件,在Spring Web子项目中利用Java 的SPI机制(SPI是一种扩展机制,很多框架底层都使用或甚至重新实现自己的SPI机制,比如像Spring和Dubbo都实现自己的SPI,以后我们再找时间来讨论,暂且到这里),在根目录下META-INF/services创建一个文件名javax.servlet.ServletContainerInitializer,文件内容是实现类的org.springframework.web.SpringServletContainerInitializer全类名
SpringServletContainerInitializer实现类针对通过@HandlesTypes感兴趣注解, 将所有实现WebApplicationInitializer的实现类放到一个Set集合中,然后再逐个调用其onStartup()方法,所以这里我们接上了1.1章节官网MyWebApplicationInitializer实现WebApplicationInitializer接口这一部分
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
?
List<WebApplicationInitializer> initializers = Collections.emptyList();
?
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
?
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
?
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
?
}
接下来我们再来看看Web容器如Tomcat是如何在Spring Boot中运行的,首先我们先得知道SpringBoot启动过程,我们知道Spring Boot 应用程序都是从SpringApplication.run开始的,而run方法则始于SpringApplication类,返回ApplicationContext接口的子接口ConfigurableApplicationContext,通过refreshContext方法调用Spring Framework最大名鼎鼎的refresh方法初始化Spring容器,而refresh方法中的onRefresh是留给子类扩展使用的,而也是在这里开始初始化Web容器的
找到其实现类ServletWebServerApplicationContext,在这里createWebServer开始创建WebServer,
这里会根据我们我们pom文件starter依赖来决定创建Tomcat、Undertow、Jetty,Undertow在并发下性能明显优化另外Tomcat和Jetty,建议优先使用Undertow,但这里由于大家对于Tomcat印象比较深刻,所以我们示例还是以Tomcat为主
Spring Boot内嵌容器默认为Tomcat,想要换成Undertow也是非常容易的,只需修改spring-boot-starter-web依赖,移除tomcat的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
添加undertow依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
server:
port: 8084
http2:
enabled: true
undertow:
io-threads: 16
worker-threads: 256
buffer-size: 1024
buffers-per-region: 1024
direct-buffers: true
至此,这里我们通过ServletWebServerFactory的实现类TomcatServletWebServerFactory的getWebServer方法找到new Tomcat的源码
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
自动装配
@SpringBootApplication注解
我们知道所有运用Spring Boot应用程序在根目录的主类上都标有一个@SpringBootApplication的注解,这个就是SpringBoot自动装配实现的入口,我们接下来一步步的来分析,首先@SpringBootApplication在org.springframework.boot.autoconfigure.SpringBootApplication中,Spring Boot的自动装配也都是在org.springframework.boot.autoconfigure项目里实现的
而我们经常看到spring-boot-starter-xxx这种里面主要是定义了依赖包装成一个个启动器,实际实现还是在spring-boot-autoconfigure中,这些都是Spring Boot官方帮我们整合实现并提供开箱即可工具,此外还有一些第三方整合提供以xxx开头的xxx-spring-boot-starter比如阿里巴巴的druid-spring-boot-starter和苞米豆mybatis-plus-boot-starter等
在@SpringBootApplication注解里主要有下面这三个注解
- @SpringBootConfiguration(继承了Configuration,表示当前是注解类)
- @ComponentScan (包含扫描和排除扫描组件配置)
- @EnableAutoConfiguration (在这里开启Spring Boot的自动配置注解功能)@AutoConfigurationPackage**@Import(AutoConfigurationImportSelector.class) **
核心之核心的地方就是借助@import导入AutoConfigurationImportSelector,这个是Spring中ImportSelector接口的实现类,ImportSelect接口是org.springframework.context里的内容,在Spring Framework 3.1版本之后就有,后续我们有时间讨论Spring Framework 的时候再来详说
重要的方法也即是这个getCandidateConfigurations,调用loadFactoryNames,而loadFactoryNames调用loadSpringFactories方法,classLoader.getResources这里就是谜底揭开的时候,通过JDK classLoader加载FACTORIES_RESOURCE_LOCATION(META-INF/spring.factories)文件内容
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
?
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
?
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
?
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
?
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
通过spring-boot-autoconfigure的resource目录下META-INF目录找到spring.factories文件后我们可以看到这里面就是Spring SPI机制实现,在此定义一个规范的路径和文件名,文件内容放置了一个个Spring Boot提供自动装配的组件,而后面我们可想而知Spring Boot底层肯定还是利用反射机制将这些类放到Spring容器中管理,我们就可以通过Spring Boot通用三板斧使用步骤,第一步加坐标依赖starter,第二步启动类中开启使用注解,第三步配置配置文件参数后轻松使用工具类的功能
Starter开发简易剖析
步骤
- 建立autoconfigure工程开发自动配置的实现类xxxProperties.class 提供参数配置实现类,比如redis的host、url等,我们也可以从这里找到所有配置参数信息xxxConnectionConfiguration.class,由@Import注解导入依赖类xxxAutoConfiguration实现类,然后通过Java Config和@Bean注解方式将Java Bean注册到Spring容器里,后续使用则直接从Spring容器中拿即可项目根目录resource建立META-INF文件夹,下面建立spring.factories文件,将自动配置类的全类名方放在org.springframework.boot.autoconfigure.EnableAutoConfiguration里
- 建立spring-boot-starter-xxx工程,管理依赖,如下Redis Starter gradle配置
RedisAutoConfiguration分析
RedisAutoConfiguration实现
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
?
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
?
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
?
}
Redis Starter依赖
plugins {
id "org.springframework.boot.starter"
}
?
description = "Starter for using Redis key-value data store with Spring Data Redis and the Lettuce client"
?
dependencies {
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
api("org.springframework.data:spring-data-redis")
api("io.lettuce:lettuce-core")
}
相关推荐
- Python操作Word文档神器:python-docx库从入门到精通
-
Python操作Word文档神器:python-docx库从入门到精通动动小手,点击关注...
- Python 函数调用从入门到精通:超详细定义解析与实战指南 附案例
-
一、函数基础:定义与调用的核心逻辑定义:函数是将重复或相关的代码块封装成可复用的单元,通过函数名和参数实现特定功能。它是Python模块化编程的基础,能提高代码复用性和可读性。定义语法:...
- 等这么长时间Python背记手册终于来了,入门到精通(视频400集)
-
本文毫无套路!真诚分享!前言:无论是学习任何一门语言,基础知识一定要扎实,基础功非常的重要,找一个有丰富编程经验的老师或者师兄带着你会少走很多弯路,你的进步速度也会快很多,无论我们学习的目的是什么,...
- 图解Python编程:从入门到精通系列教程(附全套速查表)
-
引言本系列教程展开讲解Python编程语言,Python是一门开源免费、通用型的脚本编程语言,它上手简单,功能强大,它也是互联网最热门的编程语言之一。Python生态丰富,库(模块)极其丰富,这使...
- Python入门教程(非常详细)从零基础入门到精通,看完这一篇就够
-
本书是Python经典实例解析,采用基于实例的方法编写,每个实例都会解决具体的问题和难题。主要内容有:数字、字符串和元组,语句与语法,函数定义,列表、集、字典,用户输入和输出等内置数据结构,类和对象,...
- Python函数全解析:从入门到精通,一文搞定!
-
1.为什么要用函数?函数的作用:封装代码,提高复用性,减少重复,提高可读性。...
- Python中的单例模式:从入门到精通
-
Python中的单例模式:从入门到精通引言单例模式是一种常用的软件设计模式,它保证了一个类只有一个实例,并提供一个全局访问点。这种模式通常用于那些需要频繁创建和销毁的对象,比如日志对象、线程池、缓存等...
- 【Python王者归来】手把手教你,Python从入门到精通!
-
用800个程序实例、5万行代码手把手教你,Python从入门到精通!...
- Python从零基础入门到精通:一个月就够了
-
如果想从零基础到入门,能够全职学习(自学),那么一个月足够了。...
- Python 从入门到精通:一个月就够了
-
要知道,一个月是一段很长的时间。如果每天坚持用6-7小时来做一件事,你会有意想不到的收获。作为初学者,第一个月的月目标应该是这样的:熟悉基本概念(变量,条件,列表,循环,函数)练习超过30个编...
- Python零基础到精通,这8个入门技巧让你少走弯路,7天速通编程!
-
Python学习就像玩积木,从最基础的块开始,一步步搭建出复杂的作品。我记得刚开始学Python时也是一头雾水,走了不少弯路。现在回头看,其实掌握几个核心概念,就能快速入门这门编程语言。来聊聊怎么用最...
- 神仙级python入门教程(非常详细),从0到精通,从看这篇开始!
-
python入门虽然简单,很多新手依然卡在基础安装阶段,大部分教程对一些基础内容都是一带而过,好多新手朋友,对一些基础知识常常一知半解,需要在网上查询很久。...
- Python类从入门到精通,一篇就够!
-
一、Python类是什么?大家在生活中应该都见过汽车吧,每一辆真实存在、能在路上跑的汽车,都可以看作是一个“对象”。那这些汽车是怎么生产出来的呢?其实,在生产之前,汽车公司都会先设计一个详细的蓝图...
- 学习Python从入门到精通:30天足够了,这才是python基础的天花板
-
当年2w买的全套python教程用不着了,现在送给有缘人,不要钱,一个月教你从入门到精通1、本套视频共487集,本套视频共分4季...
- 30天Python 入门到精通(3天学会python)
-
以下是一个为期30天的Python入门到精通学习课程,专为零基础新手设计。课程从基础语法开始,逐步深入到面向对象编程、数据处理,最后实现运行简单的大语言模型(如基于HuggingFace...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)