大家好,又见面了,我是全栈君。
怕什么真理无穷
进一步有近一步的欢喜
说明
本文是Spring Boot核心编程思想记录的笔记,书籍地址:Spring Boot编程思想(核心篇):
本书已经简单读过一遍,在第一遍读的时候发现里面有些内容没太理解,现在在读下,这次读的过程中对之前的内容又有了许多新的理解和收获,现在整理记录下来,方便后续的回顾。
这篇文档会记录这本我的一些读书的思考,内容可能比较多,我也会根据理解去扩展一些自己的东西,加强自己对技术理解以及应用。
在开头在叨叨一下,技术书籍的评论或评分有时候也就是简单参考下,因为不同的人对书籍内容的理解是不同的。莎士比亚也说过:”一千个观众眼中有一千个哈姆雷特”。我是觉得一本书如果你能从中有些许的收获和思考,那都是有价值的,有时候可以忽略网上的评论或者评价。
PS:本文有大部分内容摘抄自书籍内容,如果内容前后不是很通顺,请阅读书籍原文,谢谢。
Spring Boot 核心编程思想
历史乃论述过去,绝不等同于过去
这本书的议题 以Spring Boot为核心,发散 Spring技术栈、JSR及Java。 ① 以全局视角,了解技术变迁的历程 ② 多方比较,理解特性原理 ③ 整合标准规范,掌握设计的哲学
Spring Boot 易学难精,它的核心是Spring Framework ,要理解Spirng Framework 又取决于对JSR规范及Java的熟悉度。
一:总览Spring Boot
第1章 初览Spring Boot
Spring Framework 时代
初览就是从Spring 开始,因为SpringBoot是在Spring基础上的封装。Spring Framework有两个核心技术:Ioc(Inversion of Control,控制反转)和DI(Dependency Inject ,依赖注入)。
Spring Framework 其实也是一种重复发明的轮子。在Java EE体系 Ioc 实现是JNDI(Java Naming and Directory Interface,Java命名和目录接口),DI是EJB的容器注入。
tips:谈谈对Spring IOC的理解-孤傲苍狼:https://www.cnblogs.com/xdp-gacl/p/4249939.html
Spring Boot简介
目的和意义:Build Anything。 两点: 1、Spring Boot 为快速启动且最小化配置的Spring应用设计
Spring Boot 基本可以不用配置就启动一个Spring应用,我们传统方式要搭建一个SpringMVC项目需要进行大量的xml配置
2、Spring Boot 具有一套固化的视图,该视图用于构建生产级别的应用
我的理解是通过maven 管理 Starter,将Spring Boot平台依赖的第三方类库进行固化,减少管理它们的烦恼。 比如:引用
Spring Boot的特性
六点:
- 创建独立的Spring应用
独立是怎么理解,独立是相对于不独立,不独立就是需要依赖第三方的容器,Spring Boot 内嵌容器,不需要重新进行部署,所以可以称为独立。
- 嵌入Web容器,Tomcat 、Jetty 、Undertow等
- 固化的“Starter”依赖,简化构建
- 自动装配,条件满足自动装配Spring或第三方类库
- 提供一些运维的特性-外部化配置,endpoint ,如指标信息,健康检查,也可以自定义
- 无代码生成,并且不需要xml。也可以引入xml,兼容旧项目
准备环境
JDK8 maven3 Idea Eclipse等
第2章 理解独立的Spring应用
特性中:创建独立的Spring应用 问:1、为什么要独立的应用?2、 为什么是Spring应用,而非SpringBoot应用?
答:1、独立的应用理解,Spring Boot 通过 Starter 直接或者间接引入依赖,然后使用自动装配,在结合自身的生命周期以及Spring Framework的生命周期,创建并启动嵌入式的Web容器。
多数Spring Boot应用场景中,程序用SpringApplication API引导应用。 应用分为:
- Web应用
- 传统Servlet
- Spring Web MVC
- Reactive Web (Spring boot 2.x -> Spring 5.0 WebFlux)
- 非Web应用(服务提供、调度任务、消息处理等场景)
即:Spring Boot无须在像传统的Java EE应用那样,将文件打包成WAR文件或者EAR文件,并部署到JavaEE容器中运行。 也就是Spring Boot应用采用嵌入容器,独立于外部的容器,对应用的生命周期拥有完全自主的控制。
说明:
1、Spring Boot也支持传统的Web部署方式,这种方式属于一种兼容或过渡的手段。 2、误区 :Spring Boot嵌入式Web容器启动时间少于传统的Servlet容器,实际没有证据证明。 正确理解:Spring boot方便快捷的启动方式(启动方式不是启动时间),提升开发和部署效率。
答:2、Spring Boot 嵌入式容器启动后,嵌入式容器成为应用的一部分,也属于Spring 应用上下文中的组件Beans,这些组件均由自动装配特性组装成Spring Bean定义(BeanDefinition)。随Spring应用上下文启动而注册并初始化。所以是Spring应用,也称为Spring Boot应用。
Tips:在传统的Spring应用中,外置容器通过启动脚本将其引导,随其生命周期回调执行Spring上下文的初始化。
如 Spring Web中的 ContextLoaderListener
, 利用javax.servlet.ServletContext生命周期构建 Web ROOT Spring 应用上下文。简单源码如下:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
// 事件回调 初始化webApplicationCotext
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
// ServletContextListener# javax.servlet.ServletContextEvent 是容器中的事件
public interface ServletContextListener extends EventListener {
default void contextInitialized(ServletContextEvent sce) {
}
default void contextDestroyed(ServletContextEvent sce) {
}
}
附 : Spring ContextLoaderListener解析
创建Spring 应用
命令行的方式创建-实际不使用,仅供学习
代码语言:javascript复制mvn archetype:generate -DgroupId=xx-ss-springhboot -DartifactId=xxx-springboot-demo -Dversion=1.0-SNAPSHOT -DinteractiveMode=fase
- mvn : Maven命令
- archetype :插件名称
- archetype:generate 插件目标
- -D参数
说明:在Java启动命令中,通过-D命令行参数设置java 的系统属性:System.getProperties()。在Maven插件也可以通过此方式获取所需的参数。
- interactiveMode:交互方式参数(设置为false,静默方式)
此方式创建的需要在Maven中添加 : 1、父pom 和 starter 等 spring-boot-starter-parent pom spring-boot-starter-web jar 2、创建启动类,ApplicationMain
图形化界面创建
三种方式: 1、使用 Spring Boot官方提供的 在线地址:http://start.spring.io/ 2、使用IDEA 创建Spring Boot应用 3、使用 Aliyun Java Initalizr : https://start.aliyun.com/
注:如果要构建Spring Boot应用可执行的JAR,则需要添加 spring-boot-maven-plugin
插件配置到 pom 文件中。图形化创建都会默认添加,使用命令行方式需要手动添加插件配置。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
运行Spring Boot项目
执行方式
可执行JAR的资源结构
主要关注生产的: xxx.jar 和 xxx.jar.original 文件。xxx.jar 文件大小 大于 xxx.jar.original。 结构参看 脑图。
FAT Jar 和 FAT War 执行模块-Spring-boot-loader
问:为何 java -jar命令能够执行FAT Jar 文件呢?
答:java -jar 这个命令是Java 官方提供的,改命令引导的是标准可执行的JAR文件,根据Java官方文档规定:
java -jar 命令引导的具体启动类必须配置在MANIFEST.MF 资源的Main-Class属性中。 https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
[By default, the first argument that is not an option of the java
command is the fully qualified name of the class to be called. If the -jar
option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class
manifest header in its source code.]()
Spring Boot打包好的文件中,在 META-INF/MANIFEST.MF
文件中如下内容:
Manifest-Version: 1.0
Implementation-Title: springboot2-core-ch02
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: org.learn.springboot2corech02.Springboot2CoreCh02Applicat
ion
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.1.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher
Main-Class: org.springframework.boot.loader.JarLauncher ,发射器,启动类。(WAR的启动类:WarLauncher )
引导类 Start-Class
也在此文件中配置。
注:org.springframework.boot.loader.JarLauncher
并非项目的文件,由Spring-boot-maven-plugin插件在repackage追加进去的。对应的依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<!--provided表明该包只在编译和测试的时候用,不会随项目发布-->
<scope>provided</scope>
</dependency>
流程: 执行java -jar 后 启动类启动-Main-Class,然后启动类在启动后会读取 Start-Class 属性,并通过反射的方式将引导类中 main方法进行启动,从而启动Spring boot应用。(启动类和引导类处于同一进程中,并在启动前准备好Classpath)
小技巧:通过Class 名称搜索Maven中心仓库,查找对应的GAV信息。
1、 访问 https://search.maven.org/ 2、单击 “Class Search”,https://search.maven.org/classic/ 在跳转后的新的页面 Classname 输入 类的全类名。进行查找即可。
JarLauncher的实现原理
原理:
org.springframework.boot.loader.JarLauncher
是Spring boot封装的一个类,具体看源码分析。
public class JarLauncher extends ExecutableArchiveLauncher { static final String BOOT_INF_CLASSES = "BOOT-INF/classes/"; static final String BOOT_INF_LIB = "BOOT-INF/lib/"; public JarLauncher() { } protected JarLauncher(Archive archive) { super(archive); } protected boolean isNestedArchive(Entry entry) { return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/"); } // 执行java -jar的时候,将调用main方法,实际是调用JarLauncher#launch(args) public static void main(String[] args) throws Exception { (new JarLauncher()).launch(args); }}
下面执行 launch方法:
代码语言:javascript复制// 启动应用程序。 此方法是初始入口点应该由一个子类被称为public static void main(String[] args)方法。
protected void launch(String[] args) throws Exception {
// 1、注册一个'java.protocol.handler.pkgs' 属性,并关联 URLStreamHandler 处理jar URLs
JarFile.registerUrlProtocolHandler();
// 2、为指定的归档文件创建类装入器。
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// 3、 启动
launch(args, getMainClass(), classLoader);
}
此处URL关联的协议内容,可以翻看书籍阅读。下面大致总结: 1、URL关联的协议protocol 对应一种UrlStreamHandler的实现,在JDK中默认实现有 如HTTP、JAR等协议,如果要扩展协议,则必须继承UrlStreamHandler类,通常是配置 Java的系统属性 java.protocol.handler.pkgs ,追加多个package用 “|” 分隔。
代码语言:javascript复制public static void registerUrlProtocolHandler() {
String handlers = System.getProperty(PROTOCOL_HANDLER, "");
System.setProperty(PROTOCOL_HANDLER,
("".equals(handlers) ? HANDLERS_PACKAGE : handlers "|" HANDLERS_PACKAGE));
resetCachedUrlHandlers();
}
Spring boot将 org.springframework.boot.loader
将 package 进行 追加,也就是在此包下有对应的Handler 类,
Spring boot覆盖了JDK内建Jar的协议,如何覆盖在此,URL#getURLStreamHandler 先读取 java.protocol.handler.pkgs 是否存在,不存在则取默认JDK的实现,在Spring boot因为上面追加了org.springframework.boot.loader ,就不取默认的handler了。
代码语言:javascript复制static URLStreamHandler getURLStreamHandler(String protocol) {
URLStreamHandler handler = handlers.get(protocol);
if (handler == null) {
// 省略....
}
return handler;
}
其实这里代码还不是完全能够理解。
那么Spring Boot为何要覆盖默认JDK 的读取jar 的Handler呢?
Spring boot 的FAT Jar是一个独立的归档文件,除了包含传统的 Java Jar资源外,还有依赖的JAR文件, 被java -jar 引导时,内部依赖的JAR 文件无法被JDK内建的jar handler 实现 当作classPath,故替换。
如果不使用Spring Boot ,要启动传统的 jar文件,如果jar文件依赖第三方的类库的话,启动命令 如下:
代码语言:javascript复制-- java 命令
java -cp ".:./*:lib/*" com.test.Main
#-cp 和 -classpath 一样,是指定类运行所依赖其他类的路径,通常是类库,jar包之类,需要全路径到jar包,window上分号“;”
#分隔,linux上是分号“:”分隔。不支持通配符,需要列出所有jar包,用一点“.”代表当前路径。
#-cp 参数后面是类路径,是指定给解释器到哪里找到你的.class文件
-jar参数运行应用时,设置classpath的方法
附:自己的一些理解实践。
示例1:MANIFEST.MF 中没有mian方法
代码语言:javascript复制 // MANIFEST.MF
Manifest-Version: 1.0
Implementation-Title: demoB
Implementation-Version: 1.0-SNAPSHOT
Build-Jdk-Spec: 1.8
Created-By: Maven Archiver 3.4.0
// 如何jar中没有main类
F:My_WorkSpacespringbootspringboot-core2.0-thinkingdemoBtarget>java -jar demoB-1.0-SNAPSHOT.jar
demoB-1.0-SNAPSHOT.jar中没有主清单属性
示例2:添加一个main方法,编译打包
代码语言:javascript复制public class DemoBAppMain {
public static void main(String[] args) {
System.out.println("hello world");
}
}
MANIFEST.MF 中依然不会出现Main-Class属性
示例3:使用插件,对示例2的代码进行打包。 Maven 生成打包可执行jar包
配置maven插件,然后用插件打包,执行 命令即可,启动打印 hello world
.
2、获取类加载器,getClassPathArchives 根据名称前缀获取,如果匹配到 就是JarFileArchive。
代码语言:javascript复制static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
3、org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader)方法
代码语言:javascript复制// org.springframework.boot.loader.ExecutableArchiveLauncher#getMainClass
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " this);
}
return mainClass;
}
代码语言:javascript复制// 注意 :mainClass 是 getMainClass()获取的值,即 Manifest#Start-Class
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
// 设置类加载器到当前线程中
Thread.currentThread().setContextClassLoader(classLoader);
// 创建 MainMethodRunner,并执行 run方法
createMainMethodRunner(mainClass, args, classLoader).run();
}
// mainClassName 其实是 Start-Class ,这里使用反射的方式 执行 Start-Class 属性中类的 main方法
public void run() throws Exception {
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
// 如果底层方法是静态的,则指定的obj参数将被忽略。 它可能为null。 具体Invoke方法可看jdk文档。
}
我们平时在IDEA直接执行 Start-Class 引导类,其实是跳过了 JarLauncher的执行过程。
WarLauncher的实现原理
war 和Jar 差异很小,主要区别在于项目文件和JAR Class Path路径不同,具体可以 修改pom进行打包,然后解压进行对应差异。
打包war文件是一种兼容措施,既能 被WarLauncher 启动,又能兼容Servlet容器环境。(WarLauncher 也是通过 java -jar 引导)。
也就是JarLauncher 和 WarLauncher 本质上 无差别,建议 Spring boot应用使用非传统Web部署时,尽可能使用JAR归档的方式。
第3章 理解固化Maven依赖
理解 spring-boot-starter-parent pom 和 spring-boot-dependencies
固化的Maven依赖,实际上是 在Springboot的特性中:固化的“Starter”依赖,简化构建 。 ** Spring boot 采用Maven来进行固化处理,只需理解 spring-boot-starter-parent pom 和 spring-boot-dependencies
代码语言:javascript复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!--<version>2.2.0.RELEASE</version>-->
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
代码语言:javascript复制<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
层级: spring-boot-dependencies 父 :定义了版本号
- spring-boot-starter-parent 子
可以单独引入spring-boot-dependencies 作为 项目的父 pom。但是有几点是需要注意的(和spring-boot-starter-parent区别,实际上区别是因为spring-boot-starter-parent已经实现了)。
第一:可能存在 maven-war-plugin 插件版本不一致问题。在Spring boot2.0 – 版本前
maven-war-plugin2.2 中,打包规则是必须存在Web应用部署描述文件WEB-INF/web.xml ,而3.1.0版本调整该默认行为
第二:引入 spring-boot-maven-plugin 插件时,需要配置 repackage元素,否则不会添加Spring boot的引导依赖。(repackage在 spring-boot-starter-parent 中默认添加)
第三:根据习惯,通常不会将 spring-boot-dependencies 作为Maven项目的
总结 :Spring boot 利用 Maven的依赖管理特性,进而固化其Maven依赖,固化的“Starter”依赖,简化构建 特性 ,并非Spring Boot专属,但是Spring 技术栈却将其利用的相当充分。 ** **
小技巧:利用 Maven Dependency 插件分析依赖树结构
代码语言:javascript复制mvn dependency:tree -Dincludes=*:spring-boot-starter-tomcat
F:My_WorkSpacespringbootspringboot-core2.0-thinkingspringboot2-core-ch02>mvn dependency:tree -Dincludes=*:spring-boot-starter-tomcat
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building springboot2-core-ch02 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:3.1.1:tree (default-cli) @ springboot2-core-ch02 ---
[INFO] org.learn:springboot2-core-ch02:jar:0.0.1-SNAPSHOT
[INFO] - org.springframework.boot:spring-boot-starter-web:jar:2.2.1.RELEASE:compile
[INFO] - org.springframework.boot:spring-boot-starter-tomcat:jar:2.2.1.RELEASE:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.784 s
[INFO] Finished at: 2020-04-27T23:50:43 08:00
[INFO] Final Memory: 28M/307M
[INFO] ------------------------------------------------------------------------
❓︎思考:为什么 当前Spring boot仅仅依赖 spring-boot-starter-tomcat 就能引导 Tomcat 容器,并且该 容器嵌入当前应用,不需要预安装?
第4章 理解嵌入式Web容器
首先理解嵌入容器,基本上大一点的Web容器,自身都提供了嵌入式容器的支持。然后大致就能明白SpingBoot的嵌入式容器,Spring Boot对嵌入式容器进行了封装。
Servlet规范和实现三种容器版本的对应关系: Servlet4.0规范 :tomcat 9.x Jetty9.x Undertow2.x Servlet3.1规范:tomcat 8.x Jetty8.x Undertow1.x Servlet3.0规范 :tomcat 7.x Jetty7.x N/A Servlet2.5规范 :tomcat 6.x Jetty6.x N/A
热门的Web容器实现均支持嵌入式容器方式,Spring Boot并非独创,只是技术的整合创新。 **
嵌入式Servlet Web容器
Spring Boot支持三种:tomcat Jetty Undertow。
- 2.x 对应Servlet3.1规范,JDK 1.8
- 1.x 对应Servlet 3.0 规范,JDK1.6
这里需要理解 Tomcat 嵌入式容器,比如之前的工程中 1、在maven 加入tomcat的插件,不用外置Tomca就可以在工程中启动项目。 2、但是打的包依然是要放入外在Tomcat容器中,也可以使用Tomcat插件配置打包,打包后用java -jar也可以运行
Tomcat插件演示
官方最高支持 tomcat7 ,tomcat8 社区维护。具体可查询网上博文。 1、导入插件
代码语言:javascript复制<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>8088</port>
<path>/</path>
</configuration>
<executions>
<execution>
<id>tomcat-run</id>
<goals>
<goal>exec-war-only</goal>
</goals>
<phase>package</phase>
<configuration>
<!-- ServletContext path -->
<path>/</path>
</configuration>
</execution>
</executions>
</plugin>
2、在maven 运行:tomcat7:run , tomcat8后运行 tomcat:run 即可。
image.png 3、打包 mvn package ,通过 java -jar 运行,不用外置容器
image.png 通过对应的地址即可访问服务。对比 tomcat7 和 Extract 文件目录。
image.png
Tomcat 插件插件生成jar包 和Spring boot 生成jar的区别
1、Tomcat maven插件,本质上还是传统的Tomcat部署,先将WEB应用打包为ROOT.war ,在启动的时候在解压到webapps目录下面;Spring Boot 2.0 的实现,利用嵌入式Tomcat API构建 为 TomcatWebServer Bean,由Sping应用上下文将其引导,嵌入式tomcat组件(Context、Connector)的运行,以及ClassLoader的装载均由Spring Boot框架代码实现。
2、Tomcat Maven 插件打包的Jar或者War 是非 FAT模式。简单说就是存在压缩的情况。Spring Boot maven 插件 采用零压缩模式。 零压缩相当于 :jar -0 参数。
总结:传统的Servlet容器是将压缩的WAR文件解压到对应的目录,然后在加载该目录的资源。 Spring Boot 可执行的 WAR文件在不解压当前文件的前提下依然可以读取其中的资源。
这也是就Spring boot loader 为何要覆盖 内建JAR 协议的URLStreamhandler 的原因
Spring Boot中使用tomcat
默认加入 spring-boot-starter-web 就会引入 tomcat。 如果要切换 Jetty 或者Undertow ,先tomcat然后在引入对应的容器jar即可。
嵌入式Reactive Web容器
默认如果 同时存在 starter-web 和 starter-webflux ,webflux 会被忽略。
理解WebServerInitializedEvent
Web服务器已初始化事件
通过监听此事件,可以获取WebServer 以及端口。 WebServerInitializedEvent
- ReactiveWebServerInitializedEvent
- ServletWebServerInitializedEvent
有两个子类,一个监听ServletWeb ,一个是ReactiveWeb。WebServerInitializedEvent包括这两种。(本质Spring的事件监听) 通过 org.springframework.boot.web.context.WebServerInitializedEvent#getWebServer()获取WebServer。
image.png
代码语言:javascript复制 @Bean
public ApplicationRunner runner(WebServerApplicationContext context){
return args -> {
System.out.println("当前的WebServer : " context.getWebServer().getClass().getName());
};
}
/**
* 使用监听器 使代码更健壮,支持web和非web方式
* */
@EventListener(WebServerInitializedEvent.class)
public void onWebServerReady(WebServerInitializedEvent event) {
System.out.println("当前监听的WebServer :" event.getWebServer().getClass().getName());
}
_
第5章 理解自动装配
要理解 自动装配,先理解一句话:没有无缘无故的爱,也没有无缘无故的恨 Spring boot 要不会无缘无故的给我们加载配置类。
在使用Springboot的时候,当我们将 “starter”添加到应用Class path 时,其关联的特性随应用启动而自动装载,这是Spring boot的亮点, 它的原理是什么呢?
原理简述:Spring Boot 有一个Spring boot autoconfigure Jar里面配置了大量自动装配的配置类,如JDBC 、cache、AOP等,这些 配置类均在 spring.factories 中配置,以
org.springframework.boot.autoconfigure.EnableAutoConfiguration
为key 保存,在Spring boot 启动的时候加载,在结合@Conditional 相关注解进行判断,最终将需要的配置类加载,并激活默认配置信息。
自动装配的前提
1、将需要的jar添加到应用中 2、激活自动装配注解 @EnableAutoConfigure/ @SpringBootApplition 标注在 @Configution 的类上
Spring 中 @Configution 类被扫描的三种方式:
- ClassPathApplicationContext ,配置xml
- AnnotationConfigurationApplicationContext
- @Import 注解
- @ComponentScan注解
理解 @SpringBootApplication 注解语义
@SpringBootApplication 组合注解
代码语言:javascript复制@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}
- @SpringBootConfiguration : Spring boot – **@since **1.4.0 : 标记为配置类
- @Configuration
-
- @Component
- @EnableAutoConfiguration :Spring boot 注解。激活自动装配
其中核心注解:@AutoConfigurationPackage 和 @Import({AutoConfigurationImportSelector.class}) 后面详细介绍
- @ComponentScan :spring的注解 。组件扫描,激活@Compnent注解的组件
思考:Spring 的 @ComponentScan注解是如何识别Spring boot 的@SpringBootConfiguration 注解?
这里就是要理解 @Component 的 “派生性”,后续会进行总结,简单的说,就是@ComponentScan 能够扫描到 注解了 @Component的组件,以及其派生(继承)此注解的注解的组件。
@SpringBootApplication 属性别名
代码语言:javascript复制属性均标注了 AliasFor 注解
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
理解 @AliasFor Spring 注解,**@since **4.2
{ **@code *@AliasFor} is an annotation that is used to declare aliases for annotation attributes.
能够将一个或多个注解的属性“别名” 注解到某个注解中。
如:
代码语言:javascript复制@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
org.springframework.boot.autoconfigure.SpringBootApplication#scanBasePackages
具有 ComponentScan#basePackages
的能力。
提示: 1、正常情况下,这个注解@SpringBootApplication 会标注在启动引导类上,但是此注解并非限制只能标注在引导类。 2、@SpringBootApplication 和 @EnableAutoConfiguration 都能激活自动装配,但是对于被标注的类的Bean类型存在差异; @SpringBootApplication “继承”@Configuration 与CGLIB 提升特性。 @EnableAutoConfiguration无。
理解自动装配机制
首先理解条件注解@Conditonal** **及其相关注解 如 @ConditionalOnClass @ConditionalOnMissingBean等,结合@Configuration 使用。
创建自动装配类三步走: 第一:创建配置类(WebConfiguration),标注 @Configuration,实现对应装配逻辑 第二:创建自动装配类 XXXAutoConfiguration(WebAutoConfiguration),标注 @Configuration 和 @Import(WebConfiguration.class) 第三:在项目下 新建 src/main/resources/META-INF/spring.factories ,添加自动配置类
代码语言:javascript复制# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.learn.springboot2corech02.config.WebAutoConfiguration
这种方式中Spring boot中不需要配置xml,完全的注解驱动开发,这样解析注解 所带来的时间成本 直接影响了应用的启动速度。Spring Framework 5.0 引入 @Index 注解。增加索引,减少运行时候的性能消耗。自己写的一篇博文:SpringFramework5.0 @Indexed注解 简单解析
第6章 理解Production-Ready特性
1、为生产准备的特性 、立足于DevOps
- 指标监控
- 健康检查
- 外部化配置
2、Spring Boot Actuator ,监视和管理Spring应用 ,通过HTTP或者JMX进行交互,暴露一些endpoints,也可以自定义endpoint。
- beans : 当前Spring 应用 上下文的SpringBean 完整列表
- Conditions :显示当前应用 所有的配置类和自动装配类的条件评估结果
- env :暴露 Spring ConfigurableEnvironment 中的PropertySource 属性
- health (默认) :应用的健康信息
- info(默认) :显示任意应用的信息
代码语言:javascript复制通过 management.endpoints.web.exposure.include=info,health,shutdown,loggers — 暴露shutdown端点,可以进行优雅关闭服务 management.endpoint.shutdown.enabled=true
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
自定义Endpoints方法:Spring boot 2.x
代码语言:javascript复制@Endpoint(id="dufy")
@Configuration
public class MyEndPointConfig {
@ReadOperation(produces = MediaType.APPLICATION_JSON_VALUE)
public Object getMyEndPoint(){
Map map = new HashMap<>();
map.put("code", 0);
map.put("msg", "success");
return map;
}
}
// 注意 :management.endpoints.web.exposure.include= 配置
- @EndPoint中的id不能使用驼峰法,需要以-分割。
- @Spring Boot会去扫描
@EndPoint
注解下的@ReadOperation
,@WriteOperation
,@DeleteOperation
注解,分别对应生成Get/Post/Delete的Mapping。注解中有个produces参数,可以指定media type, 如:application/json等。
3、理解“外部化配置” 外部化配置的顺序。 读取外部化配置方式:
- Bean @Value
- Spring environment 读取
- @ConfigurationProperties 绑定到结构化对象
4、理解 “规约大于配置”
5、Spring Boot 做为微服务中间件,Spring Framework 是Spring Boot 的“基础设施”,Spring boot的基本特性均来自Spring Framework。 Spring Boot作为Spring cloud 基础设施。在Spring Cloud中,致力于为开发人员提供快速构建通用的分布式系统,特性:
- 分布式配置
- 服务注册和发现
- 路由
- 服务调用
- 负载均衡
- 熔断机制
- 分布式消息
- …. 最后在说一下,这是第一章的内容总结和整理,如果对上述内容感兴趣,可以去看看书籍,谢谢你的阅读。
See you next good day
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/120956.html原文链接:https://javaforall.cn