Spring系列面试题

 2022-08-29
原文地址:https://cloud.tencent.com/developer/article/1516796

1、springmvc用到的注解,作用是什么,原理。

@Controller注解 是在Spring的org.springframework.stereotype包下,org.springframework.stereotype.Controller注解类型用于指示Spring类的实例是一个控制器,使用@Controller注解的注解注解的控制器可以同时支持处理多个请求动作,使程序开发变的更加灵活。 @Controller用户标记一个类,使用它标记的类就是一个Spring MVC Controller对象,即:一个控制器类。Spring使用扫描机制查找应用程序中所有基于注解的控制器类,分发处理器会扫描使用了该注解的方法,并检测该方法是否使用了@RequestMapping注解,而使用@RequestMapping注解的方法才是真正处理请求的处理器。为了保证Spring能找到控制器,我们需要完成两件事:

1、在Spring MVC的配置文件中的头部引入spring-context; 2、使用 元素,该元素的功能是:启动包扫描功能,以便注册带有@Controller、@Service、@repository、@Component等注解的类成为Spring的Bean。

@RequestMapping注解

Spring MVC中用于参数绑定的注解有很多,都在org.springframework.web.bind.annotation包中,根据它们处理的request的不同内容可以分为四类(常用的类型)。

第一类:处理request body部分的注解有:@RequestParam和@RequestBody 第二类:处理requet uri部分的注解有:@PathVaribale 第三类:处理request header部分的注解有:@RequestHeader和@CookieValue 第四类:处理attribute类型的注解有:@SessionAttributes和@MoelAttribute

@RequestParam注解

下面来说org.springframework.web.bind.annotation包下的第三个注解,即:@RequestParam注解,该注解类型用于将指定的请求参数赋值给方法中的形参。那么@RequestParam注解有什么属性呢?它有4种属性,下面将逐一介绍这四种属性:

1、name属性 该属性的类型是String类型,它可以指定请求头绑定的名称; 2、value属性 该属性的类型是String类型,它可以设置是name属性的别名; 3、required属性 该属性的类型是boolean类型,它可以设置指定参数是否必须绑定; 4、defalutValue属性 该属性的类型是String类型,它可以设置如果没有传递参数可以使用默认值。

@PathVaribale注解 下面来说org.springframework.web.bind.annotation包下的第四个注解,即:@PathVaribale注解,该注解类型可以非常方便的获得请求url中的动态参数。@PathVaribale注解只支持一个属性value,类型String,表示绑定的名称,如果省略则默认绑定同名参数。

常用的就是以上几个,如需学习更多可以参考下面的链接: https://blog.csdn.net/qian_ch/article/details/73826663

2、springmvc controller方法中为什么不能定义局部变量?。

因为controller是默认单例模式,高并发下全局变量会出现线程安全问题

现这种问题如何解决呢?

第一种方式:既然是全局变量惹的祸,那就将全局变量都编程局部变量,通过方法参数来传递。

第二种方式:jdk提供了java.lang.ThreadLocal,它为多线程并发提供了新思路。

第三种:使用@Scope("session"),会话级别

        @Controller  
        //把这个bean 的范围设置成session,表示这bean是会话级别的,  
        @Scope("session")  
        public class XxxController{  
            private List<String> list ;  
    
          //@PostConstruct当bean加载完之后,就会执行init方法,并且将list实例化;  
            @PostConstruct  
            public void init(){  
                list = new ArrayList<String>();  
            }  
    
        }

第四种:将控制器的作用域从单例改为原型,即在spring配置文件Controller中声明 scope="prototype",每次都创建新的controller

3、Springmvc 中DispatcherServlet初始化过程。

202208292235420891.png

4、SpringMVC执行流程和原理 SpringMVC流程:

202208292235443802.png

01、用户发送出请求到前端控制器DispatcherServlet。

02、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。

03、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。

04、DispatcherServlet调用HandlerAdapter(处理器适配器)。

05、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。

06、Controller执行完成返回ModelAndView对象。

07、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。

08、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。

09、ViewReslover解析后返回具体View(视图)。

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

11、DispatcherServlet响应用户。

5、@autowire和@resource区别

202208292235458503.png

6、SpringMVC中的拦截器和Servlet中的filter有什么区别?

首先最核心的一点他们的拦截侧重点是不同的,SpringMVC中的拦截器是依赖JDK的反射实现的,SpringMVC的拦截器主要是进行拦截请求,通过对Handler进行处理的时候进行拦截,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法(他是介于处理完业务之后和返回结果之前)和afterCompletion方法却会后执行。并且Spring的拦截器是按照配置的先后顺序进行拦截的。

