后端学习day1-Spring(八股)--还剩9个没看

一、Spring

1.请你说说Spring的核心是什么

参考答案

Spring框架包含众多模块,如Core、Testing、Data Access、Web Servlet等,其中Core是整个Spring框架的核心模块。Core模块提供了IoC容器、AOP功能、数据绑定、类型转换等一系列的基础功能,而这些功能以及其他模块的功能都是建立在IoC和AOP之上的,所以IoC和AOP是Spring框架的核心。

IoC(Inversion of Control)是控制反转的意思,这是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高,在一个大型的项目中这十分的不利于代码的维护。IoC则可以解决这种问题,它可以帮我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。

说到IoC就不得不说DI(Dependency Injection),DI是依赖注入的意思,它是IoC实现的实现方式,就是说IoC是通过DI来实现的。由于IoC这个词汇比较抽象而DI却更直观,所以很多时候我们就用DI来代替它,在很多时候我们简单地将IoC和DI划等号,这是一种习惯。而实现依赖注入的关键是IoC容器,它的本质就是一个工厂。

AOP(Aspect Oriented Programing)是面向切面编程思想 ,这种思想是对OOP的补充,它可以在OOP的基础上进一步提高编程的效率。简单来说,它可以统一解决一批组件的共性需求(如权限检查、记录日志、事务管理等) 。在AOP思想下,我们可以将解决共性需求的代码独立出来然后通过配置的方式声明这些代码在什么地方、什么时机调用 。当满足调用条件时,AOP会将该业务代码织入到我们指定的位置 ,从而统一解决了问题,又不需要修改这一批组件的代码。

(1)实现依赖注入(DI)的是IOC容器,IOC容器本身就是工厂。

(2)AOP面向切面编程,统一解决一批组件的共性需求(权限检查、记录日志、事务管理)。把共性需求的代码独立出来,通过配置,声明代码在什么地方,什么时机调用。满足调用条件的时候,AOP会将该业务植入到我们所指定的位置。

(3)所以IoC和AOP是Spring框架的核心。

2.说一说你对Spring容器的了解

参考答案

Spring主要提供了两种类型的容器:BeanFactory和ApplicationContext。

(1)BeanFactory:是基础类型的IoC容器 ,提供完整的IoC服务支持。如果没有特殊指定,默认采用延 迟初始化策略 。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IoC容器选择。

(2)ApplicationContext:它是在BeanFactory的基础上构建的 ,是相对比较高级的容器实现 ,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等**。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。**所以,相对于BeanFactory来说,**ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,**容 器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器是比较合适的选择。

3 说一说你对BeanFactory的了解

参考答案
BeanFactory是一个类工厂 ,与传统类工厂不同的是,BeanFactory是类的通用工厂 ,它可以创建并管理各种类的对象 。这些可被创建和管理的对象本身没有什么特别之处,仅是一个POJOSpring称这些被创建和管理的Java对象为Bean 。并且,Spring中所说的Bean比JavaBean更为宽泛一些,所有可以被Spring容器实例化并管理的Java类都可以成为Bean

BeanFactory是Spring容器的顶层接口 ,Spring为BeanFactory提供了多种实现,最常用的是XmlBeanFactory 。但它在Spring 3.2中已被废弃,建议使用XmlBeanDefinitionReaderDefaultListableBeanFactory 替代。BeanFactory最主要的方法就是 getBean(String beanName),该方法从容器中返回特定名称的Bean。


4.说一说你对Spring IOC的理解

参考答案

IoC(Inversion of Control)是控制反转 的意思,这是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高,在一个大型的项目中这十分的不利于代码的维护。IoC则可以解决这种问题,它可以帮我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。

说到IoC就不得不说DI(Dependency Injection),DI是依赖注入的意思,它是IoC实现的实现方式,就是说IoC是通过DI来实现的。由于IoC这个词汇比较抽象而DI却更直观,所以很多时候我们就用DI来代替它,在很多时候我们简单地将IoC和DI划等号,这是一种习惯。而实现依赖注入的关键是IoC容器,它的本质就是一个工厂。

在具体的实现中,主要由三种注入方式:

(1)构造方法注入

就是被注入对象可以在它的构造方法中声明依赖对象的参数列表 ,让外部知道它需要哪些依赖对象。然后,IoC Service Provider 会检查被注入的对象的构造方法 ,取得它所需要的依赖对象列表进而为其注入相应的对 象。构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。

