🎈个人公众号:🎈 :✨✨✨ 可为编程✨ 🍟🍟
🔑个人信条:🔑 知足知不足 有为有不为 为与不为皆为可为🌵
🍉本篇简介:🍉 本篇由表及里分析Spring-IOC容器始末,如有出入还望指正。关注公众号【可为编程】回复【面试】领取年度最新面试题!!!
上一篇我们讲到了IOC容器,其实就是我们常说的Spring容器,IOC容器是具有依赖注入功能(也就是DI)的容器,负责对象的实例化、对象的初始化,对象和对象之间依赖关系配置、对象的销毁、对使用者提供对象的查找等操作,可以说IOC容器控制了整个对象的生命周期。我们需要使用的对象都由IOC容器进行创建并管理,不需要我们再去手动通过new的方式去创建对象,而是由IOC容器直接帮我们组装好,当我们需要使用的时候直接从IOC容器中直接获取就可以了,这是上一篇我们讲过的内容,这里再回顾一下。
上一篇 Spring中的核心概念
那么spring ioc容器是如何知道需要管理哪些对象呢?
上文说到我们需要给IOC容器提供一个配置清单,这个配置清单支持xml格式和java注解的方式,在配置文件中列出需要让IOC容器管理的对象,以及指定好让IOC容器如何构建某些对象,比如A对象依赖B对象、C对象等对象与对象之间的依赖关系。当spring容器启动的时候,就会去加载这个配置文件,然后将这些对象给组装好以供外部访问者使用。
IOC容器初始化细节
当然,到了springboot的时候有了自动注入的功能,在启动时并不第一时间去直接加载所有Bean的配置文件,而是采用了懒加载的方式。这意味着在应用程序启动后,只有当实际需要使用到配置信息时,才会去加载对应的配置文件。通过加载META-INF/spring.factories文件来实现自动配置。这是一个特殊的配置文件,它包含了各种自动配置类的全限定类名。Spring Boot在启动时会扫描这个文件,并根据pom文件中的配置信息创建和配置应用程序所需的组件。其实本质上还是读取配置文件,只是加载的方式变了。后续到springboot章节咱细聊。
那么说具体一点,IOC容器到底是怎样实现的呢?用了哪种结构?怎么存储?从哪看?
IOC容器其实就是一个Map,本身就是一个CurrentHashMap,这里就涉及到三级缓存,后面我们会详细拿出一篇文章进行讲解,这个Map里面存放的是各种对象,包括在Xml里面配置的Bean节点和我们采用注解的方式进行标注的类,在项目启动的时候会读取配置文件里面的Bean节点,根据全限定类名使用反射机制创建对象放到Map里。扫描到带注解的类也是通过反射机制创建对象并存放到Map中,比如一些注解、xml中的bean节点内的ref属性,项目启动的时候会读取xml节点ref属性,并根据ID进行注入,同时对于注解形式,根据类型或者id进行注入,id也就是对象名称且默认按照驼峰首字母小写的形式进行注入。
定义类
整个IOC容器的启动入口在ClassPathXmlApplicationContext的构造方法中的refresh()方法里,其实在每一个容器接口类里都会存在refresh方法,比如AnnotationConfigApplicationContext、ClassPathXmlApplicationContext、FileSystemXmlApplicationContext你都会发现refresh()方法的身影。该方法调用的是父类AbstractApplicationContext的refresh()方法。BeanDefinitionParserDelegate是 BeanDefinition解析委托类,就是专门解析由xml 转成Document的类,Document里面是以 beans 为根节点的Spring配置文件的全部内容。也就是下面这一区域。
关注公众号【可为编程】回复【面试】领取年度最新面试题!!!
BeanDefinition在Spring框架中用于定义Bean的配置元信息。它包含了Bean的类名、设置父bean名称、是否为primary、Bean行为配置信息(如作用域、自动绑定模式、生命周期回调等)、依赖设置、构造参数、属性设置等内容。BeanDefinition可以被看作是对Bean属性和配置信息的抽象,这些信息被存储在Spring容器中,以便在需要的时候创建和管理Bean。通过这样的设计,Spring将Bean的创建和管理逻辑与具体的实现解耦,使得用户可以更加灵活地配置Bean。
然后将Document类进行解析,载入到BeanDefinition,这个过程非常复杂,在这里就不细细展开。其实BeanDefinition说白了就是引入了一个第三方来统一Bean的格式,因为创建Bean我们可能有多种方式,配置文件、注解、配置类等等,他都会转换成同一种格式文件,到这步只是一个定义类,只是将BeanDefinition信息发布到IOC容器中,此时依旧没有对应的Bean实例创建,没有被初始化,更没有完成依赖注入,也就是没有注入其配置的资源给Bean,那么它还不能完全使用。
初始化
对于初始化和依赖注入,Spring Bean还有一个配置选项lazy-init属性,其含义就是是否初始化SpringBean,在没有任何配置的情况下,它的默认值为default,实际值为false,也就是IOC默认全自动初始化Ben,如果将其设置为tue,那么只有当我们使用SpringIoC容器的getBean方法获取它时,它才会进行Bean的初始化,完成依赖注入。
DI依赖注入
当我们用到这个对象的时候,再通过DI注入。DI注入采用三种方式:
-
构造函数注入:这是在对象创建时通过构造函数参数进行的依赖注入。当Spring容器创建一个新的bean实例时,会调用相应的构造函数,将所需的依赖作为参数传递给该构造函数。
-
Setter方法注入:在这种方式下,依赖注入发生在bean的初始化阶段。首先,Spring容器会调用bean的无参数构造函数创建一个bean实例,然后通过反射调用bean的setter方法,将依赖注入到bean中。
-
注解注入:这是通过在字段上使用@Autowired注解进行的依赖注入。这种方式的注入也发生在bean的初始化阶段,与setter方法注入类似。但需要注意的是,字段注入通常不推荐使用,因为它违反了封装的原则,而且可能会导致不可预见的副作用。
需要注意的是,以上的时机都是相对于Spring容器的生命周期来说的。在Spring容器启动并初始化bean时,依赖注入就会发生。具体的时机取决于选择的依赖注入方式(构造函数、setter、字段)以及配置。构造函数注入能够保证所有的依赖在对象创建后就立即可用,并且它们是final的,不会被修改。而setter方法注入则提供了更大的灵活性,可以在运行时动态更改依赖,但它也带来了更大的复杂性。到这终于把IOC容器的流程大致说了一遍。
关注公众号【可为编程】回复【面试】领取年度最新面试题!!!
Bean的概念
为啥叫Bean不叫其他呢,大家可以自行百度哈。由spring容器管理的对象统称为Bean对象。Bean就是普通的java对象,他和我们自己new的对象其实是一样的,只是这些对象是由spring去创建和管理的,我们需要在配置文件中告诉spring容器需要创建哪些bean对象,这就是我们之前说配置文件和注解两种方式,如果采用配置文件,需要先在配置文件中定义好需要创建的bean对象,这些配置统称为bean的元数据配置信息,spring容器通过读取这些bean配置元数据信息来构建和组装我们需要的对象。如果是采用注解的形式,那么spring会扫描指定包下所有带有相关注解的类,并通过AnnotationConfigApplicationContext上下文获取到指定的Bean对象。这里要注意,当我们通过注解的形式进行Bean定义的时候,如果没有指定BeanId,系统会默认通过类名驼峰的形式定义BeanId。这就是Bean的两种加载方式。
@Service
public class KeWeiService {
public KeWeiService() {
System.out.println("基于注解形式创建正在创建KeWeiService--- " + this);
}
}
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(KeWeiService.class);
KeWeiService kw = (KeWeiService) annotationConfigApplicationContext.getBean("keWeiService");
System.out.println(kw);
基于XML的SpringIOC容器初始化步骤
1、引入spring相关的maven配置
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
</dependencies>
2、创建bean配置文件,比如test.xml配置文件
3、在test.xml文件中定义好需要spring容器管理的bean对象
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
">
<!--关注公众号【可为编程】回复【面试】领取年度最新面试题!!!-->
<!--构造方法中增加妻子对象--> <bean class="org.kewei.pojo.Person" id="person">
<constructor-arg type="org.kewei.pojo.Wife" ref="wife"/>
</bean>
<bean class="org.kewei.pojo.Wife" id="wife" autowire-candidate="true">
<property name="age" value="18"/>
<property name="name" value="可为"/>
</bean>
</beans>
4、创建spring容器,并给容器指定需要装载的bean配置文件,当spring容器启动之后,会加载这些配置文件,然后创建好配置文件中定义好的bean对象,将这些对象放在容器中以供使用。
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("test.xml");
Person person = (Person) classPathXmlApplicationContext.getBean("person");
System.out.println(person.getWifeName() + person.getWifeAge());
}
5、通过容器提供的方法获取容器中的对象,然后调用Person中的方法获取Wife的年龄和名字。
IOC容器对象分析
Spring内部提供了很多表示Spring容器的接口和对象,比如BeanFactory接口,ApplicationContext接口,我们来看看比较常见的几个容器接口和具体的实现类。
BeanFactory接口
org.springframework.beans.factory.BeanFactory
spring容器中具有代表性的容器就是BeanFactory接口,这个是spring容器的顶层接口,提供了容器最基本的功能,其他实现类都是基于BeanFactory进行的功能扩展。
常用的几个方法
/按bean的id或者别名查找容器中的bean
Object getBean(String name) throws BeansException
//这个是一个泛型方法,按照bean的id或者别名查找指定类型的bean,返回指定类型的bean对象
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
//返回IOC容器中指定类型的bean对象
<T> T getBean(Class<T> requiredType) throws BeansException;
//获取指定类型bean对象的获取器,这个方法比较特别,以后会专门来讲
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
//判断容器中是否有该Bean
boolean containsBean(String name);
//判断某个Bean对象是否为单例Bean
boolean isSingleton(String name) throws NoSuchBeanDefinitionException
//判断某个Bean是否是原型Bean
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
ApplicationContext接口
org.springframework.context.ApplicationContext
这个接口继承了BeanFactory接口,所以内部包含了BeanFactory所有的功能,并且在其上进行了扩展,增加了很多企业级功能,比如AOP、国际化、事件支持等等。
BeanFactory与ApplicationContext有什么区别?
BeanFactory采用的是延迟加载形式来注入Bean,即只有在使用某个Bean的时候才调用getBean()方法,才对Bean进行加载和实例化。意味着Bean在需要时才进行初始化,而不是在应用启动时立即初始化。这样我们就不会发现一些存在的Spring的配置问题。如果Bean中的某一个属性没有进行注入,当BeanFactory加载后,直到第一次使用调用getBean方法的时候才会抛出异常。
要实现延迟加载,你可以通过在Bean的定义中使用lazy-init属性来配置。例如,在XML配置中,可以这样设置:
<bean id="myBean" class="com.example.MyBean" lazy-init="true"/>
通过设置lazy-init="true",告诉Spring在第一次请求Bean时才初始化它,而不是在应用上下文启动时去加载。一开始我以为是设置了延迟时间吗?诚然不是,这里所说的延迟加载是BeanFactory本身没有默认的延迟时间,只是控制Bean的初始化时机,不涉及具体的延迟时间。
ApplicationContext是在容器启动的时候一次性创建了所有的Bean对象。先根据byType去找再通过byName去找,这样在容器启动的时候就能够发现Spring中存在的错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的BeanFactory,ApplicationContext唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建, 如使用ContextLoader。
BeanFactory与ApplicationContext都支持BeanPostProcessor与BeanFactoryPostProcessor但两者之间的区别是BeanFactory要手动注册,而ApplicationContext则是自动注册。
Spring中的核心概念
不要称之为卷土重来:为什么 Java 仍然会是冠军!
关于高并发你必须知道的几个概念
线程的创建方式对比与线程池相关原理剖析
BigDecimal对象的日常使用汇总
欢迎大家关注公众号【可为编程】,回复【加群】进入微信群,右边为Q群:761374713,成长,进步,编程,技术、掌握更多知识!