Java 基础面试题【Spring】二
Spring 是什么?
轻量级的开源的 J2EE 框架。它是一个容器框架,用来装 javabean(java 对象),中间层框架(万能胶)可以起一个连接作用,比如说把 Struts 和 hibernate 粘合在一起运用,可以让我们的企业开发更快、更简洁。
Spring 是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架
从大小与开销两方面而言 Spring 都是轻量级的。
通过控制反转(IoC)的技术达到松耦合的目的
提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发
包含并管理应用对象(Bean)的配置和生命周期,这个意义上是一个容器。
将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架。
BeanFactory 和 ApplicationContext 有什么区别?
ApplicationContext
是BeanFactory
的子接口ApplicationContext
提供了更完整的功能,如下:
继承
MessageSource
,因此支持国际化。统一的资源文件访问方式
提供在监听器中注册 Bean 的事件。
同时加载多个配置文件。
载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的 web 层。
两者区别:
BeanFactroy 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用 getBean()),才对该 Bean 进行加载实例化。这样,我们就不能发现一些存在的 Spring 的配置问题。如果 Bean 的某一个属性没有注入,BeanFacotry 加载后,直至第一次使用调用 getBean 方法才会抛出异常。
ApplicationContext,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext 启动后预载入所有的单实例 Bean,通过预载入单实例 bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的 BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置 Bean 较多时,程序启动较慢。
BeanFactory 通常以编程的方式被创建,ApplicationContext 还能以声明的方式创建,如使用
ContextLoader
。BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但两者之间的区别是:BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册。
描述一下 Spring Bean 的生命周期?
解析类得到 BeanDefinition
如果有多个构造方法,则要推断构造方法
确定好构造方法后,进行实例化得到一个对象
对对象中的加了 @Autowired 注解的属性进行属性填充
回调 Aware 方法,比如 BeanNameAware,BeanFactoryAware
调用 BeanPostProcessor 的初始化前的方法
调用初始化方法
调用 BeanPostProcessor 的初始化后的方法,在这里会进行 AOP
如果当前创建的 bean 是单例的则会把 bean 放入单例池
使用 Bean
Spring 容器关闭时调用 DisposableBean 中 destory()方法
解释下 Spring 支持的几种 bean 的作用域。
singleton
:默认,每个容器中只有一个 bean 的实例,单例的模式由 BeanFactory 自身来维护。该对象的生命周期是与 Spring IOC 容器一致的(但在第一次被注入时才会创建)。prototype
:为每一个 bean 请求提供一个实例。在每次注入时都会创建一个新的对象request
:bean 被定义为在每个 HTTP 请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。session
:与 request 范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,Bean 会随之失效。application
:bean 被定义为在 ServletContext 的生命周期中复用一个单例对象。websocket
:bean 被定义为在 websocket 的生命周期中复用一个单例对象。
global-session:全局作用域,global-session 和 Portlet 应用相关。当你的应用部署在 Portlet 容器中工作时,它包含很多 portlet。如果你想要声明让所有的 portlet 共用全局的存储变量的话,那么这全局变量需要存储在 global-session 中。全局作用域与 Servlet 中的 session 作用域效果相同。
Spring 框架中的单例 Bean 是线程安全的么?
Spring 中的 Bean 默认是单例模式的,框架并没有对 Bean 进行多线程的封装处理。如果 Bean 是有状态的那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变 Bean 的作用域把singleton
改为protopyte
这样每次请求 Bean 就相当于是 new Bean()这样就可以保证线程的安全了。
有状态就是有数据存储功能
无状态就是不会保存数据 controller、service 和 dao 层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
Dao 会操作数据库 Connection,Connection 是带有状态的,比如说数据库事务,Spring 的事务管理器使用 Threadlocal 为不同线程维护了一套独立的 connection 副本,保证线程之间不会互相影响(Spring 是如何保证事务获取同一个 Connection 的)不要在 bean 中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用 ThreadLocal 把变量变为线程私有的,如果 bean 的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized
、lock
、CAS
等这些实现线程同步的方法了。
谈谈你对 IOC 的理解
概括来说主要表现为三点:容器概念
、控制反转
、依赖注入
所谓的容器概念也就是IOC容器
。
实际上就是个 map(key,value),里面存的是各种对象(在 xml 里配置的 bean 节点、@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的 bean 节点,根据全限定类名使用反射创建对象放到 map 里、扫描到打上上述注解的类还是通过反射创建对象放到 map 里。
这个时候 map 里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过 DI 注入(autowired、resource 等注解,xml 里 bean 节点内的 ref 属性,项目启动的时候会读取 xml 节点 ref 属性根据 id 注入,也会扫描这些注解,根据类型或 id 注入;id 就是对象名)。
控制反转:
没有引入 IOC 容器之前,对象 A 依赖于对象 B,那么对象 A 在初始化或者运行到某一点的时候,自己必须主动去创建对象 B 或者使用已经创建的对象 B。无论是创建还是使用对象 B,控制权都在自己手上。
引入 IOC 容器之后,对象 A 与对象 B 之间失去了直接联系,当对象 A 运行到需要对象 B 的时候,IOC 容器会主动创建一个对象 B 注入到对象 A 需要的地方。
通过前后的对比,不难看出来:对象 A 获得依赖对象 B 的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
全部对象的控制权全部上缴给“第三方”IOC 容器,所以,IOC 容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把 IOC 容器比喻成“粘合剂”的由来。
依赖注入:
概括来说就是获得依赖对象的过程被反转了
。控制被反转之后,获得依赖对象的过程由自身管理变为了由 IOC 容器主动注入。依赖注入是实现 IOC 的方法,就是由 IOC 容器在运行期间,动态地将某种依赖关系注入到对象之中。
如何实现一个 IOC 容器
概括来说分为四步:
配置文件配置包扫描路径
递归包扫描获取.class 文件
反射、确定需要交给 IOC 管理的类
对需要注入的类进行依赖注入
大致流程为:
配置文件中指定需要扫描的包路径
定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解
从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以
.class
结尾的文件添加到一个 Set 集合中进行存储遍历这个 set 集合,获取在类上有指定注解的类,并将其交给 IOC 容器,定义一个安全的 Map 用来存储这些对象
遍历这个 IOC 容器,获取到每一个类的实例,判断里面是有有依赖其他的类的实例,然后进行递归注入
谈谈你对 AOP 的理解
没有 AOP 前:
系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。当我们需要为分散的对象引入公共行为的时候,OOP 则显得无能为力。也就是说,OOP 允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在 OOP 设计中,它导致了大量代码的重复,而不利于各个模块的重用。
有了 AOP 后。
产生了 AOP 后,主要作用是:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP 可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情。
如有问题,欢迎加微信交流:w714771310,备注- 技术交流 。或关注微信公众号【码上遇见你】。
版权声明: 本文为 InfoQ 作者【派大星】的原创文章。
原文链接:【http://xie.infoq.cn/article/717e86997c5d43a224339e2b2】。
本文遵守【CC BY-NC-ND】协议,转载请保留原文出处及本版权声明。
评论