Spring原理

目录

一、Bean的作用域

1、概念

2、Bean的作用域

(1)singleton

(2)prototype

(3)request

(4)session

(5)application

二、Bean的生命周期

1、代码演示

2、源码阅读

三、Spring Boot自动配置

1、Spring 加载 Bean

(1)问题描述

(2)原因分析

(3)解决方案

@ComponentScan

@Import

1、导入类

2、导入ImportSelector接口实现类

(4)问题

1、第三方依赖提供注解

2、在启动类上使用第三方提供的注解

2、SpringBoot原理分析

(1)源码阅读

1、元注解

2、@SpringBootConfiguration

3、@EnableAutoConfiguration(开启自动配置)

4、@ComponentScan(包扫描)

(2)EnableAutoConfiguration

1、Import({AutoConfigurationImportSelector.class})

2、@AutoConfigurationPackage

3、总结

四、总结


一、Bean的作用域

1、概念

        在Spring IoC&DI阶段,学习了 Spring是如何帮助我们管理对象的

1、通过 @Controller,@Service,@Repository,@Component,@Configuration,@Bean声明Bean对象

2、通过 ApplicationContext 或者 BeanFactory获取对象

3、通过 @Autowired,Setter 方法或者构造方法等来为应用程序注入所依赖的Bean对象

        下面进行简单回顾一下:

1、通过@Bean声明bean,把bean存进Spring容器中

public class Dog {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

@Configuration
public class DogConfig {

    @Bean
    public Dog dog() {
        Dog dog = new Dog();
        dog.setName("huahua");
        return dog;
    }
}

2、从Spring容器中获取bean

@SpringBootApplication
public class SpringPrincipleApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringPrincipleApplication.class, args);
        Dog dog = context.getBean(Dog.class);
        System.out.println(dog);
    }

}

        通过注入的方式获取

@SpringBootTest
class SpringPrincipleApplicationTests {

    @Autowired
    private ApplicationContext context;

    @Test
    void contextLoads() {
        Dog dog1 = context.getBean(Dog.class);
        dog1.setName("name1");
        System.out.println(dog1.getName());
        System.out.println(dog1);

        Dog dog2 = context.getBean(Dog.class);
        System.out.println(dog2.getName());
        System.out.println(dog2);
    }
}

        观察运行结果:

        发现输出bean的地址都是一样的,说明每次从Spring容器中拿出的对象都是同一个

        dog1 和 dog2  为同一个对象,dog2 拿到了 dog1 设置的值。

        这也是“单例模式”。

        单例模式:确保一个类只有一个实例,多次创建也不会创建出多个实例。

        默认情况下,Spring容器中的bean都是单例的,这种行为模式,我们就称之为Bean的作用域

Bean 的作用域是指Bean在Spring框架中的某种行为模式

        比如单例作用域表示Bean在整个Spring中只有一份,它是全局共享的。那么当其他人修改了这个值之后,那么另一个人读取到的就是被修改的值

        那能不能将bean对象设置为非单例的(每次获取的bean都是一个新的对象呢)?这就是Bean的不同作用域了。

2、Bean的作用域

        在Spring中支持6中作用域,后4中在Spring MVC环境才生效

1、singleton:单例作用域

2、prototype:原型作用域(多例作用域)

3、request:请求作用域

4、session:会话作用域

5、Application:全局作用域

6、websocket:HTTP WebSocket 作用域

        参考文档:Bean Scopes :: Spring Framework

作用域说明
singleton每个Spring IoC容器内同名称的bean只有一个实例(单例)(默认)
prototype每次使用该bean时会创建新的实例(非单例)
request每个HTTP 请求生命周期内,创建新的实例(web环境中,了解)
session每个HTTP Session 生命周期内,创建新的实例(web环境中,了解)
application每个ServletContext生命周期内,创建新的实例(web环境中,了解)
websocket每个WebSocket生命周期内,创建新的实例(web环境中,了解)

(范围从小到大)

        我们来简单看下代码实现,定义⼏个不同作⽤域的Bean