而Servlet的filter是基于函数回调实现的过滤器,Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类)

7、讲讲Spring加载流程。

初始化环境—>加载配置文件—>实例化Bean—>调用Bean显示信息

首先从大的几个核心步骤来去说明,因为Spring中的具体加载过程和用到的类实在是太多了。

(1)、首先是先从AbstractBeanFactory中去调用doGetBean(name, requiredType, final Object[] args, boolean typeCheckOnly【这个是判断进行创建bean还是仅仅用来做类型检查】)方法,然后第一步要做的就是先去对传入的参数name进行做转换,因为有可能传进来的name=“&XXX”之类,需要去除&符号

(2)、然后接着是去调用getSingleton()方法,其实在上一个面试题中已经提到了这个方法,这个方法就是利用“三级缓存” 来去避免循环依赖问题的出现的。【这里补充一下,只有在是单例的情况下才会去解决循环依赖问题】

(3)、对从缓存中拿到的bean其实是最原始的bean,还未长大,所以这里还需要调用getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd)方法去进行实例化。

(4)、然后会解决单例情况下尝试去解决循环依赖,如果isPrototypeCurrentlyInCreation(beanName)返回为true的话,会继续下一步,否则throw new BeanCurrentlyInCreationException(beanName);

(5)、因为第三步中缓存中如果没有数据的话,就直接去parentBeanFactory中去获取bean,然后判断containsBeanDefinition(beanName)中去检查已加载的XML文件中是否包含有这样的bean存在,不存在的话递归去getBean()获取,如果没有继续下一步

(6)、这一步是吧存储在XML配置文件中的GernericBeanDifinition转换为RootBeanDifinition对象。这里主要进行一个转换,如果父类的bean不为空的话,会一并合并父类的属性

(7)、这一步核心就是需要跟这个Bean有关的所有依赖的bean都要被加载进来,通过刚刚的那个RootBeanDifinition对象去拿到所有的beanName,然后通过registerDependentBean(dependsOnBean, beanName)注册bean的依赖

(8)、然后这一步就是会根据我们在定义bean的作用域的时候定义的作用域是什么,然后进行判断在进行不同的策略进行创建(比如isSingleton、isPrototype)

(9)、这个是最后一步的类型装换,会去检查根据需要的类型是否符合bean的实际类型去做一个类型转换。Spring中提供了许多的类型转换器

8、Spring AOP的实现原理。

AOP(Aspect-OrientedProgramming,面向方面编程):是OOP的补充和完善。OOP引入了封装、继承、多态性等建立一种对象层次结构(从上到下的关系)。当需要为分散的对象引入公共行为的时候(从左到右的关系),OOP就显得无能为力。例如:日志功能。日志代码往往水平的散步所有对象层次中,与对象的核心功能毫无关系。这种代码被称为横切(cross-cutting)代码还有像安全性、异常处理、透明的持续性等都称为横切代码。在OOP设计中,它们导致了大量代码的重复,不利于模块的重用。

AOP与OOP相反,利用“横切”技术将影响多个类的公共行为封装到一个可重用模块,称为Aspect。简单点,就是将那些与业务无关,却被业务模块所共同调用的逻辑封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。” Spring提供了两种方式生成代理对象:JDKProxy和Cglib具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

9、讲讲Spring事务的传播属性。

1)、PROPAGATION_REQUIRED —— 支持当前事务,如果当前没有事务,就新建一个事务。(常见的选择)比如ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED,那么由于执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚。 2)、PROPAGATION_SUPPORTS —— 支持当前事务,如果当前没有事务,就以非事务方式执行。 3)、PROPAGATION_MANDATORY ——支持当前事务,如果当前没有事务,就抛出异常。 4)、PROPAGATION_REQUIRES_NEW ——支持当前事务,如果当前没有事务,就将当前事务挂起。如ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,A才继续执行。他与PROPAGATION_REQUIRED的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。 5)、PROPAGATION_NOT_SUPPORTED —— 以非事务方式执行当前操作,如果当前存在事务,就把事务挂起来。 6)、PROPAGATION_NEVER —— 以非事务方式执行,如果当前存在事务,则抛异常。 7)、PROPAGATION_NESTED—— 如果当前存在事务,则在嵌套事务内执行,关键是savepoint。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。与PROPAGATION_REQUIRES_NEW的区别是NESTED的事务和他的父事务是相依的,它的提交是要等父事务一块提交。也就是说,如果父事务最后回滚,它也要回滚。

