栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

4.Spring面试题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

4.Spring面试题

4.Spring面试题
  • 1.什么是控制反转IOC?什么是依赖注入DI?
  • 2.SpringIOC创建bean对象的生命周期
  • 3.什么是Spring的AOP?
  • 4.说下SpringAOP里面的几个名词
  • 5.什么是Spring的自动装配?spring自动装配bean有哪些方式
  • 6.Spring中的bean的初始化方式有几种?有什么区别?
  • 7.@Autowired和@Resource的匹配规则
  • 8.Spring IoC 的实现机制。
  • 9.spring 提供了哪些配置方式?
  • 10.Spring开启context:annotation-config和context:component-scan注解的区别
  • 11.Spring中有多少个模块,它们分别是什么
  • 12.SpringAOP中有哪些类型的通知
  • 13.SpringAop的操作方法(纯注解)
  • 14.声明式事务配置的三种方式
  • 15.ApplicationContext和BeanFactory的区别
  • 16.Spring框架中的单例bean是线程安全的吗
  • 17.InitializingBean接口afterPropertiesSet(),DisposableBean接口的destroy()和自定义的init-method,destroy-method的区别
  • 18.Spring中如何注入一个java集合
  • 19.Spring支持的事务管理类型,你更倾向用那种事务管理类型?
  • 20.Spring中@Transactional里的事务的隔离级别和事务传播特性
  • Spring循环依赖问题
    • 1.什么是循环依赖?
    • 2.为什么构造器注入无法解决循环依赖问题?
    • 3.多例的情况下,循环依赖问题为什么无法解决?
    • 4.三级缓存分别是什么?三个Map有什么异同
    • 5.Spring解决循环依赖的流程
    • 6.两个对象在三级缓存中的迁移说明
    • 7.在给B注入的时候为什么要注入一个代理对象?
    • 8.B中提前注入了一个没有经过初始化的A类型对象不会有问题吗?如果开启了AOP初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的是A的代理对象,这样不会有问题吗?
    • 9.为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?
    • 10.那如果使用三级缓存 不使用二级缓存呢?
    • 11.三级缓存真的提高了效率了吗?
    • 12.思考题为什么在下表中的第三种情况的循环依赖能被解决,而第四种情况不能被解决呢?

1.什么是控制反转IOC?什么是依赖注入DI?

IOC就是控制反转,是将组件对象的控制权由程序代码本身交给外部容器管理,所以叫控制反转。
控制反转的作用是:创建对象,维护对象的依赖关系,实现了解耦,降低了维护成本。
DI:我们使用Spring容器的时候,容器通过调用set方法或者是构造器来建立对象之间的依赖关系。控制反转是目标,依赖注入是我们实现控制反转的一种手段。

2.SpringIOC创建bean对象的生命周期

bean的生命周期初步分为四个阶段Bean的实例化阶段,Bean的设置属性阶段,Bean的初始化阶段,Bean的销毁阶段
1.Bean容器利用反射机制对bean进行实例化
2.Bean容器为实例化的bean设置属性值
3.如果bean实现了BeanNameAware接口,则执行setBeanName方法
4.如果bean实现了BeanClassLoaderAware接口,则执行setBeanClassLoader方法
5.如果bean实现了BeanFactoryAware接口,则执行setBeanFactory方法
6.如果bean实现了ApplicationContextAware接口,则执行setApplicationContext方法
7.如果加载了BeanPostProcessor相关实现类,则执行postProcessBeforeInitialization方法
8.如果bean实现了InitializationBean接口,则执行afterPropertiesSet方法
9.如果bean定义了初始化方法(配置文件配置init-method或者添加注解@PostConstruct),则执行定义的初始化方法
10.如果加载了BeanPostProcessor相关实现类,则执行postProcessAfterInitialization方法
11.当要销毁这个bean时,如果Bean实现了DisposableBean接口,则执行destroy方法
12.如果bean自定义了销毁方法(配置文件配置destroy-method或者添加注解@PreDestory),则执行定义的销毁方法
参考文章
源码补充:
Spring中bean的生命周期
AbstractAutowireCapableBeanFactory的doCreateBean方法是创建bean的开始
(1)首先我们要实例化bean,createBeanInstance方法就是采用反射机制生成实例对象。

if (instanceWrapper == null) {
	instanceWrapper = createBeanInstance(beanName, mbd, args);
}

(2)接着需要给实例化的bean设置属性值,populateBean方法就是给属性赋值,如果注入过程中发现有要注入的对象还没有生成,就会先去生产要注入的对象