@Configuration
public class DogConfig {
    
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    public Dog singletonDog() {
        return new Dog();
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Dog prototypeDog() {
        return new Dog();
    }

    @Bean
    @RequestScope
    public Dog requestDog() {
        return new Dog();
    }

    @Bean
    @SessionScope
    public Dog sessionDog() {
        return new Dog();
    }

    @Bean
    @ApplicationScope
    public Dog applicationDog() {
        return new Dog();
    }

}

        测试不同作⽤域的Bean取到的对象是否⼀样:

(这里Autowired注入,从Spring中拿对象,先是根据类名查找,如果注入的类名在Spring容器中只有一个,那就直接获取,如果注入多个同名的类,则按变量名查找,如果根据变量名称查找不到,就会报错;因此可以使用@Qualifiy和@Source指定名称)

@RestController
public class DogController {

    @Autowired
    private ApplicationContext context;

    @Autowired
    private Dog singletonDog;

    @Autowired
    private Dog prototypeDog;

    @Autowired
    private Dog requestDog;

    @Autowired
    private Dog sessionDog;

    @Autowired
    private Dog applicationDog;

    @RequestMapping("/single")
    public String single() {
        //从context获取对象
        Dog dog = context.getBean("singletonDog", Dog.class);
        return "contextDog: " + dog + "</br>Autowired: " + singletonDog;
    }

    @RequestMapping("/prototype")
    public String prototype() {
        //从context获取对象
        Dog dog = context.getBean("prototypeDog", Dog.class);
        return "contextDog: " + dog + "</br>Autowired: " + prototypeDog;
    }

    @RequestMapping("/request")
    public String request() {
        //从context获取对象
        Dog dog = context.getBean("requestDog", Dog.class);
        return "contextDog: " + dog + "</br>Autowired: " + requestDog;
    }

    @RequestMapping("/session")
    public String session() {
        //从context获取对象
        Dog dog = context.getBean("sessionDog", Dog.class);
        return "contextDog: " + dog + "</br>Autowired: " + sessionDog;
    }

    @RequestMapping("/application")
    public String application() {
        //从context获取对象
        Dog dog = context.getBean("applicationDog", Dog.class);
        return "contextDog: " + dog + "</br>Autowired: " + applicationDog;
    }
}

        观察bean的作用域:

(1)singleton

        多次访问,得到的都是同一个对象,并且 @Autowired 和 applicationContext.getBean() 也是同一个对象。(不同浏览器也是一样的)

(2)prototype

        可以看到,每次使用context得到的对象都不同(程序运行时机是请求该接口)

        但使用@Autowired注入进来的bean对象是一样的,这是因为依赖注入在Spring容器启动时,就已经注入了,所以多次请求也不会发生变化。(程序运行时机是项目启动的时候)

        不同浏览器Autowired注入的对象是相同的

(3)request

        可以看到,不同的请求,获取的对象不同。(在一次请求中,@Autowired 和applicationContext.getBean()也是同一个对象。但每次请求,都会重新创建对象)

(4)session

        每次获取的对象都是一样的,因为是同一个浏览器,所以属于同一个会话。(如果换个浏览器就不是一样的了)

(5)application

        每次访问的bean对象都是同一个,因为它是作用在全局的。

Application scope就是对于整个web容器来说,bean的作⽤域是ServletContext级别的。这个和singleton有点类似,区别在于:Application scope是ServletContext的单例,singleton是⼀个ApplicationContext的单例。在⼀个web容器中ApplicationContext可以有多个。(了解即可)


二、Bean的生命周期

        生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做一个对象的生命周期。

        Bean 的生命周期分为以下5个部分:

1、实例化(为Bean分配内存空间)

2、属性赋值(Bean注入和装配,比如@Autowired)

3、初始化

        a、执行各种通知,如 BeanNameAware,BeanFactoryAware,ApplicationContextAware 的接口方法。

        b、执行初始化方法

        (1)xml 定义 init-method。

