天天看点

spring面试题2(2021-12-14)

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配置的那个方法。