2021-12-14
7、BeanFactory的作用。【spring底层核心API】
这个是Spring源码里面的最核心的API。地位很重要。BeanFactory是Spring最核心的顶层接口。他是Bean的工厂。主要职责就是生产Bean它实现了简单工厂设计模式。调用getBean方法生产一个beanBeanfactory接口有很多个实现类,但是其中DefaultListableBeanFactory是最强大的工厂,Spring的底层就是使用这个实现类来生产beanBeanFactory也是容器,它管理着bean的生命周期。就像Tomcat是Servlet的容器一样。底层就是:1、最先开始new ApplicationContext上下文对象context。2、new DefaultListableBeanFactory()帮助我们生产bean3、才可以通过第一步得到的context对象的getBean方法获取容器里面的bean
8、BeanDefinition的作用:
这个是研究Spring源码必须掌握的东西。
它主要负责存储bean的定义信息,决定bean的生产方式。因为这些定义信息里面有class,id,scope和lazy等属性,需要根据定义信息的属性来确定bean的生产方式。
什么是bean的定义信息呢?
下面就是bean的定义信息:
<bean class="com.rtl.User" id="user" scope="sington">
<property name="username" value="rtl"></property>
</bean>
这些定义信息必须要先被BeanDefinition对象存储起来,后面BeanFactory需要生产bean之前,一定要拿到bean对应的定义信息的。
一个bean对应一个BeanDefinition
所以存在BeanDefinitionMap来进行存储。它的key就是bean的名字,value就是BeanDefinition对象。
9、BeanFactory和ApplicationContext有什么区别?
Beanfactory管理着Bean的生命周期,是bean的容器。ApplicationContext:这个是Spring的上下文,也可以叫做Spring的容器。其中ApplicationContext有很多种:AnnotationApplicationContext,ClassPathXmlApplicationContext。BeanFactory提供了getBean方法用来生产beancontext也提供了getBean方法。因为ApplicaionContext也实现了BeanFactory接口。 它不生产bean,而是通知BeanFactory去生产Bean。所以AppicationContext里面的getBean就是一个门面方法。BeanFactory和ApplicationContext共同点:都可以作为bean的容器。不同点:1、ApplicationContext是BeanFactory的实现类。2、ApplicationContext做的事情比较多,比如:会自动把那些加了注解@component的Bean注册进来,但是BeanFactory需要手动注册。才能去getBean,否则会报错说找不到bean;加载环境变量;支持多语言;实现事件监听;注册很多对外扩展点;BeanFactory的优点:内存占有率小,可用于嵌入式设备。
10、说下Spring IOC容器的加载过程。【五颗星|非常重要】
【专门去找一下讲了两三个小时的IOC加载过程的公开课视频】难度系数很高。SpringIOC加载的时机:在new一个ApplicatonContext的时候。才会去开始加载IOC。加载的时候,做了哪些事情呢?IOC的加载过程就是Bean的创建过程。【简单版】:bean的创建过程分为四种形态:1、概念态:给这个bean配置了,通过@Bean注解或者xml里面的bean标签进行配置,但是还没有开始new ApplicationContext2、定义态:当我们去new ApplicationContext的时候,IOC容器真正开始加载。首先这些概念态的bean会被注册成定义态。这就是BeanDefinition。配置的这些Bean的定义信息存储到BeanDefinition对象里面。定义态是SpringIOC开始加载的第一种形态。3、纯净态:早期暴露的bean。纯净的意思就是:现在这些依赖注入的属性还没有赋值。4、成熟态:属性已经赋值好了。【详细加载过程】: 1、由概念态转换为定义态的过程:通过调用Beanfactory的后置处理器。【invokeBeanFactoryPostProcessors】,将那些配置好的Bean全部放到BeanDefinition对象里面存储起来2、由定义态进入纯净态:真正开始生产,生产之前需要拿到这个bean的定义信息。看是否是单例,是否是懒加载。满足生产条件的,才会调用BeanFactory的getBean方法真正进行生产bean。得到的bean就是纯净态。3、由纯净态进入到成熟态:得到bean之后,就是为属性赋值了,其实就是DI的实现。根据自动注入的类型byName或者byType或者@Autowired进行自动注入。bean创建完成之后会放到Map集合里面。key是bean的名字,value是bean对象。【总结】:【概念态 到 定义态】1、IOC的加载过程首先是从创建Spring的容器开始的。2、配置好这些bean【xml方式,注解@Component方式】,调用工厂后置处理器来扫描这些配置好的bean,扫描所有的.class,然后看那些有注解@Component的,把这些有注解@Component的类实例化成BeanDefinition。把这些实例化好的BeanDefinition对象装到Map中缓存起来。【从定义态到纯净态】在BeanFactory调用getBean生产bean之前,首先需要拿到所有bean的定义信息BeanDefinition,检查这些bean是否具备生产条件,比如是否是单例的,是否是懒加载的,是否是抽象的等等?如果是单例的,不是抽象的,不是懒加载的。满足这个条件才会在IOC加载的时候,同时创建bean。在创建bean的时候,会看bean是否已经创建过了,如果没有,那就通过反射的方式,实例化对象,这才得到一个纯净态的bean。【纯净态到成熟态】: 实例化之后得到的纯净态的bean,属性还没有注入,解析DI,解析自动注入。注入属性。得到成熟态的bean。初始化,检查这个bean是否需要AOP,如果需要,就创建AOP,不需要不创建。创建完成之后,就将这些成熟态的bean放到单例池(map),key是bean的名字,value就是bean的成熟态的对象。
11、SpringIOC有哪些扩展点,在什么时候调用?【四颗星】
SpringIOC在加载的过程中,底层会对外提供一些扩展接口,一些钩子方法
注册BeanDefinition的时候的一些扩展接口:
扩展接口1:BeanDefinitionRegistryPostProcessor:
在创建bean的过程中,由概念态到定义态的过程中,会通过invokeBeanFactoryPostProcessors。将那些配置过的bean的定义信息存储到BeanDefinition对象中。但是有些特殊情况,bean没有配置过,但是还是希望帮我们注册bean定义。
可以通过这个扩展接口BeanDefinitionRegistryPostProcessor进行实现。这个扩展接口的作用就是动态注册BeanDefinition。调用时机:IOC加载时注册BeanDefinition的时候会调用。
这个扩展接口有两个方法:
1、postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry){}
2、postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
扩展接口2:BeanFactoryPostProcessor :
这个扩展接口里面的方法:
1、postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
这个方法很万能,可以在注册BeanDefinition的时候进行扩展。
调用时机也是在注册BeanDefinition的时候。
联系:扩展接口1是扩展接口2的实现类。
扩展接口1先于扩展接口2调用。
生产bean的时候:在bean的创建过程中会调用九次bean的后置处理器 xxxPostProcessor。
初始化阶段会调用很多Aware扩展接口 xxxAware,且在初始化阶段也会调用很多生命周期的回调接口。
【总结 五个】:
1、在注册BeanDefinition的时候,用来动态注册BeanDefinition的接口BeanDefinitionRegistryPostProcessor和可以拿到bean工厂做任何扩展的BeanFactoryPostProcessor扩展接口
2、在bean生产的过程中,调用九次后置处理器xxxPostProcessor
3、在bean初始化的过程中:调用很多Aware接口和很多生命周期的回调接口。
12、什么是SpringBean?JavaBean和SpringBean和对象三者有什么区别?
1、SpringBean:被SpringIOC容器管理的对象。由SpringIOC实例化,并且组装和管理的对象。
2、JavaBean:java的类
JavaBean是自己实例化出来的。
13、配置Bean有哪几种方式?
1、xml的方式:
<bean class="xxx.User">
2、注解@Component (Controller,Service,Repository三个注解都是属于Component )
前提,需要配置扫描包component-scan
3、javaConfig:通过写java代码的方式,再加上注解@Bean
方式2和3的区别:
方式2通过注解@Component是Spring底层使用反射调用构造方法生成。
方式3:结合配置类(某个类上标上了@Configuration)一起使用,@Bean标在这个配置类的某个方法上,方法返回的对象就是bean。这个对象是可以自己控制实例化过程的new。
4、使用注解@Import。三种方式
方式1:@ImportSelector,返回很多全类名,逗号分开。自动将这些类常见成bean
方式2:@ImportBeanDefinitionRegistrar,这个注解提供了BeanDefinitonRegistry的注册器。
方式3:直接使用@Import(xxx.class),括号里面的class是Component/自动配置类。
14、解释Spring支持的几种bean的作用域?
1、在xml里面写属性scope=xxx
2、在class Car的类体上加注解@Scope设置。
作用域:有单例和多例。
单例:是默认的作用域。
如果当前应用是web应用
还有request作用域,一个请求创建一个对象
还有session作用域,一个会话…
还有application…,一个全局应用…
15、单例bean的优势?(单例的设计模式是什么?)
单例的意思:对象只会创建一次
优势:
1、Spring底层通过反射创建对象这个操作非常损耗性能。所以,单例的话,只创建一次对象,那就会减轻内存的消耗。提高服务器内存的利用率。
2、减少JVM垃圾回收的负担。
3、可以快速从缓存中获取对象。
16、Spring的Bean是线程安全的吗?【经典面试题】
SpringBean默认作用域是单例的。只会实例化一次。
如果这个类里面声明了一些共享成员变量,并且存在读写情况,这个时候在并发的情况下,就会出现线程不安全的问题。
例子:
有一个类UserService,有一个成员变量username和一个成员方法welcome(String uname)
这个welcome方法就是修改username的。
String welcome(String uname){
? username = “welcome”+uname;
? return username;
}
测试:
1、先拿到上下文对象
2、context.getBean(UserService.class),拿到UserService对象
3、开启两个线程。调用UserService对象的welcome方法。
线程1:UserService.welcome(“张三”)
线程2:UserService.welcome(“李四”)
线程1和2访问的是同一个UserService实例对象。
线程1处理完之后,线程2进来了,把线程1的username=张三的值改成了李四。所以这就出现了线程安全里面脏读的问题。
从这个测试中发现Spring的单例对象bean确实会存在线程安全的问题。
【总结】:
单例bean的情况:
如果在类中声明成员变量 并且有读写操作(有状态的),这个时候会出现线程不安全。但是,只需要把成员变量声明在方法中,单例bean就是线程安全的。
之前是:这样写会出现线程安全问题。
class UserService{
private String username;
public String welcome(String uname){
username = "welcome"+uname;
return username;
}
}
但是,如果把成员变量写在方法里面:单例bean就是线程安全的。
class UserService{
public String welcome(String uname){
String username = "welcome"+uname;
return username;
}
}
17、Spring是如何处理线程并发问题?
除了将成员变量声明在方法当中,还有什么方法可以解决线程安全问题?
1、将UserService设置成多例的。scope=prototype
2、将成员变量放在ThreadLocal里面。本地线程。
class UserService{
private ThreadLocal<String> username = new ThreadLocal<>();
public String welcome(String uname){
username.set("welcome"+uname);
return username.get();
}
}
虽然现在线程1和2是操作同一个UserService对象,但是,username是各自绑定在自己的线程上面的。username是各个线程独有的。
3、使用同步锁解决。
class UserService{
private String username;
public synchronized String welcome(String uname){
username = "welcome"+uname;
return username;
}
}
同步锁实现原理:当线程1进来的时候,线程2会被阻塞在外面,只有当1执行完之后,才会释放锁。但是使用同步锁会降低服务器的吞吐量。因为同步锁把并行执行的程序改为了串行执行。
18、Spring实例化bean有几种方式?
1、配置bean可以通过xml或者注解@Component的方式,拿到bean的所有定义信息存储到BeanDefinition中,拿到定义信息,利用反射技术,调用构造方法获取bean实例
2、静态工厂的方式。
在xml配置bean标签的时候,配置属性factory-method。就会调用这个方法,来实例化
3、实例工厂。除了属性factory-method需要指定之外,还需要指定factory-bean,因为此时实例工厂的方法不是static的,还需要创建实例工厂对象,然后再根据对象调用工厂方法。来实例化bean
其实,通过@Configuration+@Bean的方式本质就是通过实例工厂来创建的。
factory-bean就是配置类的全类名。factory-method就是@Bean配置的那个方法。
4、FactoryBean的方式:在类car的实现接口FactoryBean。重写getObject和getObjectType方法。
其中只有方式1是Spring控制对象的创建,其他三种都可以是程序员自己控制bean的实例化过程。另外三种方式更加灵活。
|