java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Springboot Spring原理

Springboot Spring原理2深度解析

作者:曾经的三心草

SpringBoot的⾃动配置就是当Spring容器启动后, ⼀些配置类, bean对象等就自动存入到了IoC容器中,不需要我们手动去声明, 从而简化了开发, 省去了繁琐的配置操作,这篇文章介绍Springboot Spring原理2,感兴趣的朋友一起看看吧

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

前言

3. Spring Boot⾃动配置

自动装配是属性的自动装配,比如刚刚说的,set注入ben,构造方法注入bean
自动配置呢

SpringBoot的⾃动配置就是当Spring容器启动后, ⼀些配置类, bean对象等就自动存入到了IoC容器中,不需要我们手动去声明, 从而简化了开发, 省去了繁琐的配置操作.
SpringBoot⾃动配置, 就是指SpringBoot是如何将依赖jar包中的配置类以及Bean加载到Spring IoC容
器中的.
主要分以下两个⽅⾯:

  1. Spring 是如何把对象加载到SpringIoC容器中的
  2. SpringBoot 是如何实现的

3.1 Spring 加载Bean

为什么加入依赖,一些bean,注解(@Mapper),就自动配置了

比如我们现在加入了一个包autoconfig,假设它是第三方的包
如果对这个打包,通过pom引入,那么就会出现在外部库中

所以这个我们自定义的第三方的包,放在这里和放在外部库是一样的效果
为什么放在这里效果和打包加依赖放在外部库是一样的效果呢
因为启动类只能加载它所在目录下的所有bean,不能加载它所在目录下的其他包下的bean的
我们的包autoconfig就是放在外面的

@Component
public class CkConfig {
    public void use(){
        System.out.println("CkConfig");
    }
}

然后我们在项目中就想使用这个包

@SpringBootApplication
public class SpringPrinciple2Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
        CkConfig bean = context.getBean(CkConfig.class);
        bean.use();
    }
}

执行

直接报错了,说找不到这个bean

因为spring扫描的路径,默认是启动类所在的目录
但是这个包在外面,所有加了注解Component也没有用

第一种方式是使用注解ComponentScan,这个就是指定要扫描的路径了

@SpringBootApplication
@ComponentScan("com.ck.demo.autoconfig")
public class SpringPrinciple2Application {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
        CkConfig bean = context.getBean(CkConfig.class);
        bean.use();
    }

}

这样就成功了

就是手动指定spring的扫描路径,但是缺点就是所有的外部的bean都要手动指定,比如redis的,mybatis的包都要手动加入

而且加了ComponentScan的话,原来的启动类下的路径就不会再扫描了,意思就是原来的config下的bean,就是package com.ck.demo.springprinciple2;包下的都不会再扫描了

ComponentScan下可以写数组的

@SpringBootApplication
@ComponentScan({"com.ck.demo.autoconfig","com.ck.demo.springprinciple2"})
public class SpringPrinciple2Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
        CkConfig bean = context.getBean(CkConfig.class);
        bean.use();
    }
}

所以这样之前的,和新加入的都可以生效了

@Import(CkConfig.class)
@SpringBootApplication
public class SpringPrinciple2Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
        CkConfig bean = context.getBean(CkConfig.class);
        bean.use();
    }
}

还可以通过这个方式,使用注解Import,来导入class

但是spring的话,这两种方式都没有采用

@Import({CkConfig.class, CkConfig2.class})

也是可以添加多个的

public class MyImport implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.ck.demo.autoconfig.CkConfig","com.ck.demo.autoconfig.CkConfig2"};
    }
}

我们在第三方autoconfig下写一个这个类,表示Import要扫描的路径

@Import(MyImport.class)
@SpringBootApplication
public class SpringPrinciple2Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
        CkConfig bean = context.getBean(CkConfig.class);
        bean.use();
    }
}

这样就可以了,这个就是第三方提供给你bean的路径

但是还是麻烦,继续升级,第三方提供注解就好了

@Target(ElementType.TYPE)//这个注解可以加在什么地方上
@Retention(RetentionPolicy.RUNTIME)//生命周期
@Import(MyImport.class)//注解的作用
public @interface EnableCkConfig {
}

这个注解定义在autoconfig下,也是第三方提供的

@EnableCkConfig
@SpringBootApplication
public class SpringPrinciple2Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
        CkConfig bean = context.getBean(CkConfig.class);
        bean.use();
        CkConfig2 bean2 = context.getBean(CkConfig2.class);
        bean2.use();
    }
}

