Web后端开发原理!!!什么是自动配置???什么是起动依赖???

目录

引言:

[1. SprngBoot-配置优先级](#1. SprngBoot-配置优先级)

[2. Bean 管理](#2. Bean 管理)

Bean的声明

Bean的注入

Bean的获取

Bean的作用域:

Bean的延迟创建

[1. 使用@Lazy注解](#1. 使用@Lazy注解)

[2. 使用@Bean注解的lazyInit属性](#2. 使用@Bean注解的lazyInit属性)

[3. 使用条件懒加载(@Conditional注解)](#3. 使用条件懒加载(@Conditional注解))

[4. 使用XML配置](#4. 使用XML配置)

第三方Bean

三.SpringBoot自动配置的原理:

[3.1 SpringBoot自动配置源码启动:](#3.1 SpringBoot自动配置源码启动:)

[3.2 如何自定义一个starter?????](#3.2 如何自定义一个starter?????)


引言:

当然,在我们学习的过程中,得知其然,还得知其所以然。So理解了其原理,更能让我们对其开发的理解,遇到问题,也更能快速找到解决办法!!!

1. SprngBoot-配置优先级

1.1 属性配置的方式

在SpringBoot中支持以下三种格式的配置文件:

1.2 配置的优先级

如果当配置三个文件同时配置了一个属性,那么谁的优先级比较高?

我们启动项目,看端口是多少就知道了.

启动发现端口是8081,说明当三分配置文件,其Properties的优先较高。OK我们注释掉它的配置,接下来继续比较另外2个优先级。

启动:

发现是8082端口,对应的Yml配置文件,所以说

优先级结果:Properties > yml > yaml

So:虽然SpringBoot支持多种格式配置文件,但是在项目开发时,我们还是使用主流配置yml


到这里还没完呢,其实在SpringBoot中,为了增强程序的扩展性: 除了支持配置文件属性配置,还支持Java系统属性和命令行参数的方式进行属性配置

java系统属性:格式为:-D+key=value (-D是固定的)

命令行参数: 格式为:--Key=value (--是固定的后面加键=值就可以了)

如下:

Idea中运行SpringBoot项目如果指定java系统属性和命令行参数的方式进行属性配置呢?当然,Idea中已经提供了可视化的界面提供操作了

如下:点击编辑配置,然后找到自己的项目启动类

进来,点击选择参数:

添加配置:

点击右下角应用,确定OK!

然后我们这里注释掉以前的三种配置属性,看看这两种的配置谁的优先级较高!!!

欧克,启动!!!

发现端口是10010,欧克我们可以得知,命名行参数配置优先级大于Java系统属性配置!!!

当然,这只是在Idea中来设置属性配置,如果我们没在Idea中,比如我们打包得jar包上线了,又该如何设置 java系统属性和命令行参数?

欧克,我们通过Maven打包运行一下,来在启动得时候添加一下参数

默认不配置任何属性端口8080,没问题,欧克,我们添加java系统属性和命名行参数:

欧克,Ctrl+C终止程序,我们只配置命令行参数,不出意外,就是端口9000


欧克,我们总结一下,在来比较一下这2种配置(命令行参数>java系统属性 )和开始得3种配置(Properties > yml > yaml )优先级,我们这边只需要拿 java系统属性来和另外三种配置来比较就可以了!!

欧克启动!

端口9000,没问题,说明我们得java系统属性大于另外三种配置得,综上: 命令行参数>java系统>Properties > yml > yaml属性

So:配置:

2. Bean 管理

Bean的声明

  1. 注解声明
    • @Component及其派生注解:这是最常用的声明Bean的方式。通过在类上添加@Component、@Service、@Repository、@Controller等注解,Spring会自动扫描这些类并将其实例化为Bean。这些注解之间在功能上并无明显区别,但通常遵循以下约定:@Controller用于控制层,@Service用于业务层,@Repository用于数据访问层,@Component用于其他组件。
    • @Bean注解:在配置类(@Configuration标注的类)中使用@Bean注解来声明Bean。通过返回实例化的对象,Spring会将其注册为Bean。这种方式在需要自定义Bean的实例化过程或引用第三方库中的类时特别有用。
    • @Component声明FactoryBean:FactoryBean是一个特殊的Bean,它可以生成并返回其他Bean的实例。通过在类上添加@Component注解,并将类实现为FactoryBean接口,可以声明一个FactoryBean类型的Bean。
  2. 编程式声明
    • BeanDefinitionRegistryPostProcessor:通过实现此接口,可以在Bean定义加载到Spring容器之前动态地注册Bean定义。
    • @Import + ImportBeanDefinitionRegistrar:在配置类或Bootstrap启动类中使用@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类,然后在该接口的实现类中注册Bean定义。

Bean的注入

  1. 自动装配
    • @Autowired:这是Spring提供的自动装配注解,可以放在构造器、setter方法或字段上,Spring会根据类型自动匹配并注入相应的Bean。如果存在多个同类型的Bean,则需要通过@Qualifier注解指定要注入的Bean的名称。
    • @Resource:这是JDK提供的注解,功能与@Autowired类似,但可以通过指定name属性来指定要注入的Bean的名称。
  2. 构造方法注入
    • 通过在类的构造方法中接收Bean参数,Spring会在实例化该类时自动注入这些Bean。这种方式有助于确保Bean的不可变性,因为一旦Bean被实例化,其依赖的Bean就不能再被更改。
  3. Setter方法注入
    • 在Bean的setter方法上使用@Autowired或@Resource注解,Spring会在Bean实例化后调用这些setter方法来注入依赖的Bean。
  4. 属性注入
    • 直接在类的字段上使用@Autowired或@Resource注解,Spring会在Bean实例化时通过反射将这些字段设置为相应的Bean实例。然而,这种方式通常不推荐用于生产环境,因为它可能导致字段的不可控访问和难以追踪的依赖关系。

Bean的获取

默认情况下,spring项目启动时,会把声明扫描到的这些Bean都创建好放在IOC容器中,如果想主动获取Bean,可以通过如下方式:

从IOC容器中获取到Bean,当然在springBoot环境中直接注入IOC容器就可以了!!

根据name获取bean:

当然里面有很多重载方法,选择第一个就可以了,顺便找一个我们项目的注入的Bean的名称来测试一下就可以了!!!

这里我直接就拿第一个接口来试试:

这里没有声明Bean的名称,默认就是首字母小写的就是 loginController

欧克:我们启动测试:

实例;

java 复制代码
@SpringBootTest
public class getTheBean {
     @Resource
    private ApplicationContext applicationContext; // 自动注入IOC容器
    @Test
    void testGetBean() {
        // 根据bean的名称获取bean
        LoginController loginController =(LoginController) applicationContext.getBean("loginController");
        System.out.println("loginController = " + loginController);
    
   
    }
}

效果:

根据类型获取bean:

根据name获取bean(带类型转化):

同理:如下

实例:

java 复制代码
@SpringBootTest
public class getTheBean {
     @Resource
    private ApplicationContext applicationContext; // 自动注入IOC容器
    @Test
    void testGetBean() {
        // 根据bean的名称获取bean
        LoginController loginController =(LoginController) applicationContext.getBean("loginController");
        System.out.println("loginController = " + loginController);
        // 根据bean的类型获取
        LoginController loginController1= applicationContext.getBean(LoginController.class);
        System.out.println("loginController1 = " + loginController1);
        // 根据bean的名称及类型获取
        LoginController loginController2 = applicationContext.getBean("loginController", LoginController.class);
        System.out.println("loginController2 = " + loginController2);
    }
}

效果:

结果:So,我们可以看到三次地址都是一样的,所以说明Ioc容器中这个Bean对象只有一个,是单列的(就是Bean的作用域了),整个生命周期内只会被创建一次,并且多个线程共享使用。

此时如果:spring中的单列Bean是否有并发线程安全???

Spring的单列Bean默认是非线程安全的,但是只要我们避免多个Bean之间共享一些数据,就不用害怕并发问题。

原因

单列Bean的生命周期:Spring容器在初始化时会创建并管理单列Bean,这些Bean整个生命周期内只会被创建一次,并且多个线程共享使用。多线程访问:如果单列Bean中包含共享可变状态(如实例变量),多个线程同时访问并修改这些共享状态时,可能会导致并发安全问题,如数据不一致,脏读,死锁等。

Bean的作用域:

以下六种:

默认的其实每次都会在springBoot启动的时候来创建:我们测试一下

列:

同时,这些Bean会在SpringBoot启动就会被创建好:方便测试,弄个构造方法,在创建完毕的时候我们可以观察:Ok,打上断点,开始调试:

效果:

ok:可以看到,在springBoot启动的时候,就已经创建好了!!!

当然;如果我们需要达成我们某个条件还才开始创建,有许多注解可以使用如下:

Bean的延迟创建

1. 使用@Lazy注解

@Lazy 注解是最直接的方式来实现Bean的懒加载。通过在Bean的声明上添加@Lazy注解,可以指示Spring容器在第一次注入或使用时才创建该Bean。这个注解可以应用于类级别或方法级别(在配置类中使用@Bean注解声明Bean时)。

java 复制代码
// 类级别
@Component  
@Lazy  
public class MyLazyBean {  
    // ...  
}

// 方法级别
@Configuration  
public class AppConfig {  
    @Bean  
    @Lazy  
    public MyLazyBean myLazyBean() {  
        return new MyLazyBean();  
    }  
}

2. 使用@Bean注解的lazyInit属性

在Spring的配置类中,使用@Bean注解声明Bean时,可以设置lazyInit 属性为true来实现懒加载。这是另一种在方法级别上实现懒加载的方式。

java 复制代码
@Configuration  
public class MyConfig {  
    @Bean(lazyInit = true)  
    public MyLazyBean myLazyBean() {  
        return new MyLazyBean();  
    }  
}

3. 使用条件懒加载(@Conditional注解)

虽然**@Conditional** 注解本身不直接用于实现懒加载,但它可以根据条件来决定是否创建Bean。通过结合自定义条件,可以在满足特定条件时才创建Bean,这可以间接实现按需创建Bean的效果。然而,它并不等同于懒加载,因为它在容器启动时就会根据条件决定是否创建Bean。

++最基础的就是根据IOC容器是否有某个类来决定是否加载这个类!!!++

java 复制代码
@Configuration
public class SomeConfiguration {

    @Bean
    @Conditional(OnWebApplicationCondition.class)
    public SomeBean someBeanForWeb() {
        return new SomeBean();
    }
    
    @Bean
    @Conditional(OnNotWebApplicationCondition.class)
    public SomeBean someBeanForNotWeb() {
        return new SomeBean();
    }
}

4. 使用XML配置

如果你使用的是基于XML的配置方式,可以通过在<bean>标签中设置lazy-init 属性为true来实现懒加载。

java 复制代码
<bean id="myBean" class="com.example.MyBean" lazy-init="true"/>

欧克:回归正题:在spring中可以通过注解:@Scope ("")来声明作用域 默认是singleton 单咧的,如果改成prototype,则每次拿取都会创建新的对象的。

试试prototype每次获取都会实例化新的Bean,启动测试:

动效果:

没问题,每次都不一样把!!!

总:

第三方Bean

当然除了我们自定义了一些类,比如我们加的这些@Component注解以及衍生类注解@Controller,Service,@Repository 等。还有我们平常的第三方配置!!就是引入的第三方依赖所提供的!!!

如果要管理的bean对象来自第三方(不是自定义的),无法用@Component及衍生注解声明bean的,就需要用到@Bean注解

比如这里就随便测试一下:

比如我们引入的是Spring Date Redis的依赖

当然:它会自动配置**RedisTemplate** 和**StringRedisTemplate** 的Bean ,这二种之间也有区别,StringRedisTemplate默认传的字符串,我们通常需要Json来转化,但是相比某条件更节约内存,OK,我们这里不详细介绍,后续补上,这里我们就使用RedisTemplate,当然这个东西我们一般都需要我们自定义的。根据需求来调整其行为,包括选择序列化器、设置连接参数等。

欧克,我们就来配置下,为了方便集中管理

我们提供@Configuration注解声明当前类为一个配置类,任何在我们的方法上去加@Bean就可以

如下:

java 复制代码
@Configuration
@Slf4j
public class RedisConfiguratiom {
    @Bean //将当前方法的返回值对象交给IOC容器管理,成为Ioc容器的Bean对象
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
       // log.info("开始创建Redis模板...");
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

插曲: 当然,如果这里 第三方bean需要引入依赖注入的化,只需要通过参数的形式来声明这个类型参数就可以了.SpringBoot会在这个容器中去找到这个Bean对象,然后完成注入操作!!!比如这里这个RedisConnectionFactory redisConnectionFactory参数是Spring自动注入的,用于创建Redis连接。

当然其实同原理,自然也可以在启动类下直接配置:

因为我们进入启动类注解中,可以看到他

自然当前类就是一个配置类,自然也可以自己声明@Bean,当然一般不建议这样!!!

当然也可以通过@bean中的name和value来声明Bean的名称,如果不设置,默认就是方法名

总:

项目中自定义的,就使用@Componet及其衍生注解:

项目中引入的第三方的,使用@Bean注解

三 . springBoot-原理

原理:就是通过扫描指定的依赖包下的文件的配置类,任何封装到String【】数组里面,实现自动 装配

2.7之前的版本:

Spring常用的注解:

java 复制代码
@SpringBootConfiguration //声明当前启动类也是一个注解
@EnableAutoConfiguration //声明哪些第三类配置当前类中
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })//默认扫描当前类和其子类

配置条件:

三.SpringBoot自动配置的原理:

Spring框架的配置相对复杂,需要手动配置大量的XML文件或注解。而Spring Boot通过自动配置和约定优于配置的原则,大大简化了配置过程。之所以SpringBoot框架更加简单,快捷,是因为其底层提供了2个非常重要的功能

起步依赖-->>(就是通过Maven的依赖传递)解决Spring中依赖配置的繁琐

自动配置 -->>大大简化框架的使用过程中Bean的声明和配置通过起步依赖,常用的配置基本也就有了!!!

起步依赖:

起步依赖本质上是一个Maven或Gradle的依赖描述符,它包含了构建特定类型应用程序所需的所有依赖项。例如,spring-boot-starter-web包含了构建Web应用程序所需的所有Spring MVC和Tomcat的依赖项。通过引入起步依赖,开发人员可以轻松地集成所需的组件,而无需手动添加每个依赖项。

自动配置:

Spring Boot 的自动配置是另一个核心特性。它基于Spring 框架的条件化配置功能,根据应用程序中声明的依赖项和类路径中的资源,自动配置Spring应用程序。自动配置会尝试猜测开发人员可能需要的配置,并自动应用这些配置。如果开发人员需要自定义配置,可以通过配置文件(如application.propertiesapplication.yml)或Java配置类来覆盖自动配置。

我们启动一个程序:

可以看到除了我们自定义的配置还有很多配置类。这些配置加载进来,就会生成很多的Bean对象了!!!我们都可以直接DI注入使用了,这就是SpringBoot启动的,自动就帮我们配置好了的效果!!

OK,哪我们就了解下SpringBoot自动配置的原理:它是如何把我们引入的这些依赖定义的配置类,以及Bean如何加载到我们的SpringIOC容器中。

这里新建一个模块,做一些配置,充当第三方依赖,然后在其他项目引入这个模块的坐标

模块中:

当然,我们测试看看能不能获取到这个Bean对象, 按理我们引入了这个依赖,并且也声明了Component注解,然后应该会加载到SpringBoot的容器中

Ok,不出意外,找不到这个Bean说明是没有加载到的,其实spring启动的时候,默认会扫描当前包及其子包下才可以的,需要扫描到才可以交到Ioc容器中实现,不是声明了注解就一定会成为Ioc容器的Bean对象的,这里可以通过注解在启动类声明 @ComponentScan() 里面是数组看源码,然后指定包名就可以了

欧克:可以看到拿到Bean对象

当然,底层肯定不是这样字的,不然我们引入第三方依赖,哪不得爆炸,这种很繁琐

另一种就是@import注解实现得,其实这个就是关键,后面得实现其实也是套用这个了得。

使用@import导入得类会被Spring加载到Ioc容器中,导入形式有以下几种:

导入 普通类 --> 这个类就会交到Ioc容器中

导入 配置类 --> 这个配置类包括下得所以@bean对象都会就会交到Ioc容器中

导入ImportSelector 接口实现类 (关键得重点,后面其实底层就是通过这个接口来扫描得文件)

@import源码也声明了可以导入这些类

返回的是数组呢

如:

java 复制代码
//@Import({MyImportSelector.class})
//@Import({TokenParser.class}) // 导入普通类 交给IOC容器
@Import({HeaderConfig.class}) // 导入配置类 交给IOC容器
@SpringBootApplication
public class SpringbootWebConfig2Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }

}

当然第三种就是 导入ImportSelector接口实现类看下源码:

返回值就是类的全类名,欧克,我们只需要实现这个接口,重写这个方法,然后添加一些我们想引入的Bean的全类名就ok了

java 复制代码
public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.example.HeaderConfig"};
    }
}


@Configuration
public class HeaderConfig {

    @Bean
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

    @Bean
    public HeaderGenerator headerGenerator(){
        return new HeaderGenerator();
    }
}


@Import({MyImportSelector.class})
@SpringBootApplication
public class SpringbootWebConfig2Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }

}

当然,上面这些方法,有些鸡肋的,当我们引入第三方依赖,是需要清晰知道我们要导入第三依赖的哪些配置类,哪些包的。 还是繁琐的,当然需要第三方自己自己来封装需要到那些类,然后通过第三方提供的@Enablexxxx注解然后在通过@Import注解来指定哪些需要声明的@Bean来实现

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableHeaderConfig {
}


public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.example.HeaderConfig"};
    }
}