        (2)使用注解的方式 @PostConstruct。

        (3)执行初始化后置方法(BeanPostProcessor)。

4、使用 Bean

5、销毁 Bean

        a、销毁容器的各种方法,如 @PreDestroy,DisposableBean 接口方法,destroy-method。

        实例化 对应 构造方法,属性赋值 对应 setter方法 的注入。初始化 和 销毁 是用户自定义扩展的两个阶段,可以在实例化之后,类加载完成之前进行自定义“事件”处理。

Bean的生命周期类似下面:

        买房的流程

        (1)先买房(实例化,从无到有)

        (2)装修(设置属性)

        (3)买家电,如洗衣机、冰箱、电视、空调等([各种]初始化,可以入住)

        (4)入住(使用 Bean)

        (5)买房(Bean 销毁)

        执行流程如图:

1、代码演示

@Component
public class BeanLifeComponent implements BeanNameAware {
    private Dog singletonDog;

    //1、实例化(为bean分配空间)
    public BeanLifeComponent() {
        System.out.println("执行构造函数....");
    }

    //2、属性赋值(bean注入和装配,比如@Autowired)
    @Autowired
    public void setSingletonDog(Dog singletonDog) {
        this.singletonDog = singletonDog;
        System.out.println("执行setSingletonDog...");
    }

    //3、初始化
    //执行通知
    @Override
    public void setBeanName(String name) {
        System.out.println("setBeanName");
    }
    //执行初始化方法
    @PostConstruct//这个注解在对BeanLifeComponent对象初始化时就会执行
    public void init() {
        System.out.println("执行PostConstruct....");
    }

    //4、使用bean
    public void use() {
        System.out.println("执行use方法");
    }

    //5、销毁bean
    @PreDestroy//销毁前做的事情
    public void destroy() {
        System.out.println("执行destroy方法...");
    }
}

        执行结果:

通过运⾏结果观察:
        1. 先执⾏构造函数
        2. 设置属性
        3. Bean初始化
        4. 使⽤Bean
        5. 销毁Bean

2、源码阅读

        上面步骤,在源码中均有体现

        创建Bean的代码入口在  AbstractAutowireCapableBeanFactory#createBean

public abstract class AbstractAutowireCapableBeanFactory extends
        AbstractBeanFactory
        implements AutowireCapableBeanFactory {
    protected Object createBean(String beanName, RootBeanDefinition mbd,
                                @Nullable Object[] args)
            throws BeanCreationException {
        //...代码省略
        try {
            // Give BeanPostProcessors a chance to return a proxy instead of the
            target bean instance.
                    //在实例化之前, 是否有快捷创建的Bean, 也就是通过
                    PostProcessorsBeforeInstantiation返回的Bean
            //如果存在, 则会替代原来正常通过target bean⽣成的bean的流程
            Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
            if (bean != null) {
                return bean;
            }
        }
        catch (Throwable ex) {
            throw new BeanCreationException(mbdToUse.getResourceDescription(),
                    beanName,
                    "BeanPostProcessor before instantiation of bean failed", ex);
        }
        try {
            //创建Bean
            //⽅法中包含了实例化、属性赋值、初始化过程
            Object beanInstance = doCreateBean(beanName, mbdToUse, args);
            if (logger.isTraceEnabled()) {
                logger.trace("Finished creating instance of bean '" + beanName +
                        "'");
            }
            return beanInstance;
        }
            //...代码省略
    }
}

        点进去继续看源码:AbstractAutowireCapableBeanFactory#createBean