if (earlySingletonExposure) {
	if (logger.isTraceEnabled()) {
		logger.trace("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	//加入到三级缓存中
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// Initialize the bean instance.
Object exposedObject = bean;
try {
	//给bean设置属性值
	populateBean(beanName, mbd, instanceWrapper);
	//初始化bean
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}

(3)就是对bean进行初始化,调用initializeBean方法

(3.1)initializeBean方法中会先去调用Aware接口的方法invokeAwareMethods,如果bean实现了BeanNameAware,BeanClassLoaderAware,BeanFactoryAware接口的话,会依次调用setBeanName,setBeanClassLoader,setBeanFactory方法;

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);
		}
	}
}

(3.2)接着去调用applyBeanPostProcessorsBeforeInitlization方法,如果发现容器中有类实现了BeanPostProcessor接口,就会执行postProcessBeforeInitlization方法。(如果加载A类到Spring容器,A类也实现了BeanPostProcessor接口,不会调用A类的就会执行postProcessBeforeInitialization方法,因为A类未加载完成,还未完全放到singletonObjects一级缓存中)

@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
		throws BeansException {
		
	Object result = existingBean;
	for (BeanPostProcessor processor : getBeanPostProcessors()) {
		Object current = processor.postProcessBeforeInitialization(result, beanName);
		if (current == null) {
			return result;
		}
		result = current;
	}
	return result;
}

需要注意的是ApplicationContextAwareProcessor也实现了BeanPostProcessor接口,并重写了postProcessBeforeInitlization方法,方法里面调用了invokeAwareInterfaces方法,invokeAwareInterfaces方法也写着如果实现了众多的Aware接口,依次调用相应的方法,值得注意的是ApplicationContextAware接口