(2)setter方法注入

通过setter方法,可以更改相应的对象属性 。所以,当前对象 只要为其依赖对象所对应的属性 添加setter方法 ,就可以通过setter方法将相应的依赖对象设置到被注入对象中 。setter方法注入虽不像构造方法注入那样,让对象构造完成后即可使用,但相对来说更宽松一些, 可以在对象构造完成后再注入。

(3)接口注入

相对于前两种注入方式来说,接口注入没有那么简单明了。被注入对象如果想要IoC Service Provider为其注入依赖对象 ,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。相对于前两种依赖注入方式,接口注入比较死板和烦琐。

总体来说,构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式。而接口注入因为侵入性较强,近年来已经不流行了。

个人总结:IOC容器实现依赖注入的三种方式

(1)构造方法注入:入侵性弱易使用和理解。被注入对象可以在它的构造方法中声明依赖对象的参数列表IoC Service Provider会检查被注入的对象的构造方法 *,取得它所需要的依赖对象列表进而为其注入相应的对 象。(可以在当前对象构造完成后使用)

(2)setter方法注入:入侵性弱易使用和理解。通过setter方法,可以更改相应的对象属性。当前对象在依赖对象设置setter方法,该setter方法会把依赖对象 设置到被注入的对象。(可以在当前对象构造完成后注入)

(3)接口注入:入侵性强,不流行。被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口,这个接口提供一个方法,用来为其注入依赖对象。

5 Spring是如何管理Bean的?

参考答案

Spring通过IoC容器来管理Bean ,我们可以通过XML配置或者注解配置 ,来指导IoC容器对Bean的管理 。因为注解配置比XML配置方便很多,所以现在大多时候会使用注解配置的方式。

以下是管理Bean时常用的一些注解:

@ComponentScan用于声明扫描策略,通过它的声明,容器就知道要扫描哪些包下带有声明的类,也可以知道哪些特定的类是被排除在外的。

@Component、@Repository、@Service、@Controller用于声明Bean ,它们的作用一样,但是语义不同。@Component用于声明通用的Bean
@Repository用于声明DAO层的Bean
@Service用于声明业务层的Bean
@Controller用于声明视图层的控制器Bean
被这些注解声明的类就可以被容器扫描并创建。

@Autowired、@Qualifier用于注入Bean即告诉容器应该为当前属性注入哪个Bean 。其中,@Autowired是按照Bean的类型进行匹配的,如果这个属性的类型具有多个Bean,就可以通过@Qualifier指定Bean的名称,以消除歧义

@Scope用于声明Bean的作用域,默认情况下Bean是单例的,即在整个容器中这个类型只有一个实例。可以通过@Scope注解指定prototype值将其声明为多例的 ,也可以将Bean声明为session级作用域、request级作用域等等,但最常用的还是默认的单例模式。

@PostConstruct、@PreDestroy用于声明Bean的生命周期。

其中,被**@PostConstruct修饰的方法将在Bean实例化后被调用**,@PreDestroy修饰的方法将在容器销毁前被调用






6 介绍Bean的作用域

参考答案

默认情况下,Bean在Spring容器中是单例的 ,我们可以通过**@Scope注解修改Bean的作用域**。该注解有如下5个取值,它们代表了Bean的5种不同类型的作用域:

7 说一说Bean的生命周期(重点但是很难懂,需要再看)

参考答案

Spring容器管理Bean,涉及对Bean的创建、初始化、调用、销毁等一系列的流程 ,这个流程就是Bean的生命周期。整个流程参考下图:

这个过程是由Spring容器自动管理的,其中有两个环节我们可以进行干预。

我们可以自定义初始化方法并在该方法前增加@PostConstruct注解届时Spring容器将在调用SetBeanFactory方法之后调用该方法。

我们可以自定义销毁方法,并在该方法前增加@PreDestroy注解,届时Spring容器将在自身销毁前,调用这个方法。

8 Spring是怎么解决循环依赖的?(需要再理解)

首先,需要明确的是spring对循环依赖的处理有三种情况:

构造器的循环依赖:这种依赖spring是处理不了的,直接抛出BeanCurrentlylnCreationException异常。

单例模式下的setter循环依赖:通过"三级缓存"处理循环依赖。

非单例循环依赖:无法处理。

接下来,我们具体看看spring是如何处理第二种循环依赖的。

Spring单例对象的初始化大略分为三步:

createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象;