    protected Object doCreateBean(String beanName, RootBeanDefinition mbd,
                                  @Nullable Object[] args)
            throws BeanCreationException {
        //...代码省略
        // Instantiate the bean.
        if (instanceWrapper == null) {
        //实例化Bean
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        //...代码省略
        //初始化bean
        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            //依据bean definition 完成bean属性赋值
            populateBean(beanName, mbd, instanceWrapper);
            //执⾏bean初始化
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        catch (Throwable ex) {
            if (ex instanceof BeanCreationException &&
                    beanName.equals(((BeanCreationException) ex).getBeanName())) {
                throw (BeanCreationException) ex;
            }
            else {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Initialization of bean
                        failed", ex);
            }
        }
        //...代码省略
        return exposedObject;
    }

        这三个⽅法与三个⽣命周期阶段⼀⼀对应

1. createBeanInstance() -> 实例化
2. populateBean() -> 属性赋值 
3. initializeBean() -> 初始化

        继续点进去 initializeBean

    //初始化Bean
    protected Object initializeBean(String beanName, Object bean, @Nullable
    RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                invokeAwareMethods(beanName, bean);
                return null;
            }, getAccessControlContext());
        }
        else {
            //调⽤的三个Bean开头的Aware⽅法
            invokeAwareMethods(beanName, bean);
        }
        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean,
                    beanName);
        }
        try {
            invokeInitMethods(beanName, wrappedBean, mbd);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
        }
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean,
                    beanName);
        }
        return wrappedBean;
    }
    //调⽤的三个Bean开头的Aware⽅法
    private void invokeAwareMethods(String beanName, Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }
            if (bean instanceof BeanClassLoaderAware) {
                ClassLoader bcl = getBeanClassLoader();
                if (bcl != null) {
                    ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
                }
            }
            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware)
                        bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
            }
        }
    }

三、Spring Boot自动配置

        SpringBoot的自动配置就是当Spring容器启动后,一些配置类,bean对象等就自动存入到了IoC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。

        SpringBoot自动配置,就是指SpringBoot是如何将依赖jar包中的配置类以及Bean加载到Spring IoC容器中的。

主要介绍以下两个方面:

1、Spring 是如何把对象加载到 Spring IoC容器中的。

2、SpringBoot 是如何实现的。

1、Spring 加载 Bean

(1)问题描述

        需求:使用 Spring管理第三方的 jar 包的配置。

        引入第三方的包,其实就是在该项目下,引入第三方代码,我们采用在该项目下创建不同的目录来模拟第三方的代码引入。

        数据准备:

        1、当前目录为:com.example.springprinciple,模拟第三方代码文件在autoconfig目录下

        代码如下:

@Component
public class AutoConfig {
    public void study() {
        System.out.println("start study...");
    }
}

        2、获取AutoConfig这个Bean,代码如下:

@SpringBootTest
class SpringPrincipleApplicationTests {
    @Autowired
    private ApplicationContext applicationContext;
    @Test
    void contextLoads() {
        AutoConfig autoConfig = applicationContext.getBean(AutoConfig.class, "autoConfig");
        System.out.println(autoConfig);
    }
}

        3、运行程序,观察结果:

        观察日志:No qualifying bean of type 'autoconfig.AutoConfig' available

        没有autoconfig.AutoConfig 这个类型的Bean

(2)原因分析

        Spring通过五大注解和@Bean注解可以帮助我们把Bean加载到SpringIoC容器中,以上有个前提就是这些注解类需要和SpringBoot启动类在同一个目录下(@SpringBootApplication 标注的类 就是 SpringBoot项目的启动类)。

        上面启动类所在目录为:com.example.springprinciple,而AutoConfig这个类在autoconfig下,所以SpringBoot并没有扫描到

        当我们引入第三方的Jar包时,第三方的Jar代码目录肯定不在启动类的目录下,如何告诉Spring帮我们管理这些Bean呢?

(3)解决方案

        我们需要指定路径或者引入的文件,告诉Spring,让Spring进行扫描到。常见的解决方案有两种:

1、@ComponentScan 组件扫描

2、@Import 导入(使用 @Import导入的类,会被Spring加载到IoC容器中)

@ComponentScan

        通过@ComponentScan指定扫描路径

@ComponentScan("autoconfig")
@SpringBootApplication
public class SpringPrincipleApplication {

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

}

        运行程序:

        可以看到这次 autoConfig Bean获取到了

