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

目录

引言:

1. SprngBoot-配置优先级

2. Bean 管理        

Bean的声明

Bean的注入

Bean的获取

 Bean的作用域:

 Bean的延迟创建

1. 使用@Lazy注解

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

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

4. 使用XML配置

第三方Bean 

三.SpringBoot自动配置的原理:

3.1 SpringBoot自动配置源码启动:

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


引言:

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

当然,在我们学习的过程中,得知其然,还得知其所以然。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

欧克:我们启动测试: 

实例;

@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(带类型转化):

同理:如下

实例:

@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时)。

// 类级别
@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来实现懒加载。这是另一种在方法级别上实现懒加载的方式。

@Configuration  
public class MyConfig {  
    @Bean(lazyInit = true)  
    public MyLazyBean myLazyBean() {  
        return new MyLazyBean();  
    }  
}

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

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

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

@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的配置方式,可以通过在标签中设置lazy-init属性为true来实现懒加载。

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

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

动效果:

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

总:

第三方Bean 

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

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

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

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

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

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

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

如下:

@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【】数组里面,实现自动 装配,点击@SpringBootApplication进入可以看到:

Spring常用的注解:

@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源码也声明了可以导入这些类

 返回的是数组呢

如:

//@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了 

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来实现

@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【】只会,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

版权声明:如无特殊标注,文章均来自网络,本站编辑整理,转载时请以链接形式注明文章出处,请自行分辨。

本文链接:https://www.shbk5.com/dnsj/74685.html