@Configuration
public class HeaderConfig {

    @Bean
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

    @Bean
    public HeaderGenerator headerGenerator(){
        return new HeaderGenerator();
    }
}


-------


@EnableHeaderConfig
public class SpringbootWebConfig2Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }

}

只需要在启动类声明这个注解就可以了,层层套娃,我们不需要关注第三方怎么实现的。 就可以把对于的配置类,bean加载到IOC容器里面了(这也是SpringBoot所采用的)

3.1 SpringBoot自动配置源码启动:

我们从启动类点击进去可以看到封装了许多注解

我们不需要关注其他,进入@EnableAutoConfiguration注解中去一探究竟:

其实关键就是找到这个接口这个方法就完事了:

欧克,进入实现类看看找到这个方法:

跟紧:

注:此版本是2.7版本之后的

可以看到其实就是扫描这2个文件的配置类容的。把这2个配置文件的信息加载出来,就会封装到这个Llst<String>集合当中,然后通过返回给给这个String【】只会,spring就会导入这些配置和@bean了

: 2.7版本之前其实只有META-INF/spring.factories文件,如下:

: 从Spring Boot 2.7开始,虽然META-INF/spring.factories文件仍然是自动配置的一个重要组成部分,但Spring Boot引入了一种新的自动配置机制,即META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。这个文件提供了一种更灵活的方式来指定自动配置类,但它并不是完全替代spring.factories文件,而是作为一种补充。

