Spring是一个开源框架,2003年兴起的一个轻量级的Java开发框架,作者: Rod Johnson 。
Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
Spring是如何简化Java开发的?
为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat,跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;
什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发http接口。
Spring Boot基于 Spring 开发,Spirng Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,它并不是用来替代Spring 的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。SpringBoot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数Spring Boot应用只需要很少的Spring配置。同时它集成了大量常用的第三方库配置(例如Redis、MongoDB、Jpa、RabbitMQ、Quartz等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用,
简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包, spring boot整合了所有的框架。
Spring Boot的主要优点:
一个服务一个jar,将一个项目打包成多个jar。
比如淘宝:购买东西服务在一个服务器上,数据库在另一个服务器上。
可以减少并发时间,更高效、快速,便于修改更新代码。
父依赖,管理项目的资源过滤和插件:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.3version>
<relativePath/>
parent>
启动器(spring-boot-starter-xx):
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
spring-boot-starter-xx:spring-boot的场景启动器
spring-boot-starter-web:帮助我们导入web模块正常运行所依赖的组件(tomcat)
SpringBoot将所有的功能场景都抽取出来,做成一个个start(启动器),只需要在项目中引入这些start即可,所有的依赖都会导入进来。我们要使用什么功能就导入相应场景的启动器即可
Maven执行package打包,在target目录下找到jar包,在target目录下cmd
java -jar XXXX.jar
在resource文件夹下创建banner.txt文件。注意看图标右下角是否有springboot识别到的角标标识。
____ (_) _ __ __
|_ / | | (_) __ _ \ \ / /
/ / _/ | | | / _` | \ \/\/ /
/___| |__/_ _|_|_ \__,_| \_/\_/
_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|
"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'
父依赖
pom.xml文件中,有一个父依赖(也可以说是该pom.xml文件依赖一个父项目),主要是管理项目的资源过滤和插件!
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.7version>
<relativePath/>
parent>
点进去是个名为spring-boot-starter-parent 的pom文件,发现该项目也依赖一个父项目(名为spring-boot-dependencies)
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.6.7version>
parent>
这里才是真正的管理SpringBoot应用里面所有依赖版本的位置,SpringBoot的版本控制中心
之后我们导入依赖默认是不需要写版本号的,但是如果导入的包没有在依赖管理中,就需要我们手动来进行版本的配置了
**启动器(spring-boot-starter-xx) **
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
spring-boot-starter-xx:spring-boot的场景启动器
spring-boot-starter-web:帮助我们导入web模块正常运行所依赖的组件
SpringBoot将所有的功能场景都抽取出来,做成一个个start(启动器),只需要在项目中引入这些start即可,所有的依赖都会导入进来;我们要使用什么功能就导入深夜场景的启动器即可;我们也可以自定义start启动器
默认的主启动类:XXXXApplication
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootFirstApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootFirstApplication.class, args);
}
}
@SpringBootApplication
作用:标注在哪个类上,就说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用
点进注解:@SpringBootApplication==>
//元注解
@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
作用:SpringBoot的配置类,标注在哪个类上,表示的哪个就是SpringBoot的配置类
@SpringBootConfiguration==>(进入@SpringBootConfiguration注解里面)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {}
@Configuration
这里的@Configuration,说明这相当于是一个Spring的xml配置文件
@Configuration==>(进入@Configuration注解里面)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {}
@Component
这里的@Component,说明启动类本身也是一个组件,负责启动应用
@EnableAutoConfiguration
开启自动配置功能(以前我们需要手动配置,现在SpringBoot可以帮我们自动配置)
@EnableAutoConfiguration注解“通知”SpringBoot开启自动配置功能,这样自动配置功能才能生效
@EnableAutoConfiguration==>
@AutoConfigurationPackage(自动配置包)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {}
AutoConfigurationImportSelector类
1、AutoConfigurationImportSelector类中有一个这样的方法
//获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//这里的getSpringFactoriesLoaderFactoryClass返回的就是我们最开始看的启动自动导入配置文件的注解类:@EnableAutoConfiguration
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
2、这个方法调用了SpringFactoriesLoader类的静态方法,我们进入SpringFactoriesLoader类的loadFactoryNames方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//这里它又调用loadSpringFactories方法
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
3、我们进入loadSpringFactories方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//获取classloader,我们这里得到的就是@EnableAutoConfiguration标注的类本身
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
//取获取一个资源:"META-INF/spring.factories"
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
//将读取到的资源遍历,封装成为一个Properties
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
4、发现多次出现:spring.factories
@AutoConfigurationPackage==>
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {}
Registrar.class:将主动类所在包及包下面所有子包里面的所有组件扫描到Spring容器中
根据源头打开spring.factories,看到很多自动配置的文件,这就是自动配置根源所在!
我们在上面的自动配置类里面随便打开一个,比如:WebMvcAutoConfiguration
可以看到这些都是JavaConfig配置类(就是Spring框架中,可以用Java的方式来配置Spring,代替Spring自带的application.xml配置文件)
所有,自动配置真正实现是从classpath中搜索所有的META-INF/spring.factories
配置文件,并将其中对应的 org.springframework.boot.autoconfigure
包下的配置项通过反射实例化成为标注了 @Configuration的JavaConfig形式的IOC容器配置类,然后将这些汇总成为一个实例并加载到IOC容器中。
SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
将这些值作为自动配置类导入容器,自动配置类就会生效,帮我们进行自动配置工作
整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包
中
它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件
有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作
最初以为就运行一个main方法,没想到开启了一个服务
@SpringBootApplication
public class SpringBootFirstApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootFirstApplication.class, args);
}
}
SpringApplication.run分析
**SpringApplication **
这个类主要做了一下四件事:
推断应用的类型是普通的项目还是Web项目
查找并加载所有可用初始化器 , 设置到initializers属性中
找出所有的应用程序监听器,设置到listeners属性中
推断并设置main方法的定义类,找到运行的主类
SpringApplication类的构造器:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。
配置文件:SpringBoot使用一个全局的配置文件,配置文件名称是固定的
作用:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了
注意:
比如 :name: “kuang \n shen” 输出 :kuang 换行 shen
比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen
springboot官方推荐使用yaml,优点多,比properties功能更全面
.yaml文件对空格的要求很高
当.properties和.yaml文件同时存在时,properties的优先级更高
properties文件只能key.XXX=value
,较复杂且不方便。yaml文件支持普通键值对、数组、对象等结构
yaml可以给实体类赋值
yaml支持语法结构:
#普通key-value
server:
port: 8081
#对象
student:
name: ZjiaW
age: 18
#支持行内写法
student1: {name: ZjiaW , age: 18}
#数组
animal:
- pig
- fish
- dog
#支持行内写法
animal1: [pig,fish,dog]
properties语法结构:
server.port=8084
yaml文件给实体类赋值需要使用@ConfigurationProperties(prefix = "dog")
注解
prefix前缀指明是哪个对象
lombok依赖:
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.22version>
<scope>providedscope>
dependency>
dog:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
@ConfigurationProperties(prefix = "dog")
public class Dog {
private String name;
private Integer age;
}
person:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean isHappy;
private Date birth;
private Map<String ,Object> maps;
private List<Object> lists;
private Dog dog;
}
dog:
name: 旺财
age: 3
person:
name: ZjiaW
age: 18
isHappy: false
birth: 2022/9/6
maps: {k1: v1,k2: v2}
lists: [code,dog,music]
Dog:
name: 旺财2
age: 6
@ConfigurationProperties(prefix = "person")
注解时实体类爆红提示<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
dependency>
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class FirstSpringbootApplicationTests {
@Autowired
Dog dog;
@Autowired
Person person;
@Test
void contextLoads() {
System.out.println(dog);
System.out.println(person);
}
}
person:
name: jia${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: ${person.hello:other}_旺财 # person.hello存在时显示该值,不存在显示“other”
age: 1
@PropertySource("classpath:XXXX.properties")
注解,标识用的是哪个配置资源文件@Value("${dog.name}")
dog实体类:
package com.zjw.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
//@ConfigurationProperties(prefix = "dog")
@PropertySource("classpath:application.properties")
public class Dog {
@Value("${dog.name}")
private String name;
@Value("${dog.age}")
private Integer age;
}
properties文件:
dog.name=hi
dog.age=2
使用properties很可能出现乱码问题,因idea默认properties为ASCII码
若出现乱码问题,在setting中找到 file Encoding
总结:
在字段中增加一层过滤器验证 , 可以保证数据的合法性
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
为实体类添加@VAlidated
注解
在属性上添加验证方式
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {
@Email(message = "这不是邮箱格式,请检查")
private String name;
@Max(value = 120,message = "最大不超过120")
private int age;
@NotNull(message = "是否开心不能为空")
private Boolean isHappy;
private Date birth;
private Map<String ,Object> maps;
private List<Object> lists;
private Dog dog;
}
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
file:./config/
file:./
classpath:/config/
classpath:/
file:指项目根目录下
classpath:指java/resource下
默认配置文件名为application.properties
开发环境或测试环境的配置文件名字为:在application加上-dev/-test
在默认的配置文件下指定激活哪个配置文件:spring.profiles.active=XX
application.properties:
server.port=8084
spring.profiles.active=dev
application-dev.properties:
server.port=8085
application-test.properties:
server.port=8086
yaml多环境配置通过**三个-
**来分隔不同的配置环境
spring:
profiles: 指定配置文件名字
(yaml也适用properties中使用-XX
文件名进行多环境配置)
server:
port: 8081
spring:
profiles:
active: test
---
server:
port: 8082
spring:
profiles: dev
---
server:
port: 8083
spring:
profiles: test
在SpringBoot的配置文件中,给特定功能的属性赋值来实现自定义的功能;
但是这些属性名(就是在配置过程中给出的属性名的提示)其实是来自XxProperties类中进行定义的,因此可以参照XxProperties相关类来了解相应属性名的含义
逐步进入底层源码,可以看到最后SpringFactoriesLoader 类
里面定义了一个静态常量public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
;
值得注意的是META-INF/spring.factories这个文件最后被加载并转换成了Properties类型
我们通过文件名全局搜索找到了spring.factories文件
然后随便点进一个熟悉的:HttpEncodingAutoConfiguration配置类
/部分代码
//出现这个@Configuration和@Bean标志着这个类是个配置类(JavaConfig)--学习Spring中,Java配置类代替Spring的配置文件
@Configuration(
proxyBeanMethods = false
)
//启动指定类的ConfigurationProperties功能;
//进入这个ServerProperties查看,将配置文件中对应的值和ServerProperties绑定起来;
//并把ServerProperties加入到ioc容器中
@EnableConfigurationProperties({ServerProperties.class})
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
prefix = "server.servlet.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
判断容器没有这个组件?
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.RESPONSE));
return filter;
}
总结:
一但这个配置类生效,这个配置类就会给容器中添加各种组件
这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着
配置文件能配置什么就可以参照某个功能对应的这个属性类
在进入ServerProperties这个类:
这就是自动装配的大概流程!