populateBean:填充属性,这一步主要是多bean的依赖属性进行填充;

initializeBean:调用spring xml中的init 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一步、第二步。也就是构造器循环依赖和field循环依赖。 Spring为了解决单例的循环依赖问题,使用了三级缓存。

java 复制代码
/** Cache of singleton objects: bean name --> bean instance */ 
private final Map singletonObjects = new ConcurrentHashMap(256);
 /** Cache of singleton factories: bean name --> ObjectFactory */
  private final Map> singletonFactories = new HashMap>(16); 
 /** Cache of early singleton objects: bean name --> bean instance */ 
 private final Map earlySingletonObjects = new HashMap(16);

这三级缓存的作用分别是:

singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存);

earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存);

singletonObjects:完成初始化的单例对象的cache(一级缓存)。

我们在创建bean的时候,会首先从cache中获取这个bean,这个缓存就是sigletonObjects。主要的调用方法是:

java 复制代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {     Object singletonObject = this.singletonObjects.get(beanName);     //isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中 
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))
     {        
 synchronized (this.singletonObjects) {           
 singletonObject = this.earlySingletonObjects.get(beanName);             //allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象   
if (singletonObject == null && allowEarlyReference) {                
 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);                 if (singletonFactory != null) {      
singletonObject = singletonFactory.getObject();  
//从singletonFactories中移除,并放入earlySingletonObjects中。                  
//其实也就是从三级缓存移动到了二级缓存                     this.earlySingletonObjects.put(beanName, singletonObject);                     this.singletonFactories.remove(beanName);             
    }          
       }      
          }     
          }   
 return (singletonObject != NULL_OBJECT ? singletonObject : null); }

从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:

java 复制代码
public interface ObjectFactory<T> 
{     T getObject() throws BeansException; }

这个接口在AbstractBeanFactory里实现,并在核心方法doCreateBean()引用下面的方法:

java 复制代码
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {     
Assert.notNull(singletonFactory, "Singleton factory must not be null");     synchronized (this.singletonObjects)
 {    
     if (!this.singletonObjects.containsKey(beanName))
      {           

this.singletonFactories.put(beanName, singletonFactory);             this.earlySingletonObjects.remove(beanName);             this.registeredSingletons.add(beanName);        
      }    
       } 
       }

这段代码发生在createBeanInstance之后,populateBean()之前,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,此时将这个对象提前曝光出来,让大家使用。

这样做有什么好处呢?让我们来分析一下"A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象"这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

9 @Autowired和@Resource注解有什么区别?

参考答案

@Autowired是Spring提供的注解,
@Resource是JDK提供的注解。

@Autowired是只能按类型注入,
@Resource默认按名称注入,也支持按类型注入。

(1)@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。

(2)@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上 ,即默认取字段的名称 作为bean名称寻找依赖对象

注解标注在属性的setter方法上 ,即默认取属性名作为bean名称寻找依赖对象

需要注意的是,@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。

10 Spring中默认提供的单例是线程安全的吗?

参考答案

不是。

Spring容器本身并没有提供Bean的线程安全策略
如果单例的Bean是一个无状态的Bean ,即线程中的操作不会对Bean的成员执行查询以外的操作 ,那么这个单例的Bean是线程安全的 。比如,Controller、Service、DAO这样的组件,通常都是单例且线程安全的。
如果单例的Bean是一个有状态的Bean ,则可以采用ThreadLocal对状态数据做线程隔离,来保证线程安全。

相关推荐
想躺平的咸鱼干4 分钟前
Volatile解决指令重排和单例模式
java·开发语言·单例模式·线程·并发编程
hqxstudying31 分钟前
java依赖注入方法
java·spring·log4j·ioc·依赖
·云扬·39 分钟前
【Java源码阅读系列37】深度解读Java BufferedReader 源码
java·开发语言
春生野草1 小时前
关于SpringMVC的整理
spring
Bug退退退1232 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠2 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
The_cute_cat2 小时前
Ajax和Axios的初步学习
前端·学习·ajax
Zz_waiting.2 小时前
Javaweb - 10.4 ServletConfig 和 ServletContext
java·开发语言·前端·servlet·servletconfig·servletcontext·域对象
全栈凯哥2 小时前
02.SpringBoot常用Utils工具类详解
java·spring boot·后端
兮动人2 小时前
获取终端外网IP地址
java·网络·网络协议·tcp/ip·获取终端外网ip地址