10、Spring如何管理事务的。

Spring事务管理主要包括3个接口,Spring事务主要由以下三个共同完成的: 1)、PlatformTransactionManager:事务管理器,主要用于平台相关事务的管理。主要包括三个方法:①、commit:事务提交。②、rollback:事务回滚。③、getTransaction:获取事务状态。

2)、TransacitonDefinition:事务定义信息,用来定义事务相关属性,给事务管理器PlatformTransactionManager使用这个接口有下面四个主要方法:①、getIsolationLevel:获取隔离级别。②、getPropagationBehavior:获取传播行为。③、getTimeout获取超时时间。④、isReadOnly:是否只读(保存、更新、删除时属性变为false--可读写,查询时为true--只读)事务管理器能够根据这个返回值进行优化,这些事务的配置信息,都可以通过配置文件进行配置。

3)、TransationStatus:事务具体运行状态,事务管理过程中,每个时间点事务的状态信息。例如:①、hasSavepoint():返回这个事务内部是否包含一个保存点。②、isCompleted():返回该事务是否已完成,也就是说,是否已经提交或回滚。③、isNewTransaction():判断当前事务是否是一个新事务。

11、Spring怎么配置事务(具体说出一些关键的xml 元素)。

配置事务的方法有两种: 1)、基于XML的事务配置。

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- from the file 'context.xml' -->  
    <beans xmlns="http://www.springframework.org/schema/beans"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xmlns:aop="http://www.springframework.org/schema/aop"  
         xmlns:tx="http://www.springframework.org/schema/tx"  
         xsi:schemaLocation="  
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd  
         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">  
    
      <!-- 数据元信息 -->  
      <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
          <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>  
          <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>  
          <property name="username" value="root"/>  
          <property name="password" value="root"/>  
      </bean>  
    
      <!-- 管理事务的类,指定我们用谁来管理我们的事务-->  
      <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
          <property name="dataSource" ref="dataSource"/>  
      </bean>   
    
      <!-- 首先我们要把服务对象声明成一个bean  例如HelloService -->  
      <bean id="helloService" class="com.yintong.service.HelloService"/>  
    
      <!-- 然后是声明一个事物建议tx:advice,spring为我们提供了事物的封装,这个就是封装在了<tx:advice/>中 -->
      <!-- <tx:advice/>有一个transaction-manager属性,我们可以用它来指定我们的事物由谁来管理。
          默认:事务传播设置是 REQUIRED,隔离级别是DEFAULT -->
      <tx:advice id="txAdvice" transaction-manager="txManager">  
          <!-- 配置这个事务建议的属性 -->  
          <tx:attributes>  
            <!-- 指定所有get开头的方法执行在只读事务上下文中 -->  
            <tx:method name="get*" read-only="true"/>  
            <!-- 其余方法执行在默认的读写上下文中 -->  
            <tx:method name="*"/>  
          </tx:attributes>  
      </tx:advice>  
    
      <!-- 我们定义一个切面,它匹配FooService接口定义的所有操作 -->  
      <aop:config>  
         <!-- <aop:pointcut/>元素定义AspectJ的切面表示法,这里是表示com.yintong.service.helloService包下的任意方法。 -->
          <aop:pointcut id="helloServiceOperation" expression="execution(* com.yintong.service.helloService.*(..))"/>  
         <!-- 然后我们用一个通知器:<aop:advisor/>把这个切面和tx:advice绑定在一起,表示当这个切面:fooServiceOperation执行时tx:advice定义的通知逻辑将被执行 -->
           <aop:advisor advice-ref="txAdvice" pointcut-ref="helloServiceOperation"/>  
      </aop:config>  
    
    </beans>

2)、基于注解方式的事务配置。

@Transactional:直接在Java源代码中声明事务的做法让事务声明和将受其影响的代码距离更近了,而且一般来说不会有不恰当的耦合的风险,因为,使用事务性的代码几乎总是被部署在事务环境中。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xmlns:aop="http://www.springframework.org/schema/aop"  
         xmlns:tx="http://www.springframework.org/schema/tx"  
         xsi:schemaLocation="  
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd  
         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">  
    
      <bean id="helloService" class="com.yintong.service.HelloService"/>  
      <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
         <property name="dataSource" ref="dataSource"/>  
      </bean>
      <!-- 配置注解事务 -->  
      <tx:annotation-driven transaction-manager="txManager"/>  
    </beans>