这样就可以了

所以第三方只需要提供注解就可以了

但是导入mybatis的依赖的时候,就那些bean也不用在启动类上加入注解啊
所以还有其他方式,连注解也不需要加
就是在resource目录下创建一个文件,放入"com.ck.demo.autoconfig.CkConfig","com.ck.demo.autoconfig.CkConfig2"这些东西,就会自动创建和管理了

这个文件就是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
放入

com.ck.demo.autoconfig.CkConfig
com.ck.demo.autoconfig.CkConfig2

这个就是告诉spring,我要管理哪些对象

这样的话,自定义注解EnableCkConfig也不需要了,也可以获取第三方的bean了

3.2 源码分析

但是spring用的是什么方式呢
我们去看启动类@SpringBootApplication
因为这个注解,所以它是启动类
还有ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);这个来启动

点进去注解SpringBootApplication

@Target({ElementType.TYPE})表示主要要加在什么上

@Retention(RetentionPolicy.RUNTIME)生命周期

@Documented文档相关

@Inherited继承相关,是源注解,不看了

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

这三个最重要

@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

因为这个,所以会扫码启动类下的路径
加了这个,默认扫描ComponentScan注解所在的路径,也就是SpringBootApplication注解所在的路径

SpringBootConfiguration注解点进去

Indexed注解是一个优化的注解,不重要

重点是Configuration,五大注解之一,所以SpringBootConfiguration注解就是Configuration分装了一下
所以注解SpringBootConfiguration表示这是一个配置类,这个会被spring给识别到

所以重点就是注解EnableAutoConfiguration了

3.3 注解@EnableAutoConfiguration

@Import({AutoConfigurationImportSelector.class})

AutoConfigurationImportSelector这个实现了DeferredImportSelector,DeferredImportSelector实现了ImportSelector

实现了方法selectImports
返回了要管理的bean

            AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

主要是从这个getAutoConfigurationEntry来的
点进去

AnnotationMetadata是注解的原信息

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

这个方法点进去

这个就是加载配置了
Assert是断言

        String aa=null;
        Assert.notNull(aa,"aa不能为空");

比如这个意思就是断言aa不能为空
为空的话,就报"aa不能为空"的错误

这个断言的就是没有配置信息在META-INF/spring/。。。。。
所以

 ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());

这个就是读取META-INF/spring/这个文件的配置信息的,没有的话,就会包一个异常

但是我们平时并没有这个文件啊,为什么没有报错

ctrl+N搜索org.springframework.boot.autoconfigure.AutoConfiguration.imports

点击第一个

找到这里,发现这里就有那个META-INF/spring/的文件

这里面放的就是spring要加载的bean
说明spring它自己就写了一个这种文件了
所以要管理我们第三方的bean的话,也是写一个这个路径就可以了,spring就会认识了,spring是根据路径来扫描的,都是这个路径的就都可以扫描到了

比如这个里面就有事务
spring就会管理DataSourceTransactionManagerAutoConfiguration这个bean了
ctrl+n搜索

然后DataSourceTransactionManagerAutoConfiguration这个里面还有bean
这样就都生效了

比如redis的,搜索RedisAutoConfiguration这个bean

然后RedisAutoConfiguration这个bean里面就有RedisTemplate这个bean,所以RedisTemplate这个bean也是自动注入的

除了@Bean之外,还有其他注解

比如注解ConditionalOnMissingBean
表示在不同的条件下才注入这个bean

@Bean
@ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }

这个意思就是,如果你没有声明stringRedisTemplate这个Bean,我就声明stringRedisTemplate这个bean

    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )

意思就是你要是声明了redisTemplate这个bean,我就不声明了,你没有声明,我就声明

@ConditionalOnClass({RedisOperations.class})

看有没有RedisOperations这个类,没有的话,这个类下的所有bean都不,包括自己这个类的bean都不生效

这个RedisOperations就在import org.springframework.data.redis.core.RedisOperations;上面摆着
这个RedisOperations类就是我们要引入的依赖里面的,换个意思就是没有引入依赖,就没有RedisOperations这个类,就不能加载Bean了

所以加了依赖,外部库就有org.springframework.data.redis.core,这个类RedisOperations就有了,对应的RedisAutoConfiguration和它下面的方法这些Bean就有了

不加依赖所以就不行了

继续回到这里,就是获取META-INF/spring/配置的这里
就是

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

