• SpringBoot原理


    一、Maven前置知识

    1、Maven中的坐标:

    • 什么是坐标:用于描述仓库中资源的位置
    • 坐标的组成:
      <1>groupId:当前maven项目隶属的组织的名称
      <2>artifactId:当前项目的名称
      <3>version:当前项目版本号
    <groupId>com.github.whvcsegroupId>
    <artifactId>easy-captchaartifactId>
    <version>1.6.2version>
    
    • 1
    • 2
    • 3
    • 坐标的作用:唯一性定位资源的位置

    2、添加依赖

    • 依赖的类型:
      <1>直接依赖:当前项目直接依赖于一个资源
      <2>间接依赖:当前项目依赖的资源又依赖于其他资源,当前项目也可以使用间接依赖的资源
      <3>依赖冲突:当依赖中出现相同资源时,依赖的层次越浅依赖的优先级越高;层级相同时,配置靠前的优先级高
      在这里插入图片描述
    • 可选依赖和排除依赖
      <1>可选依赖:假设A依赖了junit,如果在依赖中添加true,如果B依赖了A那B就不可以使用junit
    <dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.12version>
    <optional>trueoptional>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    <2>排除依赖:如果依赖了spring-boot-starter-web,spring-boot-starter-web中依赖了spring-boot-starter-json,添加了当前项目就不会传递依赖于spring-boot-starter-json。

    <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
    <exclusions>
    <exclusion>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-jsonartifactId>
    exclusion>
    exclusions>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 依赖作用范围:通过设置scope数据来设置依赖的作用范围。complie表示任何地方都可用,test只在测试代码中用(比如junit,它只用于测试,没必要在打包时将依赖也添加上去),provided主代码和测试代码中用(比如servlet-api,tomcat中有servlet-api,如果在打包中添加依赖就会产生冲突),runtime只在打包时使用。
    <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
    <scope>compilescope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    3、继承依赖

    一个资源(项目)除了可以依赖于另一个资源(项目)外,还可以继承另一个资源(项目)。在一个资源依赖于另一个资源时,它会间接依赖于其他资源;而在一个资源继承另一个资源时,它会继承这个资源对依赖的配置。

    • 父工程:对于一个工程,我们可以添加来对依赖的版本进行管理。对于,还可以在中进行定义,然后在中进行引用。
    
    <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.0modelVersion>
    
        <groupId>com.zyfgroupId>
        <artifactId>ssmartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <properties>
            <spring.context>5.3.22spring.context>
        properties>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframeworkgroupId>
                    <artifactId>spring-contextartifactId>
                    <version>${spring.context}version>
                dependency>
            dependencies>
        dependencyManagement>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 子工程:子工程可以继承父工程(父工程可以是第三方提供的jar包),在使用依赖时,如果这个依赖在父工程中定义过,可以不添加,会直接使用父工程的version。这样可以有效的避免依赖冲突。
    
    <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.0modelVersion>
    
        <groupId>com.zyfgroupId>
        <artifactId>controllerartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <parent>
            <groupId>com.zyfgroupId>
            <artifactId>ssmartifactId>
            <version>1.0-SNAPSHOTversion>
            <relativePath>../ssm/pom.xmlrelativePath>
        parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-contextartifactId>
            dependency>
        dependencies>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    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

    
    <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 id="bookService"
    class="com.itheima.service.impl.BookServiceImpl"
    scope="singleton"/>
    
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    使用XML文件初始化IOC容器

    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    
    • 1

    2、使用注解

    • 使用XML去扫描注解并加载bean
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 使用@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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    <2>使用类文件初始化IOC容器,这个类也会被实列化并添加到容器中

    ApplicationContext ctx = new AnnotationConfigApplicationContext(TotalConfig.class);
    
    • 1

    <3>配置类:含有添加了@Bean注解的方法的类。当这个配置类被实例化称为bean并且被放入IOC容器时,它的被加了@Bean注解的方法的返回值也将被放入IOC容器。

    public class Config {
        @Bean
        public Cat cat(){
            return new Cat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    <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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    比如对于下面这个类,它被实列化并且添加到IOC容器中时会创建一个Cat对象,这个Cat对象也会被添加到IOC容器中。如果调用Config对象的cat()方法就会去IOC容器中去取Cat对象而不是重新创建对象。但如果添加的是@Configuration(proxyBeanMethods = false)注解,每次调用cat()方法都会重新创建对象。

    @Configuration
    public class Config {
        @Bean
        public Cat cat(){
            return new Cat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    <2>FactoryBean的作用:有的对象的创建是复杂的,如果都写在配置类中将会使配置类变得臃肿。
    <3>FactoryBean在配置类中的使用:
    直接在配置类中创建对应的FactoryBean并返回即可,不过需要注意的是虽然cat()方法返回值为CatFactoryBean,但放入容器中的并不是CatFactoryBean的实列而是CatFactoryBean中getObject()方法返回的实列。

    @Configuration(proxyBeanMethods = false)
    public class Config {
        @Bean
        public CatFactoryBean cat(){
            return new CatFactoryBean();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3、使用@Import注解

    如下,如果在一个类上使用了@Import注解,当这个类被实例化并放入IOC容器称为一个Bean时,@Import注解中的类也会被实例化并且放入IOC容器称为一个bean。

    @Component
    @Import(Cat.class)
    public class Config {
    }
    
    • 1
    • 2
    • 3
    • 4

    4、直接在IOC容器(仅限于AnnotationConfigApplicationContext容器)中添加Bean

    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig5.class); ctx.register(Cat.class);
    
    • 1

    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"};
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三、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 {
    }
    
    • 1
    • 2
    • 3
    • 4

    <2>在方法上加@ConditionalOnClass注解:如下,只有在存在Cat类时才会加载Cat类对应的bean。

    @Configuration
    public class Config {
        @Bean
        @ConditionalOnClass(name = "com.example.yuanli.example.Cat")
        public Cat cat(){
            return new Cat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • @ConditionalOnBean:只有存在对应的Bean时才会加载bean
      <1>在类上加@ConditionalOnBean注解: 如下,当加实列化Config类成为Bean时,会先检查存不存在dog这个bean,存在则实例化Config类成为Bean。
    @Configuration
    @ConditionalOnBean(name = "dog")
    public class Config {
    }
    
    • 1
    • 2
    • 3
    • 4

    <2>在方法上加@ConditionalOnBean注解:如下,只有在存在dog这个bean时才会加载Cat类对应的bean。

    @Configuration
    public class Config {
        @Bean
        @ConditionalOnBean(name = "dog")
        public Cat cat(){
            return new Cat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    属性类Animal依赖的类Cat和Mouse,只是一个普通的提供了set方法的类

    @Data
    public class Cat {
        private String name;
        private Integer age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @Data
    public class Mouse {
        private String name;
        private Integer age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注入属性类,可以像使用普通的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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、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})})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • @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() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    再看一下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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    经过上面的分析我们发现@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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    3、SpringBoot的监听器

    • 监听器的实现:
      <1>实现ApplicationListener,范型指定需要监听哪类事件,然后重新onApplicationEvent方法,当监听的类型的事件发生后就会调用onApplicationEvent方法。
    public class MyListener implements ApplicationListener<ApplicationStartedEvent> {
        @Override
        public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
            System.out.println("=================");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    <2>在项目的resources目标下创建META-INF文件夹,并在这个文件夹中创建spring.factories文件,添加如下内容。

    org.springframework.context.ApplicationListener=\
    com.example.demo.day1.MyListener
    
    • 1
    • 2
    • 监听器的类型
      <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.bootgroupId>
    	<artifactId>spring-boot-starter-parentartifactId>
    	<version>2.7.1version>
    	<relativePath/> 
    parent>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    <2>各种starter:合并了常用功能需要使用到的依赖。
    它整合了若干项目,在用户使用一个功能时(比如web),不需要要再配置各种依赖(比如spring-context,spring-mvc),直接依赖(spring-boot-starter-web)就可。

    2、自动配置(简化常用工程相关配置)

    3、辅助配置(内置服务器)

    sprinboot将一些常用的服务器抽象为对象(比如tomcat),并将这些对象交由IOC容器管理,需要访问服务器时就直接执行这些对象的方法。修改服务器的端口通过在yml中添加如下配置实现。

    server:
      port: 8080
    
    • 1
    • 2
  • 相关阅读:
    手工测试转自动化,学习路线必不可少,更有【117页】测开面试题,欢迎来预测
    解决DDP的参数未参与梯度计算
    使用GPT帮忙修改论文
    数学建模学习(76):多目标线性规划模型(理想法、线性加权法、最大最小法),模型敏感性分析
    【数据结构】学习笔记
    java毕业设计奥利给共享自习室系统mybatis+源码+调试部署+系统+数据库+lw
    golang:context
    【无标题】51单片机人体反应速度测试仪带万年历功能LCD1602 DS1302
    成功解决:文档根元素 “mapper“ 必须匹配 DOCTYPE 根 “null“
    Leetcode70. 爬楼梯
  • 原文地址:https://blog.csdn.net/zyf7764029/article/details/126375587