Spring 是否使用了这种方式呢?

        非常明显,没有(引入我们引入第三方框架时,没有加扫描路径,比如 Mybatis)

如果Spring Boot采用这种方式,当我们引入大量的第三方依赖,比如 MyBatis、Jackson等时,就需要再启动类上配置不同。

        加入依赖需要扫描包,这种方式会非常繁琐。

@Import

        @Import 导入主要有以下几种形式:

1、导入类

2、导入 ImportSelector 接口实现类

1、导入类
@Import(AutoConfig.class)
@SpringBootApplication
public class SpringPrincipleApplication {

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

}

        运行程序:

        可以看到,这种方式也可以告诉Spring加载autoConfig。

        问题是如果多了一些配置项呢?

@Import({BiteConfig.class, BiteConfig2.class})
@SpringBootApplication
public class SpringAutoconfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }
}

        那是不是就要频繁写多个配置项的类,也非常繁琐,所以Spring Boot 也没有采用这种方式。

2、导入ImportSelector接口实现类
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"autoconfig.AutoConfig", "autoconfig.AutoConfig2"};//需要导⼊的全限定类名
    }
}

        运行程序:

@Import(MyImportSelector .class)
@SpringBootApplication
public class SpringPrincipleApplication {

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

}

        可以看到,我们采用这种方式也可以导入第三方依赖提供的Bean。

(4)问题

        但是他们都有一个明显的问题,就是使用者需要知道第三方依赖中有哪些Bean对象或者配置类。如果漏掉一些Bean,很可能导致我们的项目出现大的事故。

        这对程序员来说非常不友好。

        依赖中有哪些Bean,使用时需要配置哪些bean,第三方依赖最清楚,那能否由第三方依赖来做这些事呢?

        比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都以@EnableXxxx开头的注解,注解中封装的就是@Import注解

        步骤如下:

1、第三方依赖提供注解
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要导⼊哪些类
public @interface EnableAutoConfig {
}

     注解中封装 @Import 注解,导入 MyImportSelector.class

2、在启动类上使用第三方提供的注解
@EnableAutoConfig
@SpringBootApplication
public class SpringPrincipleApplication {

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

}

        运行程序:

        可以看到,这种方式也可以导入第三方依赖提供的Bean。并且这种方式更优雅一点。SpringBoot采用的也是这种方式。

2、SpringBoot原理分析

(1)源码阅读

        SpringBoot是如何帮助我们做的呢?一切的来自起源SpringBoot启动类开始。(@SpringBootApplication 标注的类就是SpringBoot项目的启动类)

@SpringBootApplication
public class SpringPrincipleApplication {

    public static void main(String[] args) {
        //获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(SpringPrincipleApplication.class, args);
        //从Spring上下文获取对象
        BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class);
        beanLifeComponent.use();
    }

}

        这个类和普通类唯一的区别就是 @SpringBootApplication 注解,这个注解也是SpringBoot实现自动配置的核心。

@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}
)}
)
public @interface SpringBootApplication {
//代码省略...
}

        @SpringBootApplication 是一个组合注解,注解中包含了:

1、元注解

JDK中提供了4个标准的用来对注解类型进行注解的注解的注解类,我们称之为 meta-annotation(元注解),他们分别是:

        @Target 描述注解的使用范围(即被修饰的注解可以用在什么地方)

        @Retention 描述注解保留的时间范围

        @Documented 描述在使用 javadoc工具为类生成帮助文档时要保留其注解信息

        @Inherited 使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)

2、@SpringBootConfiguration

        里面就是 @Configuration,标注当前类为配置类,其实只是做了一层封装,改了个名字而已(@Indexed注解,是用来加速应用启动的,不用关心)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
3、@EnableAutoConfiguration(开启自动配置)

        Spring 自动配置的核心注解。

4、@ComponentScan(包扫描)

        可以通过 basePackageClasses 或 basePackages 来定义要扫描的特定包,如果没有定义特定的包,将从声明该注解的类的包开始扫描,这也是为什么 SpringBoot项目声明的注解类必须要在启动类的目录下。

        excludeFilters 自定义过滤器,通常用于排除一些类、注解等