这里configurations就是META-INF/spring/文件里面的配置,包括我们自己写在META-INF/spring/里面的

this.checkExcludedClasses(configurations, exclusions);

这个是检查排除,根据依赖信息来检查排除

 configurations.removeAll(exclusions);

这个就是删除没有引入依赖的配置

最后返回

所以getAutoConfigurationEntry返回的就是要求spring给我们管理的配置,对象,bean

所以注解@Import({AutoConfigurationImportSelector.class}),返回的就是要求spring给我们管理的配置,对象,bean

现在我们看AutoConfigurationPackage这个注解

它又import了

import了Register,这又是一个我们注入包的方式

Register加载bean

public class MyRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(CkConfig.class);
//        beanDefinition.setScope("xxxx");//表示这个bean是单例的还是多例的
        registry.registerBeanDefinition("ckConfig",beanDefinition);//注入bean,第一个参数是bean的名称
    }
}

我们在autoconfig下定义MyRegister

@SpringBootApplication
@Import(MyRegister.class)
public class SpringPrinciple2Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
        CkConfig bean = context.getBean(CkConfig.class);
        bean.use();
//        CkConfig2 bean2 = context.getBean(CkConfig2.class);
//        bean2.use();
    }
}

这又是一种方式导入在这里插入代码片

继续分析

这里定义了一个Register的类
定义了registerBeanDefinitions这个方法

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])这个得到的就是我们启动类所在路径com.ck.demo.springprinciple2

AutoConfigurationPackages.register这个就是注入第三方的注解,比如@Mapper

为什么spring能够管理第三方的注解

第三方就会写那些register
然后给我们注入

然后BlogInfoMapper使用了注解Mapper
然后注解Mapper就会拿到BlogInfoMapper的路径

比如我们搜索这个MybatisPlus的这个类

找到这里,这里实现了Register

这里调用的registerBeanDefinitions,就是我们刚刚看到的启动类原码里面定义的方法

就是这个方法

这里会得到启动类的路径
然后在这个启动类里面扫描得到Mapper注解,扫描谁加了Mapper注解
然后调用方法registerBeanDefinitions加到spring就可以管理了
这个就是MybatisPlus第三方注解加入Bean的方式

就是调用第三方的注解,但是第三方的注解不是立马加入Bean,而是,先获取启动类路径,根据启动类路径扫描谁
调用了Mapper注解,谁调用个,就调用方法registerBeanDefinitions加到spring就可以管理了

public class MyRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(CkConfig.class);
//        beanDefinition.setScope("xxxx");//表示这个bean是单例的还是多例的
        registry.registerBeanDefinition("ckConfig",beanDefinition);//注入bean,第一个参数是bean的名称
    }
}

比如这个,xx使用了注解Mapper(会自动调用相应方法),—》把xx给registry.registerBeanDefinition,然后MyRegister 由于引入了依赖,而且implements ImportBeanDefinitionRegistrar,就自动会被spring给@Import(MyRegister.class)

3.4 总结

AutoConfigurationPackage用的是Register的方式来注入bean,主要是用来扫描第三方注解,比如@Mapper,
第三方实现ImportBeanDefinitionRegistrar,然后就可以注入了

当SpringBoot程序启动时, 会加载配置⽂件当中所定义的配置类, 通过 @Import 注解将这些配置类全
部加载到Spring的IOC容器中, 交给IOC容器管理.

  1. Bean的作⽤域共分为6种: singleton, prototype, request, session, application和websocket.
  2. Bean的⽣命周期共分为5⼤部分: 实例化, 属性复制, 初始化, 使⽤和销毁
  3. SpringBoot的⾃动配置原理源码⼝是 @SpringBootApplication 注解, 这个注解封装了3个注解◦@SpringBootConfiguration 标志当前类为配置类
    ◦ @ComponentScan 进⾏包扫描(默认扫描的是启动类所在的当前包及其⼦包)
    ◦ @EnableAutoConfiguration
    ▪ @Import 注解 : 读取当前项⽬下所有依赖jar包中 META-INF/spring.factories ,
    METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 两个⽂件⾥⾯定义的配置类(配置类中定义了 @Bean 注解标识的⽅法)
    ▪ @AutoConfigurationPackage : 把启动类所在的包下⾯所有的组件都注⼊到 Spring容器中

总结

到此这篇关于Springboot Spring原理2的文章就介绍到这了,更多相关Springboot Spring原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文