主要在类中定义事务注解@Transactional,如下:

    //@Transactional 注解可以声明在类上,也可以声明在方法上。在大多数情况下,方法上的事务会首先执行
    @Transactional(readOnly = true)    
    public class HelloService{  
        public Foo getFoo(String fooName) {  
        }  
        //@Transactional 注解的事务设置将优先于类级别注解的事务设置   propagation:可选的传播性设置 
        @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)      
        public void updateFoo(Hel hel) {
        }
    }

12、说说你对Spring的理解

1)、Spring是一个开源框架,主要是为简化企业级应用开发而生。可以实现EJB可以实现的功能,Spring是一个IOC和AOP容器框架。 ♧ 控制反转(IOC):Spring容器使用了工厂模式为我们创建了所需要的对象,我们使用时不需要自己去创建,直接调用Spring为我们提供的对象即可,这就是控制反转的思想。 ♧ 依赖注入(DI):Spring使用Java Bean对象的Set方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程就是依赖注入的基本思想。 ♧ 面向切面编程(AOP):在面向对象编程(OOP)思想中,我们将事物纵向抽象成一个个的对象。而在面向切面编程中,我们将一个个对象某些类似的方面横向抽象成一个切面,对这个切面进行一些如权限验证,事物管理,记录日志等公用操作处理的过程就是面向切面编程的思想。 2)、在Spring中,所有管理的都是JavaBean对象,而BeanFactory和ApplicationContext就是Spring框架的那个IOC容器,现在一般使用ApplicationContext,其不但包括了BeanFactory的作用,同时还进行了更多的扩展。

13、Spring非单例注入的原理

在大部分情况下,容器中的bean都是singleton类型的。如果一个singleton bean要引用另外一个singleton bean或者一个非singleton bean要引用另外一个非singleton,通常情况下将一个bean定义为另一个bean的property值就可以了。不过对于具有不同生命周期的bean来说这样做就会有问题了,比如在调用一个singleton类型bean A的某个方法时,需要引用另一个非singleton(prototype)类型的bean B,对于bean A来说,容器只会创建一次,这样就没法在需要的时候每次让容器为bean A提供一个新的的bean B实例。

14、Spring依赖注入原理

我们知道Spring的依赖注入有四种方式,各自是get/set方法注入、构造器注入、静态工厂方法注入、实例工厂方法注入。

https://www.cnblogs.com/mthoutai/p/7278427.html

15、Springbean的生命周期

202208292235475564.png

通过这张图能大致看懂spring的生命周期,详解:

首先会先进行实例化bean对象 然后是进行对bean的一个属性进行设置 接着是对BeanNameAware(其实就是为了让Spring容器来获取bean的名称)、BeanFactoryAware(让bean的BeanFactory调用容器的服务)、ApplicationContextAware(让bean当前的applicationContext可以来取调用Spring容器的服务) 然后是实现BeanPostProcessor 这个接口中的两个方法,主要是对调用接口的前置初始化postProcessBeforeInitialization 这里是主要是对xml中自己定义的初始化方法 init-method = “xxxx”进行调用 然后是继续对BeanPostProcessor 这个接口中的后置初始化方法进行一个调用postProcessAfterInitialization() 其实到这一步,基本上这个bean的初始化基本已经完成,就处于就绪状态 然后就是当Spring容器中如果使用完毕的话,就会调用destory()方法 最后会去执行我们自己定义的销毁方法来进行销毁,然后结束生命周期。

16、Spring中bean的循环依赖怎么解决?

Spring的循环依赖其实就是在进行getBean的时候,A对象中去依赖B对象,而B对象又依赖C对象,但是对象C又去依赖A对象,结果就造成A、B、C三个对象都不能完成实例化,出现了循环依赖。就会出现死循环,最终导致内存溢出的错误。

.如何去解决Spring的循环依赖呢?

1、先知道什么是Spring的“三级缓存”:就是下面的三个大的Map对象,因为Spring中的循环依赖的理论基础其实是基于java中的值传递的,然后其实Spring中的单例对象的创建是分为三个步骤的:

createBeanInstance,其实第一步就是通过构造方法去进行实例化对象。但是这一步只是实例对象而已,并没有把对象的属性也给注入进去 然后这一步就是进行注入实例对象的属性,也就是从这步对spring xml中指定的property进行populate 最后一步其实是初始化XML中的init方法,来进行最终完成实例对象的创建。但是AfterPropertiesSet方法会发生循环依赖的步骤集中在第一步和第二步。

    singletonObjects指单例对象的cache (一级缓存)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
    
    singletonFactories指单例对象工厂的cache(三级缓存)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
    
    earlySingletonObjects指提前曝光的单例对象的cache(二级缓存)
    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