(2)EnableAutoConfiguration

        看下 @EnableAutoConfiguration 注解的实现:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

        这个注解包含两部分:

1、Import({AutoConfigurationImportSelector.class})

        使用@Import注解,导入了实现ImportSelector接口的实现类。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, 
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            //获取自动配置的配置类信息
            AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
}

        selectImports() 方法底层调用 getAutoConfigurationEntry() 方法,获取可自动配置的配置类信息集合

点进去:

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //获取在配置文件中配置的所有自动配置类的集合
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }
    }

        getAutoConfigurationEntry() 方法通过调用getCandidateConfigurations(annotationMetadata, attributes)方法 获取在配置文件中配置的所有自动配置类的集合。

点击去:

//获取所有基于
//METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports⽂件
//META-INF/spring.factories⽂件中配置类的集合
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
			.getCandidates();
		Assert.notEmpty(configurations,
				"No auto configuration classes found in "
						+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

getCandidateConfiguration 方法的功能:

        获取所有基于 METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports⽂件,META-INF/spring.factories⽂件中配置类的集合。

        在引入的起步依赖中,通常都有包含以上两个文件。

        这里面包含了很多第三方依赖的配置文件(连续按两下shift可以查看对应的源码)

1、在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载。

        @Conditional是Spring底层的注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。

2、META-INF/spring.factories文件是Spring内部提供的一个约定俗成的加载方式,只需要在模块的META-INF/spring.factories文件中配置即可,Spring就会把相应的实现类注入到Spring容器中。

注意:会加载所有jar包下的classpath路径下的META-INF/spring.factories文件,这样的文件不止一个。

比如 Redis的配置: RedisAutoConfiguration

        可以看到,配置文件中使用@Bean声明了一下对象,Spring就会自动调用配置类中使用@Bean标识的方法,并把对象注册到Spring IoC容器中。

        在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载

        @Conditional是Spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定条件,那么配置类里边的配置才会生效

2、@AutoConfigurationPackage

        源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

	/**
	 * Base packages that should be registered with {@link AutoConfigurationPackages}.
	 * <p>
	 * Use {@link #basePackageClasses} for a type-safe alternative to String-based package
	 * names.
	 * @return the back package names
	 * @since 2.3.0
	 */
	String[] basePackages() default {};

	/**
	 * Type-safe alternative to {@link #basePackages} for specifying the packages to be
	 * registered with {@link AutoConfigurationPackages}.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 * @return the base package classes
	 * @since 2.3.0
	 */
	Class<?>[] basePackageClasses() default {};

}

        这个注解主要是导入一个配置文件:AutoConfigurationPackages.Registrar.class

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

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

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

        Registrar实现了 ImportBeanDefinitionRegistrar 类,就可以被注解@Import导入到Spring容器里。

        (String[ ]) (new PackageImports(metadata)).getPackageNames().toArray(new String[0]):当前启动类所在的包名。

        所以:@AutoConfigurationPackage 就是将启动类所在的包下面所有的组件都扫描注册到Spring 容器中。

3、总结

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


四、总结

1、Bean的作用域共分为6种:singleton、prototype、request、session、application、websocket

2、Bean的生命周期共分为5大部分:实例化、属性赋值、初始化、使用Bean、销毁Bean

3、SpringBoot的自动配置原理源码入口:@SpringBootApplication 注解,这个注解封装了3个注解:

(1)@SpringBootConfiguration标志当前类为配置类

(2)@ComponentScan进行包扫描(默认扫描的是启动类所在的当前包及其子包)

(3)EnableAutoConfiguration

        1、@Import注解读取当前项目下所有依赖jar包中 META-INF/spring.factories、METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports这两个⽂件 里面定义的配置类(配置类中定义了 @Bean 注解标识的方法)

        2、@AutoConfigurationPackage把启动类所在的包下面所有的组件都注入到 Spring 容器中

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tao滔不绝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值