共计 15779 个字符,预计需要花费 40 分钟才能阅读完成。
1、springmvc 用到的注解,作用是什么,原理。
@Controller 注解
是在 Spring 的 org.springframework.stereotype 包下,org.springframework.stereotype.Controller 注解类型用于指示 Spring 类的实例是一个控制器,使用 @Controller 注解的类不需要继承特定的父类或者实现特定的接口,相对之前的版本实现 Controller 接口变的更加简单。而 Controller 接口的实现类只能处理一个单一的请求动作,而 @Controller 注解注解的控制器可以同时支持处理多个请求动作,使程序开发变的更加灵活。
@Controller 用户标记一个类,使用它标记的类就是一个 Spring MVC Controller 对象,即:一个控制器类。Spring 使用扫描机制查找应用程序中所有基于注解的控制器类,分发处理器会扫描使用了该注解的方法,并检测该方法是否使用了 @RequestMapping 注解,而使用 @RequestMapping 注解的方法才是真正处理请求的处理器。为了保证 Spring 能找到控制器,我们需要完成两件事:
1、在 Spring MVC 的配置文件中的头部引入 spring-context;
2、使用 <context:component-scan/> 元素,该元素的功能是:启动包扫描功能,以便注册带有 @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 初始化过程。
4、SpringMVC 执行流程和原理
SpringMVC 流程:
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 区别
对比项 | @Autowire | @Resource |
---|---|---|
注解来源 | Spring 注解 | JDK 注解(JSR-250 标准注解,属于 J2EE) |
装配方式 | 优先按类型 | 优先按名称 |
属性 | required | name、type |
作用范围 | 字段、setter 方法、构造器 | 字段、setter 方法 |
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/mthou…
15、Springbean 的生命周期
通过这张图能大致看懂 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);
- 然后是怎么具体使用到这个三级缓存的呢,或者说三级缓存的思路?
首先第一步是在 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);}
- 总结一下为什么这么做就能解决 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 方法调用的实际处理者(其实就是帮你代理的那个对象),代理对象的方法调用都会转发到这里,然后直接就能生成你想要的对象类了。
最后
如果对 Java、大数据感兴趣请长按二维码关注一波,我会努力带给你们价值。觉得对你哪怕有一丁点帮助的请帮忙点个赞或者转发哦。
关注公众号 【爱编码】,回复2019 有相关资料哦。