说简单点就是通过@SpringBootApplication 封装的@EnableAutoConfiguration 注解然后封装的@Import 来扫描指定文件,通过这些文件获取到配置类,@Bean的全类名啊这些在封装到这个String【】数组对象就ok!!!

其实这些文件在我们引入的起步依赖中都有

进去看看:

就是这些全类名,通过读取这些配置文件全类名后,通过@import把这些配置类,Bean加载到Ioc容器中 。(当然,你也就可以自己定义一个启动类了)

进入一个实例看看,其实就是一个配置类,并且下面的加了@Bean,所以我们ioc容器加载到这些,自然就有了Bean了,我们就可以注入使用了!!!

总结:

就是启动了注解底层封装了三个核心的注解,

  1. @SpringBootConfiguration-->> 其注解又封装了@Configuration,就是声明当前也是一个配置类

  2. @EnableAutoConfiguration->> 其又封装了@Import注解 ,其注解又指定了ImportSelector实现类,其类有实现了selectImports的方法,其返回值就是String【】,这个数组封装的内容就是我们要导入到SpringIoc容器中的类的全类名,然后这个方法其实就会去扫描加载2个文件的这些全类名,这些全类名就是一个一个配置类,1个是spring.factories的文件(2.7版本之前早期使用的,3.0版本之后就没了,在此期间会兼容),另一个是spring下的org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(2.7.x版本之后一种新的自动配置机制,3.0版本之后主导使用)。然后这些配置类下其实就是声明了一个一个的@Bean对象,然后就会把这些配置类加载通过String数组全部封装返回,然后通过Import注解把这些全部交给Spring的Ioc容器当中。

  3. @ComponentScan-->> 组件扫描,默认扫描当前引导类所在的包及其子包

