第一章:SpringBoot
基础入门
1.1:Spring
与SpinrBoot
-
Spring
能做什么-
Spring
的能力
-
Spring
的生态网址:
https://spring.io/projects/spring-boot
覆盖了:
Web
开发、数据访问、安全控制、分布式、消息服务、移动开发、批处理等。 -
Spring5
重大升级-
响应式编程
-
内部源码设计
基于
Java8
的一些新特性。
-
-
-
为什么用
SpringBoot
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run"。
能快速创建出生产级别的Spring
应用。-
SpringBoot
的优点Create stand-alone Spring applications
:创建独立的Spring
应用。Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
:内嵌Web
服务器。Provide opinionated 'starter' dependencies to simplify your build configuration
:自动starter
依赖,简单构建配置Automatically configure Spring and 3rd party libraries whenever possible
:自动配置Spring
以及第三方功能。Provide production-ready features such as metrics, health checks, and externalized configuration
:提供生产级别的监控、健康检查以及外部化配置。Absolutely no code generation and no requirement for XML configuration
:无代码生成、无需编写XML
。
SpringBoot
是整合Spring
技术栈的一站式框架,SpringBoot
是简化Spring
技术栈的快速开发脚手架。 -
SpringBoot
的缺点- 迭代快,需要时刻关注变化。
- 封装太深,内部原理复杂,不容易精通。
-
-
时代背景
-
微服务
James Lewis and Martin Fowler (2014)
提出微服务完整概念。https://martinfowler.com/microservices/
微服务是一种架构风格。一个应用拆分为一组小型服务。每个服务运行在自己的进程内,也就是可独立部署和升级。服务之间使用轻量级
HTTP
交互。服务围绕业务功能拆,可以由全自动部署机制独立部署,去中心化,服务自治。服务可以使用不同的语言、不同的存储技术。 -
分布式
-
分布式的困难
远程调用、服务发现、负载均衡、服务容错、配置管理、服务监控、链路追踪、日志管理、任务调度。
-
分布式的解决
SpringBoot + SpringCloud
-
-
-
如何学习
SpringBoot
-
官网文档架构
查看版本新特性:
https://github.com/spring-projects/spring-boot/wiki#release-notes
-
1.2:SpringBoot2
入门
需求:浏览器发送/hello
请求,响应Hello, Spring Boot 2
。
-
创建
maven
工程在
IDEA
创建一个新的工程boot_helloworld_01
。 -
引入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
-
创建主程序
package com.wang; // 主程序类。@SpringBootApplication:这是一个SpringBoot应用 @SpringBootApplication public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } }
-
编写业务
package com.wang.controller; @RestController public class HelloController { @RequestMapping("/hello") public String handle01() { return "Hello, Spring Boot 2!"; } }
-
测试
直接运行
mian
方法,浏览器访问http://localhost:8080/hello
-
修改配置
在
resources
文件夹下创建application.properties
文件server.port=8888
修改了上面配置,重新启动项目,
http://localhost:8080/hello
访问成功。 -
简化部署
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
重新打成一个
jar
包在cmd
窗口下也能运行。
1.3:了解自动配置原理
-
SpringBoot
特点-
依赖管理
-
父项目做依赖管理
几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制。
<!-- 依赖管理,自己工程引入的依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> </parent> <!-- 它的父项目 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.4.RELEASE</version> </parent>
-
开发导入
starter
场景启动器-
见到很多
spring-boot-starter-*
:*
就是某种场景。 -
只要引入
starter
,这个场景的所有常规需要的依赖我们都自动引入。 -
SpringBoot
所有支持的场景:https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
-
见到
*-spring-boot-starter
:第三方为我们提供的简化开发的场景启动器。 -
所有场景启动器最底层的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.3.4.RELEASE</version> <scope>compile</scope> </dependency>
-
-
无需关注版本号,自动版本仲裁
- 引入依赖默认都可以不写版本。
- 引入非版本仲裁的
jar
,要写版本号。
-
可以修改默认版本
查看
spring-boot-dependencies
里面规定当前依赖的版本用的key
。<!-- 举例:修改mysql的版本依赖 --> <properties> <mysql.version>5.1.43</mysql.version> </properties>
-
-
自动配置
-
自动配好
Tomcat
-
引入
Tomcat
依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.3.4.RELEASE</version> <scope>compile</scope> </dependency>
-
配置
Tomcat
-
-
自动配置好
SpringMVC
- 引入
SpringMVC
全套组件。 - 自动配好
SpringmVC
常用组件(功能)。
- 引入
-
自动配好
Web
常见功能SpringBoot
帮我们配置好了所有Web
开发的场景。
-
默认的包结构
-
主程序所在包及其下面的所有子包里面的组件都会被默认扫描出来。
-
无序以前的包扫描配置。
-
想要改变扫描路径:
@SpringBootApplication(scanBasePackages = "com")
或者@ComponentScan("com.wang")
指定扫描路径@SpringBootApplication(scanBasePackages = "com.wang") // 等同于 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan("com.wang")
-
-
各种配置拥有默认值
- 默认配置最终都是映射到某个类上。
- 配置文件的值最终会绑定在每个类上,这个类会在容器中创建对象。
-
按需加载所有自动配置项
- 非常多的
starter
。 - 引入了哪些场景这个场景的自动配置才会开启。
SpringBoot
所有的自动配置功能都在spring-boot-autoconfigure
包里面。
- 非常多的
-
-
-
容器功能
-
添加组件
先创建两个实体类:
User
和Pet
package com.wang.bean; // 用户 public class User { private String name; private Integer age; private Pet pet; // 省略空参、全参构造、get/set方法、toString方法 }
package com.wang.bean; // 宠物 public class Pet { private String name; // 省略空参、全参构造、get/set方法、toString方法 }
-
@Configuration
Full
模式与Lite
模式:- 配置类组件之间无依赖关系用
Lite
模式加速容器启动过程,减少判断。 - 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用
Full
模式。
// Configuration使用示例 package com.wang.config; // 配置类里面使用@Bean标注在方法给容器注册组件,默认也是单实例的。 // 配置类本身也是组件 // proxyBeanMethods = false:代理bean的方法 // Full(proxyBeanMethods = true): 【保证每个@Bean方法被调用多少次返回的组件都是单实例的】 // Lite(proxyBeanMethods = false): 【每个@Bean方法被调用多少次返回的组件都是新创建的】 // 组件依赖必须使用Full模式默认。其他默认是否Lite模式 // 告诉SpringBoot这一个配置类 == 配置文件 @Configuration(proxyBeanMethods = false) public class MyConfig { // 给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例 @Bean public User user01() { User zhangsan = new User("zhangsan", 18); // User组件依赖了Pet组件 zhangsan.setPet(tomcatPet()); return zhangsan; } @Bean("tom") public Pet tomcatPet() { return new Pet("tomcat"); } }
// Configuration测试代码 package com.wang; @SpringBootApplication public class MainApplication { public static void main(String[] args) { // 1. 返回我们IOC容器 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 2. 查看容器里面的组件 String[] names = run.getBeanDefinitionNames(); for (String name: names) { System.out.println(name); } // 3.从容器中获取组件 Pet tom01 = run.getBean("tom", Pet.class); Pet tom02 = run.getBean("tom", Pet.class); System.out.println("组件:" + (tom01 == tom02)); MyConfig bean = run.getBean(MyConfig.class); System.out.println(bean); // 4. 组件是否单例 User user1 = bean.user01(); User user2 = bean.user01(); System.out.println(user1 == user2); User user01 = run.getBean("user01", User.class); Pet tom = run.getBean("tom", Pet.class); System.out.println("用户的宠物: " + (user01.getPet() == tom)); } }
- 配置类组件之间无依赖关系用
-
@Import()
@Import({User.class, DBHelper.class})
给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名。 -
@Conditional
条件装配:满足
Conditional
指定的条件,则进行组件注入。// 条件转配示例 package com.wang.config; @Configuration // 容器中存在tom组件时,才给类中的bean进行组件注入 @ConditionalOnBean(name = "tom") public class MyConfig { @Bean public User user01() { User zhangsan = new User("zhangsan", 18); zhangsan.setPet(tomcatPet()); return zhangsan; } @Bean("tom22") public Pet tomcatPet() { return new Pet("tomcat"); } }
// 测试条件装配 package com.wang; @SpringBootApplication() public class MainApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); boolean tom = run.containsBean("tom"); System.out.println("容器中Tom组件:" + tom); boolean user01 = run.containsBean("user01"); System.out.println("容器中user01组件:" + user01); boolean tom22 = run.containsBean("tom22"); System.out.println("容器中tom22组件:" + tom22); } }
-
-
原生配置文件引入
-
@ImportResource
<!--创建beans.xml配置文件--> <bean id="haha" class="com.wang.bean.User"> <property name="name" value="zhangsan"></property> <property name="age" value="18"></property> </bean> <bean id="hehe" class="com.wang.bean.Pet"> <property name="name" value="tomcat"></property> </bean>
package com.wang.config; // 添加@ImportResource注解 @ImportResource("classpath:beans.xml") public class MyConfig { .... }
// 测试IOC容器是否存在haha和hehe组件 package com.wang; @SpringBootApplication public class MainApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); boolean haha = run.containsBean("haha"); System.out.println("haha: :" + haha); boolean hehe = run.containsBean("hehe"); System.out.println("hehe::" + hehe); } }
-
-
配置绑定
如何使用
Java
读取到properties
文件中的内容,并且把它封装到JavaBean
中,以供随时使用:# application.properties文件中 mycar.brand=BYD mycar.price=100000
-
@ConfigurationProperties
package com.wang.bean; // 只有在容器中的组件,才会拥有SpringBoot提供的强大功能 @Component @ConfigurationProperties(prefix = "mycar") public class Car { private String brand; private Integer price; // 省略get/set方法、toString方法 }
-
@EnableConfigurationProperties
+@ConfigurationProperties
package com.wang.bean; @ConfigurationProperties(prefix = "mycar") public class Car { ..... }
package com.wang.config; @Configuration // 1.开启Car配置绑定功能 // 2.把这个Car这个组件自动注册到容器中 @EnableConfigurationProperties(Car.class) public class MyConfig { ... }
-
-
-
自动配置原理入门
-
引导加载类自动配置
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { ... }
-
@SpringBootConfiguration
// 代表当前是一个配置类 @Configuration public @interface SpringBootConfiguration {}
-
@ComponentScan
// 指定扫描那些注解 @Repeatable(ComponentScans.class) public @interface ComponentScan { .... }
-
@EnableAutoConfiguration
@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... }
-
@AutoConfigurationPackage
// 利用Registrar给容器中导入一系列组件 // 将指定的一个包下的所有组件导入进来,MainApplication主程序所在的包下 @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { .... }
package org.springframework.boot.autoconfigure; public abstract class AutoConfigurationPackages { ...... /** * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing * configuration. */ static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImports(metadata)); } } ..... }
-
@Import(AutoConfigurationImportSelector.class)
// 1.利用getAutoConfigurationEntry(annotationMetadata)给容器中批量导入一些组件 // 2.调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes) // 获取到所有需要导入到容器中的配置类 package org.springframework.boot.autoconfigure; public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { ...... @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } ....... }
// 接上面的类 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; }
// 3.利用工厂加载Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) // 得到所有的组件 // 4.从META-INF/spring.factories位置来加载一个文件。 // 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件。 // spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面有META-INF/spring.factories package org.springframework.core.io.support; public final class SpringFactoriesLoader { .... // 此方法的返回值 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); 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(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } .... }
-
-
-
按需开启自动配置项
# 文件里面写死了springBoot启动就要给容器加载的所有配置类 .... # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ .... # 虽然我们127个场景的所有自动配置启动的时候默认全部加载。XxxAutoConfiguration # 按照条件装配规则(@Conditional),最终会按需配置。
-
总结
// 随便点开spring-boot-autoconfigure-2.3.4.RELEASE.jar包下XxxAutoConfiguration文件看是否配置了此组件 @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(ServerProperties.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass(CharacterEncodingFilter.class) @ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true) public class HttpEncodingAutoConfiguration { .... }
SpringBoot
先加载所有的自动配置类XxxAutoConfiguration
。- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。
XxxProperties
里面拿。XxxProperties
和配置文件进行了绑定。 - 生效的配置类就会给容器中装配很多组件。
- 只要容器中有这些组件,就相当于这些功能就有了。
- 定制化配置。
- 用户直接自己
@Bean
替换底层的组件。 - 用户去看这个组件是获取的配置文件什么值就去修改。
- 用户直接自己
XxxAutoConfiguration
——> 加载组件 ——>XxxProperties
里面拿值 ——>application.properties
修改配置 -
最佳实践
- 引入场景依赖
- 查看自动配置了哪些(选做):配置文件中
debug=true
开启自动配置报告。【Negative
(不生效)、Positive
(生效)】 - 是否需要修改:参照文档修改配置项
- 自动加入或者替换组件:
@Bean、@Component....
- 自定义器
XxxCustomizer
-