一、Maven前置知识
1、Maven中的坐标:
- 什么是坐标:用于描述仓库中资源的位置
- 坐标的组成:
<1>groupId:当前maven项目隶属的组织的名称
<2>artifactId:当前项目的名称
<3>version:当前项目版本号
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
- 坐标的作用:唯一性定位资源的位置
2、添加依赖
- 依赖的类型:
<1>直接依赖:当前项目直接依赖于一个资源
<2>间接依赖:当前项目依赖的资源又依赖于其他资源,当前项目也可以使用间接依赖的资源
<3>依赖冲突:当依赖中出现相同资源时,依赖的层次越浅依赖的优先级越高;层级相同时,配置靠前的优先级高
- 可选依赖和排除依赖
<1>可选依赖:假设A依赖了junit,如果在依赖中添加true,如果B依赖了A那B就不可以使用junit
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<optional>true</optional>
</dependency>
<2>排除依赖:如果依赖了spring-boot-starter-web,spring-boot-starter-web中依赖了spring-boot-starter-json,添加了当前项目就不会传递依赖于spring-boot-starter-json。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>
- 依赖作用范围:通过设置scope数据来设置依赖的作用范围。complie表示任何地方都可用,test只在测试代码中用(比如junit,它只用于测试,没必要在打包时将依赖也添加上去),provided主代码和测试代码中用(比如servlet-api,tomcat中有servlet-api,如果在打包中添加依赖就会产生冲突),runtime只在打包时使用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>compile</scope>
</dependency>
3、继承依赖
一个资源(项目)除了可以依赖于另一个资源(项目)外,还可以继承另一个资源(项目)。在一个资源依赖于另一个资源时,它会间接依赖于其他资源;而在一个资源继承另一个资源时,它会继承这个资源对依赖的配置。
- 父工程:对于一个工程,我们可以添加<dependencyManagement></dependencyManagement>来对依赖的版本进行管理。对于<version></version>,还可以在<properties></properties>中进行定义,然后在<dependencyManagement></dependencyManagement>中进行引用。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zyf</groupId>
<artifactId>ssm</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring.context>5.3.22</spring.context>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.context}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
- 子工程:子工程可以继承父工程(父工程可以是第三方提供的jar包),在使用依赖时,如果这个依赖在父工程中定义过,可以不添加<version></version>,会直接使用父工程的version。这样可以有效的避免依赖冲突。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zyf</groupId>
<artifactId>controller</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>com.zyf</groupId>
<artifactId>ssm</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../ssm/pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
</project>
Maven生命周期
- Clean生命周期:清理项目
1、pre-clean:执行一些清理前需要完成的工作
2、clean:清理上一次构建生成的文件
3、post-clean:执行一些清理后需要完成的工作 - Default生命周期:构建项目
1、validate:验证工程是否正确,所有需要的资源是否可用。
2、compile:编译项目的源代码。
3、test:使用合适的单元测试框架来测试已编译的源代码。这些测试不需要已打包和布署。
4、Package:把已编译的代码打包成可发布的格式,比如jar。
5、integration-test:如有需要,将包处理和发布到一个能够进行集成测试的环境。
6、verify:运行所有检查,验证包是否有效且达到质量标准。
7、install:把包安装到maven本地仓库,可以被其他工程作为依赖来使用。
8、Deploy:在集成或者发布环境下执行,将最终版本的包拷贝到远程的repository,使得其他的开发者或者工程可以共享。 - Site生命周期:建立和发布项目站点
1、pre-site:生成项目站点之前需要完成的工作
2、site:生成项目站点文档
3、post-site:生成项目站点之后需要完成的工作
4、site-deploy:将项目站点发布到服务器
二、什么是SpringBoot
SpringBoot是Spring开源组织下的一个子项目,降低了使用SpringBoot的难度,简省了Spring的配置,提供了各种启动器。
三、ben的加载方式
1、使用XML配置文件的方式加载ben
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--声明自定义bean-->
<bean id="bookService"
class="com.itheima.service.impl.BookServiceImpl"
scope="singleton"/>
<!--声明第三方开发bean-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
</beans>
使用XML文件初始化IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
2、使用注解
- 使用XML去扫描注解并加载bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.itheima"/>
</beans>
- 使用@ComponentScan注解扫描注解并加载bean
<1>先定义一个类,然后在这个类上添加@ComponentScan注解,当这个类被加载到容器时,就会去扫描这个类所在包中所有添加了@Component/@Service/@Controller/@Repository/@Configuration注解的类,并创建对应的实列放入容器中,如果这些类中有添加了@Bean注解的方法(这个类也叫做配置类),添加@Bean注解方法的返回值也将会被放入到容器中称为bean。当然,如果这个类本身也是一个配置类,那它产生的bean也会被放入到容器中。需要注意的是@ComponentScan默认只会扫描当前类所在包中的bean,如果要扫描其他包需要加basePackages属性,比如@ComponentScan(basePackages = “xxx”)。
@ComponentScan
public class TotalConfig {
@Bean
public Cat cat(){
return new Cat();
}
}
<2>使用类文件初始化IOC容器,这个类也会被实列化并添加到容器中
ApplicationContext ctx = new AnnotationConfigApplicationContext(TotalConfig.class);
<3>配置类:含有添加了@Bean注解的方法的类。当这个配置类被实例化称为bean并且被放入IOC容器时,它的被加了@Bean注解的方法的返回值也将被放入IOC容器。
public class Config {
@Bean
public Cat cat(){
return new Cat();
}
}
<4>为什么需要配置类:
在使用@Component注解时,它只能去扫描当前项目中的包,并且如果是对项目的更新,之前项目中可能使用的xml文件加载bean而没有使用注解,这就导致使用@Component无法实列化这些类并将其放入容器中成为bean。使用配置类就可以解决这个问题。
<5>为什么要在配置类上添加@Configuration或@Component
添加@Configuration或@Component注解只是为了让配置类称为一个bean,可以被加载到容器中
<6>@Configuration和@Component的区别
@Configuration注解中含有@Component注解,可以使得一个类被扫描到并且初始化为Bean。但相比于@Component注解,@Configuration多了proxyBeanMethods()方法,默认返回值为true。当proxyBeanMethods()返回值为ture时,添加了@Configuration的类并不会被实列化,被实列化的是它的代理类。当我们从容器中获取这个实列后,调用它的添加了@Bean注解的方式时,不会重新创建对象,而是从容器中去取对应的ben。如果proxyBeanMethods()返回值为false,则每次调用添加了@Bean注解的方式时都会重新创建一新的对象。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
比如对于下面这个类,它被实列化并且添加到IOC容器中时会创建一个Cat对象,这个Cat对象也会被添加到IOC容器中。如果调用Config对象的cat()方法就会去IOC容器中去取Cat对象而不是重新创建对象。但如果添加的是@Configuration(proxyBeanMethods = false)注解,每次调用cat()方法都会重新创建对象。
@Configuration
public class Config {
@Bean
public Cat cat(){
return new Cat();
}
}
- FactoryBean
<1>FactoryBean是java提供的一个接口,这个接口本质上就是一个工厂,用来创建对象,但这个工厂只能创建一个对象。这个工厂有三个方法需要实现,如下面的代码。我们实现了FactoryBean,并且设置它返回的类型是Cat()对象。
public class CatFactoryBean implements FactoryBean<Cat> {
@Override
public Cat getObject() throws Exception {
return new Cat();
}
@Override
public Class<?> getObjectType() {
return Cat.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
<2>FactoryBean的作用:有的对象的创建是复杂的,如果都写在配置类中将会使配置类变得臃肿。
<3>FactoryBean在配置类中的使用:
直接在配置类中创建对应的FactoryBean并返回即可,不过需要注意的是虽然cat()方法返回值为CatFactoryBean,但放入容器中的并不是CatFactoryBean的实列而是CatFactoryBean中getObject()方法返回的实列。
@Configuration(proxyBeanMethods = false)
public class Config {
@Bean
public CatFactoryBean cat(){
return new CatFactoryBean();
}
}
3、使用@Import注解
如下,如果在一个类上使用了@Import注解,当这个类被实例化并放入IOC容器称为一个Bean时,@Import注解中的类也会被实例化并且放入IOC容器称为一个bean。
@Component
@Import(Cat.class)
public class Config {
}
4、直接在IOC容器(仅限于AnnotationConfigApplicationContext容器)中添加Bean
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig5.class); ctx.register(Cat.class);
5、实现ImportSelector接口
当使用@Import注解导入这个类时(通过在这个类上加@Component注解让这个类成为bean没有这个效果),selectImports方法返回值中的类将会被实例化并放入IOC容器中成为bean。
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata metadata) {
return new String[]{"com.example.yuanli.example.Cat"};
}
}
6、实现ImportBeanDefinitionRegistrar接口
实现ImportBeanDefinitionRegistrar接口,实现其中的registerBeanDefinitions方法。先定义一个BeanDefinition ,然后将BeanDefinition 注册到registry中,BeanDefinition中的Class对象对应的类就会被实现化并放入IOC容器中。需要注意的是MyImportBeanDefinitionRegistrar类也需要使用@Import注解导入。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(Cat.class)
.getBeanDefinition();
registry.registerBeanDefinition("cat", beanDefinition);
}
}
7、实现BeanDefinitionRegistryPostProcessor 接口
与ImportBeanDefinitionRegistrar唯一不同的是,这里的bean会被最后创建。
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(Cat.class)
.getBeanDefinition();
registry.registerBeanDefinition("cat", beanDefinition);
}
}
三、Bean的加载控制
1、使用编码方式控制
在4、5、6、7三种方式创建bean时都可以使用编码的方式来控制Bean的加载
2、使用注解控制
用户控制加载bean的注解都以@Conditional开头,常用的有@ConditionalOnBean,@ConditionalOnClass,@ConditionalOnMissingBean,@ConditionalOnMissingClass。这些注解可以添加在类上也可以添加配置类的方法上。只有这个注解的条件满足了才能加载对应的Bean。
- @ConditionalOnClass:只有当存在对应的类时才会去加载bean。
<1>在类上加@ConditionalOnClass注解: 如下,当加实列化Config类成为Bean时,会先检查存不存在Cat类,存在则实例化Config类成为Bean。
@Configuration
@ConditionalOnClass(name = "com.example.yuanli.example.Cat") //@ConditionalOnClass(Cat.class)也可
public class Config {
}
<2>在方法上加@ConditionalOnClass注解:如下,只有在存在Cat类时才会加载Cat类对应的bean。
@Configuration
public class Config {
@Bean
@ConditionalOnClass(name = "com.example.yuanli.example.Cat")
public Cat cat(){
return new Cat();
}
}
- @ConditionalOnBean:只有存在对应的Bean时才会加载bean
<1>在类上加@ConditionalOnBean注解: 如下,当加实列化Config类成为Bean时,会先检查存不存在dog这个bean,存在则实例化Config类成为Bean。
@Configuration
@ConditionalOnBean(name = "dog")
public class Config {
}
<2>在方法上加@ConditionalOnBean注解:如下,只有在存在dog这个bean时才会加载Cat类对应的bean。
@Configuration
public class Config {
@Bean
@ConditionalOnBean(name = "dog")
public Cat cat(){
return new Cat();
}
}
- @ConditionalOnMissingBean,@ConditionalOnMissingClass与@ConditionalOnBean,@ConditionalOnClass相反,只有在没有对应的类或bean才去加载bean。
- 不扫描某个类
@ComponentScan(excludeFilters = {
@ComponentScan.Filter( type = FilterType.ANNOTATION, value = AvoidScan.class)
})
3、属性配置
将bean运行需要的资源抽取成独立的属性类,然后在这个bean中注入属性类。
- 定义属性类,并通过yml或者properties文件注入属性
<1>注入属性需要使用@ConfigurationProperties(prefix = “xx”)注解,会从yml或者properties文件中读取以xx为前缀的配置。属性类的属性可以是自定义的类,需要为属性类和属性类依赖的类提供set方法。
属性类Animal,添加@Component是为了让其成为bean,这样容器才会为其注入属性。
@Component
@Data
@ConfigurationProperties(prefix = "animal")
public class Animal {
private Cat cat;
private Mouse mouse;
}
属性类Animal依赖的类Cat和Mouse,只是一个普通的提供了set方法的类
@Data
public class Cat {
private String name;
private Integer age;
}
@Data
public class Mouse {
private String name;
private Integer age;
}
注入属性类,可以像使用普通的bean一样用@Resource等注入,也可以使用@EnableConfigurationProperties注解注入,不过需要注意是使用@EnableConfigurationProperties注解时需要提供构造函数,IOC容器会通过构造函数注入属性bean。
@Component
@EnableConfigurationProperties(Animal.class)
public class Config {
private Animal animal;
public Config(Animal animal) {
this.animal = animal;
}
public void say(){
System.out.println(animal.getCat().getName() + ":" + animal.getMouse().getName());
}
}
三、Spring的核心注解
@SpringBootApplication是SpringBoot的核心注解。
1、@SpringBootApplication的组成
@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})})
- @Target:作用目标(这个类是作用在类上,方法上还是成员变量上),ElementType.TYPE表示允许被修饰的注解作用在类、接口和枚举上。
- @Retention:注解保留多久(保留在源文件中,保留在class文件中还是保留到运行时),RetentionPolicy.RUNTIME表示保留到运行时
- @Documented:表明这个注解应该被 javadoc工具记录。
- @Inherited:添加了这个注解的类,它上面添加的注解可以被继承。
- @SpringBootConfiguration:包含了@Configuration注解,说明只是一个配置类
- @EnableAutoConfiguration:主要包含如下两个注解。
<1>@AutoConfigurationPackage:将当前类所在的包设置为需要扫描的包。
<2>@Import({AutoConfigurationImportSelector.class}):它会去扫描每个jar包(包括当前项目)下的META-INF文件夹中的spring.factories文件,这个文件中包含了这个jar中的所有配置类的类路径,IOC容器尝试创建这个配置类并将其加入到IOC容器中。下面以SpringBoot为例说明,SpringBoot把配置类的路径放在了spring-boot-autoconfigure jar包下的META-INF文件夹下的spring.factories文件中。
该文件部分内容如下,里面存放了大量的配置类的路径。
spring将尝试加载所有的配置类,但这些配置类的加载是有条件的,这里选取RedisAutoConfiguration配置类,它的部分内容如下。可以看到它加了@Configuration注解,那这个类不是应该能够被扫描到吗?为什么还要通过扫描spring.factories文件文件的方式去加载呢?其实,@ComponentScan注解只能扫描当前项目下的bean,而这些jar包是别人提供的并不能扫描到。这个配置类添加了@ConditionalOnClass注解,说明只有在添加了RedisOperations这个类(通过在pom文件中添加了对Redis的依赖后才会有RedisOperations这个类)后才能实例化这个配置类。@EnableConfigurationProperties({RedisProperties.class})这个注解用来导入配置信息。
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
}
再看一下RedisProperties类,部分代码如下。可以看到,这个类中的变量就是我们常用的对Redis的配置,同时我们也可以通过再yml文件或者properties文件中进行配置来覆盖RedisProperties类中的默认值。
@ConfigurationProperties(
prefix = "spring.redis"
)
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String username;
private String password;
private int port = 6379;
private boolean ssl;
private Duration timeout;
private Duration connectTimeout;
private String clientName;
private RedisProperties.ClientType clientType;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
}
经过上面的分析我们发现@Import({AutoConfigurationImportSelector.class})注解就是实现springboot中自动配置的关键。
- @ComponentScan:扫描添加了@Component/@Service/@Controller/@Repository/@Configuration注解的类,并创建对应的实列放入容器中
四、自动配置的原理
1、收集技术集和配置集:
Spring去收集开发者常用的技术和这些技术常用的参数的信息。由此构成了技术集(这些技术集对应这一个一个的类比如说RedisTemplate,而这些技术集由一堆配置类创建,比如上面提到的RedisAutoConfiguration类)和设置集(也就是一堆属性类,比如RedisProperties类)。
2、初始化SpringBoot基础环境:
加载用户自定义的bean和导入的其他坐标,形成初始化环境(比如用户添加了对Redis的依赖)。
3、加载技术集:
SpringBoot初始化容器,并且尝试去加载这些技术集(也就是实例化各种配置类,比如RedisAutoConfiguration类,这些配置类会创建技术集)。当然并不是所有的配置类都会被实例化,只有满足条件的配置类才会被实例化(比如对于RedisAutoConfiguration,如果用户没有添加对Redis的依赖,RedisAutoConfiguration就不会被加载)。
4、为技术集添加设置集:
对于一个技术比如Redis,它需要各种配置各种参数。springboot通过在配置类中导入属性类就可以实现对参数的配置。比如RedisAutoConfiguration类通过@Import标签导入了RedisProperties类。
5、覆盖设置集中的配置:
用户可以通过在.yml中或者.properties文件中进行配置来实现对properties类中属性的覆盖。
五、SpringBoot中的starter
1、什么是starter
SpringBoot可以实现自动配置,而实现自动配置需要依赖的项目满足下面两个条件(也就是上面介绍的自动配置的元素):
<1>要在resources目录下建立一个META-INF目录,并且META-INF目录中提供一个spring.factories文件,这个文件中提供了配置类的类路径。
<2>要提供spring.factories文件中对应的配置类(有的配置类可能还需要提供属性类)。
满足上面两个条件的项目就能够成为一个starter,springboot添加了这个starter的依赖后就可以直接使用里面的功能而无需额外的配置。
2、starter的种类
分为springboot自己提供的starter和第三方stater。
- springboot自己提供的starter
一般项目名为:spring-boot-starter-xxx,这里的xxx一般是模块的功能,比如spring-boot-starter-web。springboot提供的starter一般是对多个功能模块的组合。 - 第三方提供的starter
一般项目名为:xxx-spring-boot-starter,这里的xxx一般是模块的功能,mybatisplus-spring-boot-starter。
六、SpringBoot的启动流程
在SpringBoot程序中,在主程序中会调用SpringApplication.run(App.class)
方法,而这个方法最终会调用(new SpringApplication(primarySources)).run(args)
这行代码。new SpringApplication(primarySources)
创建了一个SpringApplication对象,然后调用其run方法。
1、初始化各种属性加载为对象
springboot会将读取环境属性(Environment),系统配置(spring.factories), 参数(Arguments、 application.properties),然后将这些信息抽象为对象。
在调用new SpringApplication(primarySources)创建一个SpringApplication对象时就会创建上面的这些属性对应的对象,SpringApplication(primarySources)会调用下面的代码。
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 = this.getBootstrapRegistryInitializersFromSpringFactories();
# 获取系统配置引导信息
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
# 获取ApplicationContextInitializer.class对应的实例
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
# 初始化监听器,对初始化过程及运行过程进行干预
this.mainApplicationClass = this.deduceMainApplicationClass();
}
2、创建Spring容器对象ConfigurableApplicationContext ,加载各种配置
SpringApplication对象的run方法就是用来创建一个Spring容器对象并且加载各种配置。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
3、SpringBoot的监听器
- 监听器的实现:
<1>实现ApplicationListener,范型指定需要监听哪类事件,然后重新onApplicationEvent方法,当监听的类型的事件发生后就会调用onApplicationEvent方法。
public class MyListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
System.out.println("=================");
}
}
<2>在项目的resources目标下创建META-INF文件夹,并在这个文件夹中创建spring.factories文件,添加如下内容。
org.springframework.context.ApplicationListener=\
com.example.demo.day1.MyListener
- 监听器的类型
<1>在应用运行但未进行任何处理时, 将发送 ApplicationStartingEvent。
<2>当Environment被使用, 且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent。
<3>在开始刷新之前, bean定义被加载之后发送 ApplicationPreparedEvent。
<4>在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent。
<5>在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求。
<6>启动时发生异常,将发送 ApplicationFailedEvent。
七、Spring的优点
1、 起步依赖(简化依赖配置)
spring简配依赖配置的两个主要途径就是提供了spring-boot-starter-parent,和各种starter(如spring-boot-starter-web等)。
<1>spring-boot-starter-parent:主要的作用就是调(tiao)包,保证条件依赖时不会冲突。
spring-boot-starter-parent项目继承自spring-boot-dependencies,而spring-boot-dependencies对各种依赖进行了调包,保证了各种依赖不会发生冲突。在使用依赖,不需要指定,会直接使用spring-boot-dependencies配置的version。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<2>各种starter:合并了常用功能需要使用到的依赖。
它整合了若干项目,在用户使用一个功能时(比如web),不需要要再配置各种依赖(比如spring-context,spring-mvc),直接依赖(spring-boot-starter-web)就可。
2、自动配置(简化常用工程相关配置)
3、辅助配置(内置服务器)
sprinboot将一些常用的服务器抽象为对象(比如tomcat),并将这些对象交由IOC容器管理,需要访问服务器时就直接执行这些对象的方法。修改服务器的端口通过在yml中添加如下配置实现。
server:
port: 8080