private void invokeAwareInterfaces(Object bean) {
    if (bean instanceof EnvironmentAware) {
        ((EnvironmentAware)bean).setEnvironment(this.applicationContext.getEnvironment());
    }

    if (bean instanceof EmbeddedValueResolverAware) {
        ((EmbeddedValueResolverAware)bean).setEmbeddedValueResolver(this.embeddedValueResolver);
    }

    if (bean instanceof ResourceLoaderAware) {
        ((ResourceLoaderAware)bean).setResourceLoader(this.applicationContext);
    }

    if (bean instanceof ApplicationEventPublisherAware) {
        ((ApplicationEventPublisherAware)bean).setApplicationEventPublisher(this.applicationContext);
    }

    if (bean instanceof MessageSourceAware) {
        ((MessageSourceAware)bean).setMessageSource(this.applicationContext);
    }

    if (bean instanceof ApplicationStartupAware) {
        ((ApplicationStartupAware)bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
    }

    if (bean instanceof ApplicationContextAware) {
        ((ApplicationContextAware)bean).setApplicationContext(this.applicationContext);
    }

}

(3.3)接着调用invokeInitMethods方法,发现如果实现了InitlizingBean接口,则会调用afterPropertiesSet方法;最后还会判断是否指定了init-method方法,如果指定了会调用自己定义的初始化方法

boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
	if (logger.isTraceEnabled()) {
		logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
	}
	if (System.getSecurityManager() != null) {
		try {
			AccessController.doPrivileged((PrivilegedExceptionAction) () -> {
				((InitializingBean) bean).afterPropertiesSet();
				return null;
			}, getAccessControlContext());
		}
		catch (PrivilegedActionException pae) {
			throw pae.getException();
		}
	}
 

(3.4)接着调用applyBeanPostProcessorsAfterInitlization方法,如果发现容器中有类实现了BeanPostProcessor接口,就会执行postProcessAfterInitlization
(4)bean的销毁

3.什么是Spring的AOP?

AOP是面向切面编程,能够将那些与业务没有关系,各个模块却都需要共同调用的代码封装起来,例如事务处理,日志管理,权限控制等,可以减少系统的重复代码,降低耦合度,更利于维护和扩展。
SpringAOP是基于动态代理实现的,如果代理对象实现了某个接口,那么SpringAOP会使用JDK的proxy去创建代理对象,如果没有实现接口,就用Cglib生成一个被代理对象的子类。

4.说下SpringAOP里面的几个名词

1.JointPoint连接点:类里面哪些方法可以被增强,这些方法叫连接点。
2.PointCut切入点:用于对哪些Joint Point进行拦截
3.Advice通知:实际增强逻辑部分称为通知,分为前置通知,后置通知,异常通知,最终通知,环绕通知
4.Aspect切面:被抽取出来的公共模块,可以用来横切多个对象。Aspect切面可以看成是PointCut和Advice通知的结合,一个切面可以由多个切入点和通知组成。
5.Target目标对象:包含连接点,被通知的对象。由于SpringAOP是动态代理实现的,所以这个对象永远是一个代理对象
6.weaving:织入:通过动态代理在目标对象的方法(Joint Point)中执行增强逻辑的过程
7.introduction引入:引入是一种特殊的通知,在不修改代码前提下,spring可以在运行期为类动态添加方法和属性

5.什么是Spring的自动装配?spring自动装配bean有哪些方式

在spring中,对象无需自己查找和创建所关联的其它对象,由容器负责把所关联的对象引用给各个对象,使用autowire来配置自动装配模式
在Spring中xml配置一共有四种自动装配

  • no:默认的方式是不进行自动装配,需要通过手动设置ref属性装配
  • byName:根据属性名称注入,类属性的名称要跟bean的id值一致,需要有setter方法才能注入
  • byType:根据属性类型注入,需要有setter方法才能注入
  • constructor: 利用构造函数进行装配,不需要setter方法,需要添加参数带关联对象的构造方法
6.Spring中的bean的初始化方式有几种?有什么区别?

Spring中给bean初始化的方式有有两种
一种是实现InitializingBean接口,重写afterPropertiesSet方法,初始化bean的时候会执行该方法
一种是定义初始化方法,使用配置文件配置init-method,或者给方法添加@PostConstruct注解
如果两种同时使用,先调用afterPropertiesSet方法,后调用自己定义的初始化方法
前者和Spring耦合程度更高但效率高,后者解除了和Spring之间的耦合但是效率低
参考文档

7.@Autowired和@Resource的匹配规则

@Autowired注解可适用于成员变量、setter方法和构造函数
@Autowired注解优先使用根据类型进行标注装配
@Autowired注解配置使用@Qualifier可完成按照名称进行装配
@Autowired注解默认要求依赖对象必须存在,如果要允许null值,则设置它的required属性为false
@Resource注解默认按照名字进行装配,找不到名字匹配的bean时才会按照类型进行装配,但如果使用name属性指定bean的名称,则只会按照名字进行装配。

8.Spring IoC 的实现机制。

Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
示例:

interface Fruit {
     public abstract void eat();
}
class Apple implements Fruit {
    public void eat(){
        System.out.println("Apple");
    }
}
class Orange implements Fruit {
    public void eat(){
        System.out.println("Orange");
    }
}
class Factory {
    public static Fruit getInstance(String ClassName) {
        Fruit f=null;
        try {
            f=(Fruit)Class.forName(ClassName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return f;
    }
}
class Client {
    public static void main(String[] a) {
        Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
        if(f!=null){
            f.eat();
        }
    }
}
9.spring 提供了哪些配置方式?

基于 xml 配置
bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特定于应用程序的配置选项。它们通常以 bean 标签开头。例如:


   

基于注解配置
您可以通过在相关的类,方法或字段声明上使用注解,将 bean 配置为组件类本身,而不是使用 XML 来描述 bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,您需要在使用它之前在 Spring 配置文件中启用它。例如:





基于 Java API 配置
Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现。

  • @Bean 注解扮演与 元素相同的角色。
  • @Configuration 类允许通过简单地调用同一个类中的其他 @Bean 方法来定义 bean 间依赖关系。
    例如:
@Configuration
public class StudentConfig {
    @Bean
    public StudentBean myStudent() {
        return new StudentBean();
    }
}
10.Spring开启context:annotation-config和context:component-scan注解的区别

是用于激活那些已经在spring容器里注册过的bean(无论是xml方式还是通过package scanning)
除了具有的功能以外,还会自动将package包下带有@Component,@Service @Controller等等注解的对象注册到Spring容器
参考文章

11.Spring中有多少个模块,它们分别是什么

Spring核心容器(Core Container)-该层是Spring框架的核心

  • spring core
  • spring bean
  • spring context
  • SpEL

数据访问/集成-该层提供与数据库交互的支持

  • JDBC
  • ORM
  • OXM
  • JMS
  • Transaction

Web-该层提供了创建web应用程序的支持

  • Web
  • Web-Socket
  • Web-Servlet
  • Web-Portlet

AOP 该层支持面向切面编程
Aspects 该层提供了AspectJ的集成提供支持
Test 该层为使用Junit和TestNG进行测试提供支持
Instrumentation – 该层为类检测和类加载器实现提供支持。
Messageing 该模块为 STOMP 提供支持

12.SpringAOP中有哪些类型的通知

前置通知@before
后置通知@AfterReturning
异常通知@AfterThrowing
最终通知@After
环绕通知@Around
五大通知执行顺序(spring5.0)
环绕前置→前置通知→目标方法执行→后置通知/异常通知→最终通知→环绕后置/环绕异常→环绕最终
多个切面的通知顺序
切面1环绕前置→切面1@Before→切面2环绕前置→切面2@Before→目标方法执行→切面2@AfterReturning→切面2@After→切面2环绕返回→切面2环绕最终→切面1@AfterReturning→切面1@After→切面1环绕返回→切面1环绕最终

13.SpringAop的操作方法(纯注解)

创建配置类开启AOP

创建类

创建增强类

14.声明式事务配置的三种方式

xml+事务注解(推荐)
1.跟完全注解区别就是数据源,jdbcTemplate,事务管理器,开启事务的注解用xml配置

完全xml声明式事务管理
1.不需要开启@Transactional注解

完全注解声明式事务管理
1.创建配置类

2.给方法添加@Transactional 也可以添加到类上面表示所有方法都添加事务

3.测试

15.ApplicationContext和BeanFactory的区别

BeanFactory:
是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;

ApplicationContext:
ApplicationContext是BeanFactory的子类,因为古老的BeanFactory无法满足不断更新的spring的需求,ApplicationContext提供了更多高级的功能:

  1. MessageSource, 提供国际化的消息访问
  2. 对web应用的支持
  3. 强大的事件机制
  4. Bean的自动装配
  5. AOP面向切面编程

区别总结

  • 当我们使用ApplicationContext去获取bean的时候,在加载XXX.xml的时候,会创建所有的配置bean。好处是可以预先加载,坏处是浪费内存。
  • 重点:当我们使用BeanFactory去获取Bean的时候,我们只是实例化了该容器,而该容器中的bean并没有被实例化。当我们getBean的时候,才会实时实例化该bean对象(懒加载)。好处是节约内存,坏处是速度比较慢。多用于移动设备的开发。
  • 没有特殊要求的情况下,应该使用ApplicationContext完成。因为BeanFactory能完成的事情,ApplicationContext都能完成,并且提供了更多接近现在开发的功能。
16.Spring框架中的单例bean是线程安全的吗
  • spring单例的bean是否是线程安全的,主要看bean有没有全局变量,如果没有全局变量只有局部变量那就是线程安全的。因为局部变量是线程私有的不共享。
  • 如果有全局变量,那就是有状态的对象,在多线程环境下,这个全局变量会被多个线程所共享,所以是非线程安全的。
  • 对于有状态的对象,最浅显的解决办法就是将bean的作用域由“singleton”变更为“prototype”。

一、局部变量,不存在线程安全问题,因为每个请求都是一个线程请求,局部变量都在线程内不共享(这里指的是单例bean是线程安全的,但是局部变量不一定是线程安全的)

二、全局变量,存在线程安全问题,全局变量是共享数据

17.InitializingBean接口afterPropertiesSet(),DisposableBean接口的destroy()和自定义的init-method,destroy-method的区别

InitializingBean接口、DisposableBean接口底层使用类型强转.方法名()进行直接方法调用,init-method、destory-method底层使用反射,前者和Spring耦合程度更高但效率高,后者解除了和Spring之间的耦合但是效率低,使用哪个看个人喜好

(1)实现InitializingBean,DisposableBean接口

(2)xml配置文件定义init-mehod,destroy-method

也可以使用注解的方式自定义init-method(@PostConstruct)和destory-method(@PreDestroy),需要开启context:annotation-config

18.Spring中如何注入一个java集合

类型用于注入一列值,允许有相同的值。
类型用于注入一组值,不允许有相同的值。
类型用于注入一组键值对,键和值都可以为任意类型。
类型用于注入一组键值对,键和值都只能为String类型。

19.Spring支持的事务管理类型,你更倾向用那种事务管理类型?

Spring支持两种类型的事务管理:

编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。

20.Spring中@Transactional里的事务的隔离级别和事务传播特性


Spring中提供了5种事务的隔离级别
DEFAULT:默认使用的是数据库的隔离级别
READ_UNCOMMITTED: 最低的隔离级别,允许读取事务未提交的数据,可能发生脏读,不可重复读,幻读问题
READ_COMMITTED:只能读取事务提交后的数据,能够解决脏读问题,不能解决不可重复读,幻读问题
REPEATABLE_READ:当一个事务读取一个数据时会对其进行加锁处理,防止其他事务对数据进行修改,如果这个事务不结束,别的事务就不可以更改这个数据,除非这个数据是事务自身修改的。可以解决脏读,不可重复读的问题,不能解决幻读。
Serlalizable:最高的隔离级别,完全服从ACID的隔离级别,所有的事务依次逐个执行,不可能相互干扰。可以解决脏读,不可重复读,幻读问题。

事务的传播特性就是多个事务方法相互调用时,事务如何在这些方法间传播。
Propagation.REQUIRED:默认的传播特性,如果调用者存在事务那就共用一个事务,否则自己会创建一个事务
Propagation.REQUIRES_NEW:无论调用者存不存在事务,都会新建一个事务。如果调用者存在事务就把当前事务挂起(等待新的事务执行完毕后,当前事务才继续执行)
Propagation.REQUIRES_SUPPORTS:如果调用者存在事务那就共用一个事务,如果当前没有事务也可以以非事务方式执行

Propagation.NOT_SUPPORTED:如论当前存不存在事务,都不会新建事务,只能以非事务方式执行。如果调用者存在事务就把当前事务挂起
Propagation.MANDATORY:必须使用当前事务,如果调用者不存在事务就抛出异常
Propagation.NEVER:如果当前有事务,就抛出异常。否则以非事务的方式执行

Propagation.NESTED:如果调用者存在事务,就在嵌套事务内执行,否则就新建一个事务.NESTED的原理就是申明一个嵌套事务来使用保存点功能,达到事务部分回滚的目的。
主事务和嵌套事务属于同一个事务
嵌套事务出错回滚不会影响到主事务
主事务回滚会将嵌套事务一起回滚
参考文档1 参考文档2

Spring循环依赖问题

参考文章1 参考文章2

1.什么是循环依赖?

多个bean之间相互依赖,形成了一个闭环,比如说A依赖B,B依赖C,C依赖A

2.为什么构造器注入无法解决循环依赖问题?

Spring解决循环依赖依靠的提前暴露已经实例化但还没初始化的bean,就是半成品。如果全是通过构造器注入,实例化A的时候必须实例化B,实例化B又要实例化A,那么A就没办法实例化完成,就不可能提前曝光,所以构造器注入无法解决循环依赖。

一种解决方案是使用setter注入,BeanA与BeanB的循环依赖关系,迫使其中一个bean先注入另一个提前暴露的半成品的bean

3.多例的情况下,循环依赖问题为什么无法解决?

只要单例的bean会加入到三级缓存中提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,spring容器不进行缓存,因此无法提前暴露一个创建中的bean,会抛出异常。

4.三级缓存分别是什么?三个Map有什么异同

第一级缓存singletonObjects:存放完整的bean实例,已经实例化和初始化好的实例

private final Map singletonObjects = new ConcurrentHashMap<>(256);

第二级缓存earlySingletonObjects:保存半成品的bean实例(属性未填充完),如果bean被AOP切面处理,保存的是代理的bean实例beanProxy,目标bean还是半成品的

private final Map> singletonFactories = new HashMap<>(16);

第三级缓存singletonFactorys:存放可以生成bean的工厂,传入的值是一个匿名内部类,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法。如果bean被代理getEarlyBeanReference方法返回的是bean的代理对象,如果未被代理,返回的是bean的实例

private final Map earlySingletonObjects = new ConcurrentHashMap<>(16);
5.Spring解决循环依赖的流程



(1)当A,B两个类发生循环依赖,先对A进行实例化,创建实例A的对象工厂添加到三级缓存中提前暴露;如果A被AOP切面处理了,那么从这个工厂中获取到的是实例A的代理对象。如果没有被AOP代理获取的就是A实例化的对象
(2)当给A注入属性B时,会先去创建实例B,跟实例A步骤一样,也会创建一个实例B的对象工厂添加到三级缓存中。
(3)此时给B注入属性A的时候,又会调用getBean()方法获取实例A,这时候会调用getSingleton方法查询缓存中是否有实例A,因为A已经添加到三级缓存中了,所以先找到三级缓存再调用getObject方法获取到对应的对象,并且将A从三级缓存中转移到二级缓存中。之后将这个对象注入到B中。
(4)当B创建完成后会添加到一级缓存,会将B再注入到A中,A会从二级缓存中获取到实例,添加到一级缓存中。

6.两个对象在三级缓存中的迁移说明

(1)假设A,B循环引用,实例化A的时候会将其对象工厂放入三级缓存中提前暴露,如果A被AOP切面处理了,那么从这个工厂中获取到的是实例A的代理对象。否则获取到的就是A的实例化对象。接着填充属性初始化A的时候发现依赖了B,同样的流程将B实例化后也将B的对象工厂放入三级缓存中。
(2)初始化B时发现依赖了A,这时候调用getBean()方法中的getSingleton方法从三级缓存中查找到早期暴露的A,这样beanB就获取到了beanA,并将beanA从三级缓存移动到二级缓存中。beanB初始化完成后会将从三级缓存移动到一级缓存中。
(3)然后接着回来初始化完成A之后从二级缓存移动到一级缓存中。

7.在给B注入的时候为什么要注入一个代理对象?

答:当我们对A进行了AOP代理时,说明我们希望从容器中获取到的就是A代理后的对象而不是A本身,因此把A当作依赖进行注入时也要注入它的代理对象

8.B中提前注入了一个没有经过初始化的A类型对象不会有问题吗?如果开启了AOP初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的是A的代理对象,这样不会有问题吗?

(1)不会,虽然在创建B时会提前给B注入了一个还未初始化的A对象,但是在创建A的流程中一直使用的是B中A对象的引用,之后会根据这个引用对A进行初始化,所以这是没有问题的。

(2)不会,这是因为不管是cglib代理还是jdk动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化相当于代理对象自身也完成了初始化

9.为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

在普通的循环依赖中,三级缓存的作用没有实际意义。可以只用一级缓存和二级缓存解决普通的循环依赖问题。
如果在开启AOP的情况下,如果只使用二级缓存,因为二级缓存存储的value是Object,那么就必须在实例化之后完成AOP代理,这样就违背了Spring的设计原则。因为AOP完成代理应该在Bean生命周期的最后一步,而不是实例化后马上进行AOP代理。三级缓存就可以延迟实例化阶段生成代理对象,因为只需要存储对象的工厂,只要当发生循环依赖的时候,才会调用对象工厂的getObject方法生成代理对象。

10.那如果使用三级缓存 不使用二级缓存呢?

(1)如果这个bean没被AOP切面处理,只使用一级缓存和三级缓存是可以解决循环依赖问题的
(2)如果依赖的bean被AOP代理了,那么调用singletonFactory的getObject()方法从三级缓存中获取到的是该bean的代理对象,如果再执行一遍getObject()方法会发现又会产生新的代理对象。这样是不行的,因为我们创建的bean是单例的。所以我们这个时候必须用二级缓存解决这个问题,将getObject()方法产生的代理对象放到二级缓存中,后面去二级缓存中拿,保证始终只有一个代理对象。所以二级缓存主要作用是用来保存产生的代理对象的。

11.三级缓存真的提高了效率了吗?

三级缓存只是延迟了bean实例化后生成代理对象的时机,并不会提高效率。

现在我们已经知道了三级缓存的真正作用,但是这个答案可能还无法说服你,所以我们再最后总结分析一波,三级缓存真的提高了效率了吗?分为两点讨论:

没有进行AOP的Bean间的循环依赖

从上文分析可以看出,这种情况下三级缓存根本没用!所以不会存在什么提高了效率的说法

进行了AOP的Bean间的循环依赖

就以我们上的A、B为例,其中A被AOP代理,我们先分析下使用了三级缓存的情况下,A、B的创建流程

假设不使用三级缓存,直接在二级缓存中。

上面两个流程的唯一区别在于为A对象创建代理的时机不同,在使用了三级缓存的情况下为A创建代理的时机是在B中需要注入A的时候,而不使用三级缓存的话在A实例化后就需要马上为A创建代理然后放入到二级缓存中去。对于整个A、B的创建过程而言,消耗的时间是一样的

综上,不管是哪种情况,三级缓存提高了效率这种说法都是错误的!

12.思考题为什么在下表中的第三种情况的循环依赖能被解决,而第四种情况不能被解决呢?

提示:Spring在创建Bean时默认会根据自然排序进行创建,所以A会先于B进行创建。

第三种情况,由于A中setter注入B,所以实例化A的时候不会依赖B,A就可以完成实例化提前暴露,B实例化的时候虽然依赖A,但是可以先注册提前暴露半成品的A,所以可以解决循环依赖问题。
第四种情况,由于A实例化依赖B,而B实例化需要A提前暴露,这样A就没办法实例化完成就没办法提前暴露,所以无法解决循环依赖问题。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号