当然:这里并不是全部就会交给Ioc容器成为@Bean.其实还是有条件的,比如底层有些@Bean是加了@Conditionxxx的条件注解,满足某些条件之后才会加载到IOc容器中的

@Conditional 注解可以作用于 @Bean 方法、配置类或其他组件类上,当 Spring 容器扫描到 @Conditional 注解

时,会调用其 Condition 实现类的 matches 方法,根据返回的布尔值来决定是否实例化对应的 Bean。

当然这里扩展一下常用的Conditional的字注解使用:

3.2 如何自定义一个starter?????

可以看另一篇博客

快速自定义一个starterhttps://blog.csdn.net/2301_77058976/article/details/142612237?spm=1001.2014.3001.5501

相关推荐
小信丶几秒前
Spring Cloud Stream EnableBinding注解详解:定义、应用场景与示例代码
java·spring boot·后端·spring
s1mple“”2 分钟前
互联网大厂Java面试实录:谢飞机的AIGC求职之旅 - JVM并发编程到Spring Cloud微服务
spring boot·aigc·微服务架构·java面试·分布式系统·rag技术·redis数据库
无限进步_4 分钟前
【C++】验证回文字符串:高效算法详解与优化
java·开发语言·c++·git·算法·github·visual studio
亚历克斯神5 分钟前
Spring Cloud 2026 架构演进
java·spring·微服务
七夜zippoe8 分钟前
Spring Cloud与Dubbo架构哲学对决
java·spring cloud·架构·dubbo·配置中心
海派程序猿9 分钟前
Spring Cloud Config拉取配置过慢导致服务启动延迟的优化技巧
java
阿维的博客日记19 分钟前
为什么不逃逸代表不需要锁,JIT会直接删掉锁
java
William Dawson21 分钟前
CAS的底层实现
java
ffqws_24 分钟前
Spring Boot入门:通过简单的注册功能串联Controller,Service,Mapper。(含有数据库建立,连接,及一些关键注解的讲解)
数据库·spring boot·后端
九英里路32 分钟前
cpp容器——string模拟实现
java·前端·数据结构·c++·算法·容器·字符串