2、然后是怎么具体使用到这个三级缓存的呢,或者说三级缓存的思路?

首先第一步是在Spring中会先去调用getSingleton(String beanName, boolean allowEarlyReference)来获取想要的单例对象。 然后第一步会先进行通过singletonObjects这个一级缓存的集合中去获取对象,如果没有获取成功的话并且使用isSingletonCurrentlyInCreation(beanName)去判断对应的单例对象是否正在创建中(也就是说当单例对象没有被初始化完全,走到初始化的第一步或者第二的时候),如果是正在创建中的话,会继续走到下一步 然后会去从earlySingletonObjects中继续获取这个对象,如果又没有获取到这个单例对象的话,并且通过参数传进来的allowEarlyReference标志,看是不是允许singletonFactories(三级缓存集合)去拿到该实例对象,如果allowEarlyReference为Ture的话,那么继续下一步 此时上一步中并没有从earlySingletonObjects二级缓存集合中拿到想要的实例对象,最后只能从三级缓存singletonFactories (单例工厂集合中)去获取实例对象,

然后把获取的对象通过Put(beanName, singletonObject)放到earlySingletonObjects(二级缓存中),然后在再从singletonFactories(三级缓存)对象中的集合中把该对象给remove(beanName)出去。 附上核心代码

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
     从一级缓存获取
       Object singletonObject = this.singletonObjects.get(beanName);
       if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
          synchronized (this.singletonObjects) {
           从二级缓存获取
             singletonObject = this.earlySingletonObjects.get(beanName);
             if (singletonObject == null && allowEarlyReference) {
              从三级缓存获取
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                   singletonObject = singletonFactory.getObject();
                   this.earlySingletonObjects.put(beanName, singletonObject);
                   this.singletonFactories.remove(beanName);
                }
             }
          }
       }
       return (singletonObject != NULL_OBJECT ? singletonObject : null);}

3、总结一下为什么这么做就能解决Spring中的循环依赖问题。

其实在没有真正创建出来一个实例对象的时候,这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用 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对象也蜕变完美了!一切都是这么神奇!!

总结:Spring通过三级缓存加上“提前曝光”机制,配合Java的对象引用原理,比较完美地解决了某些情况下的循环依赖问题!

https://blog.csdn.net/itmrchen/article/details/90201279 https://blog.csdn.net/qq_36520235/article/details/88257749

17、spring容器的bean什么时候被实例化?

(1)如果你使用BeanFactory作为Spring Bean的工厂类,则所有的bean都是在第一次使用该Bean的时候实例化 (2)如果你使用ApplicationContext作为Spring Bean的工厂类,则又分为以下几种情况: 如果bean的scope是singleton的,并且lazy-init为false(默认是false,所以可以不用设置),则 ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在一个map结构的缓存中,下次再使 用该 Bean的时候,直接从这个缓存中取 如果bean的scope是singleton的,并且lazy-init为true,则该Bean的实例化是在第一次使用该Bean的时候进行实例化。 如果bean的scope是prototype的,则该Bean的实例化是在第一次使用该Bean的时候进行实例化。

18、Spring中AOP的底层是怎么实现的?

Spring中AOP底层的实现其实是基于JDK的动态代理和cglib动态创建类进行动态代理来实现的:

基于JDK的动态代理的原理是:

需要用到的几个关键成员 InvocationHandler (你想要通过动态代理生成的对象都必须实现这个接口) 真实的需要代理的对象(帮你代理的对象) Proxy对象(是JDK中java.lang.reflect包下的)

下面是具体如何动态利用这三个组件生成代理对象

(1)首先你的真是要代理的对象必须要实现InvocationHandler 这个接口,并且覆盖这个接口的invoke(Object proxyObject, Method method, Object[] args)方法,这个Invoker中方法的参数的proxyObject就是你要代理的真实目标对象,方法调用会被转发到该类的invoke()方法, method是真实对象中调用方法的Method类,Object[] args是真实对象中调用方法的参数

(2)然后通过Proxy类去调用newProxyInstance(classLoader, interfaces, handler)方法,classLoader是指真实代理对象的类加载器,interfaces是指真实代理对象需要实现的接口,还可以同时指定多个接口,handler方法调用的实际处理者(其实就是帮你代理的那个对象),代理对象的方法调用都会转发到这里,然后直接就能生成你想要的对象类了。