Spring
面试总结
Spring
是什么?
Spring
框架是一个java
平台,提供全面基础设施支持开发java
应用程序。
Spring
的主要模块有哪些?
-
Spring Code
:基础模块,主要提供IOC
和DI
功能。 -
Spring Aspects
:该模块对与AspectJ
集成提供支持。 -
Spring AOP
:提供面向切面编程的实现。 -
Spring JDBC
:提供java
数据库连接。 -
Spring JMS
:java
消息服务。【与远程调用机制以及REST接口类似】 -
Spring ORM
:用于支持Hibernate
等ORM
工具。 -
Spring Web
:为创建Web
应用程序提供支持。 -
Spring Test
:提供了对JUnit
和TestNG
测试支持。
Spring
的优点
低侵入式设计,代码污染低,低耦合,复用性高,对主流框架的集成支持
aop
的理解
aop
是面向切面编程,是面向对象的一种补充,用于将那些与业务无关,却对多个对象有影响的公共行为和逻辑,抽取并封装为一个可重用的公共模块,减少重复代码降低模块间的耦合度,提高系统的可维护性
代理模式
静态代理和动态代理
- 静态代理
AspectJ
也称编译时增强,静态代理就是编译阶段生成代理类,将切面织入java
字节码中,运行后就是增强之后的aop
对象 - 动态代理
Spring Aop
动态代理就是不去修改字节码,而是每次运行时在内存中为方法生成一个aop
对象,这个对象包含了目标对象的全部方法,在特定的切点做了增强处理,并回调原对象的方法
- 动态代理基于
JDK
动态代理和CJLIB
动态代理
JDK
动态代理JDK
动态代理只提供接口的代理,不支持类的代理CJLIB
动态代理CJLB
时通过继承的方式做的动态代理,如果某个类被标记为final
,是无法使用CJLIB
做动态代理
Spring AOP
默认是JDK
动态代理。
- 静态代理和动态代理的区别
区别在于生成aop
代理的时机不同,相对来说AspectJ
的静态代理有更好的性能,但是AspectJ
需要特定的编译器处理,而Spring Aop
不需要特定的编译器处理
IOC
的理解
IOC
控制反转,不是一种技术,而是一种设计思想。是创建对象的控制权和转移,以前创建对象的时机和主动权是由自己把控的,而现在是把这种权力转移到spring容器中,由容器根据配置文件去创建实例和管理各个实例间的依赖关系,对象与对象的松散耦合,也就是IOC
让对象的创建不用去new
了,由spring
自动生产,利用java
的反射机制,根据配置文件运行时动态的去创建对象和管理,并调用对象的方法
- 使用
java
反射机制的几种方法
- 通过全限定类名
- 获取构造器对象,通过构造器
new
出来 - 通过
class
对象创建一个实例对象 - 通过
class
对象获得一个属性对象 - 通过
class
对象获得一个方法对象
ioc
控制反转理解:
- 谁控制了谁,控制了什么:
-
ioc
控制了对象,控制了外部资源的获取
- 什么是反转?
- 传统的程序直接获取依赖对象,也就是正转。
- 而反转就是
ioc
容器帮我们创建对象及注入依赖
为何是反转?
- 是因为容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象。
DI
依赖注入理解:
形象的说,由容器动态的将某个依赖关系注入到组件中。
- 谁依赖了谁:
- 应用程序依赖了
ioc
容器
- 为什么要依赖:
- 因为需要
ioc
容器来提供对象需要的外部资源
- 谁注入了谁:
- 是
ioc
容器注入了应用程序某个对象,应用程序依赖的对象
- 注入了什么:
- 就是注入了某个对象所需要的外部资源(包括对象,资源,常量数据等)
ioc
为什么要和DI
同时出现
- 因为他们是同一概念不同角度的描述
IOC
的注入
- 构造器注入
-
setter
方法注入 - 注解注入
【构造器参数实现强制依赖】
【setter方法实现可选依赖】
Spring
中bean
的作用域
singleton:
- 唯一bean实例,Spring中的bean默认都是单例。
prototype
:
- 每次请求都会创建一个新的
bean
实例。(多例)
request
:
- 每次
HTTP
请求都会产生一个新的bean
,该bean
仅在HTTP Request
内有效。
session
:
- 每次
HTTP
都会产生一个新的bean
,该bean
仅在HTTP Session
内有效。
global-session
:
- 全局session作用域,仅仅在基于
Portlet
的Web
应用中才有意义,Spring5
中已经没有了。
BeanFactory
和AppplicationContext
的区别?
BeanFactory
是spring
最底层的接口,包含了各种bean
的定义,管理bean
的加载,实例化,控制bean
的生命周期,维护bean
之间的关系,而ApplicationContext
接口是BeanFactory
的派生,除了有BeanFactory
所有的功能外,还有更加完整的框架功能:
-
ApplicationContext
继承了MessageSource
- 统一的文件访问方式
- 可以在监听器中注册
bean
的事件 - 同时加载多个配置文件
- 载入多个有继承关系的上下文,让每一个上下文都专注与一个特定的层次
-
BeanFactory
是延迟加载的方式来注入bean
,如果有异常,只有在使用bean
的时候,才会抛出,不能及时发现存在spring配置的问题。而ApplicationContext
是容器启动时一次性创建所有bean
,如果有问题,在启动时我们就可以发现了,有利于检查所有依赖属性是否注入(ApplicationContext
启动后预载入所有的单实例bean
) -
BeanFactory
通常是以编程的方式进行创建,而ApplicationContext
可以以声明的方式进行创建(如:ContextLoader
) -
BeanFactory
和ApplictionContext
都支持BeanPostProcessor
、BeanFactoryPostProcessor
的使用,但BeanFactory
是手动注册,而ApplicationContext
是自动注册
Spring Bean
的生命周期
-
Bean容器找到配置文件中Spring Bean的定义
。 -
Bean容器利用Java Reflection API创建一个Bean的实例
。 -
如果涉及到属性值,利用set()方法设置一些属性值
。 -
如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean名字
。 -
如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象实例
。 如果Bean实现BeanFactoryAware接口,调用setBeanClassFactory()方法,传入ClassLoader对象实例。(如果实现了其他Aware接口,就调用相应的方法)
-
如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessorBeforeInitialization()方法
。 -
如果Bean实现了InitializingBean接口, 执行afeterPropertiesSet()方法
。 -
如果Bean在配置文件中定义了包含init-method属性,执行指定的方法
。 -
如果有何加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization()方法
。 -
当要销毁Bean的时候,如果Bean实现了DisposableBena接口,执行destroy()方法
。 -
当要销毁Bean的时候,如果Bean在配置文件中的定义包含destory-method属性,执行指定方法
。
怎么重载Bean
生命周期重要的方法?
-
init-method
和destroy-method
属性,可以用他们来定制初始化方法和注销方法 - 相应的注解:
@PostConstuct
@PreDestroy
Spring
框架中的单例Bean
是否线程安全
大部分时候我们的系统中并没有使用多线程,所以很少去关注这个问题,单例Bean
是存在线程问题的,主要是因为当多个线程对这个对象的非静态成员变量进行写操作时会存在线程安全问题。
有常用的俩种解决方案:
- 在
Bean
对象中尽量避免定义可变的成员变量。(并不现实) - 在类中定义一个
ThreadLoad
成员变量,把需要可变的成员变量保存在ThreadLoad
中。(推荐的一种方式)
Spring
事务的管理方式
- 编程式事务:在代码中硬编码。(不推荐使用)
- 声明式事务:在配置文件中配置。(推荐使用)
- 基于
XML
的声明式事务。 - 基于注解的声明式事务。
Spring
事务中的隔离级别有哪几种?
在TransactionDefinition
接口中定义了五个表示隔离级别的常量
-
ISOLATION_DEFAULT
:使用后端数据库默认的隔离级别,MySQL
默认采用REPEATABLE_READ
隔离级别,Oracle
默认采用的READ_COMMITTED
隔离级别。 -
ISOLATION_READ_UNCOMMITTED
:最低级隔离级别,允许读取尚未提交的数据变更,可能会导致脏读,幻读,不可重复读。 -
ISOLATION_READ_COMMITTED
:允许读取并发事务已提交的数据,可以阻止脏读,但是幻读,不可重复读仍有可能发生。 -
ISOLATION_REPEATABLE_READ
:对同一个字段多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但是幻读仍有可能发生。 -
ISOLATION_SERIALIZABLE
:最高的隔离级别,完全服从ACID的隔离级别。所有事务依次逐个执行,这样事务之间就完全不可能产生干扰。可阻止脏读,不可重复读,幻读。但是会严重影响程序的性能通常情况不会用到该级别。
Spring事务中的传播行为
在TransactionDefiniton
接口中定义了八个表示事务传播行为的常量
支持当前事务的情况:
-
PROPAGATION_REQUIRED
:如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务。 -
PROPAGATION_SUPPORTS
:如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务的方式继续运行。 -
PROPAGATION_MANDATORY
:如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
-
PROPAGATION_REQUIRES_NEW
:创建一个新的事务,如果当前存在事务,则把事务挂起。 -
PROPAGATION_NOT_SUPPORTED
:以非事务的方式运行,如果当前存在事务,则把事务挂起。 -
PROPAGATION_NEVER
:以非事务的方式运行,如果当前存在事务,则抛出异常。
其他情况:
-
PROPAGATION_NESTED
:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则取值等价于PROPAGATION_REQUIRED。
Spring中用到了那些设计模式?
- 工厂设计模式:
Spring
使用工厂代理模式通过BeanFactory
和ApplicationContext
创建对象。 - 代理设计模式:
Spring AOP
功能实现。 - 单例设计模式:
Spring
中的Bean
,默认都是单例的。 - 模板方法模式:
Spring
中的jdbcTempalte
等以Template
结尾的对数据库操作的类,就使用到了模板模式。 - 包装器设计模式:当我们的项目需要连接多个数据库的时候,而且不同的客户在每次访问中会去访问不同的数据库,这种模式可以根据客户的需求动态的切换不同的数据源。
- 观察者模式:
Spring
事件驱动模型就是观察者模式很经典的一个应用。
- 什么是事件驱动模型?
是一种对象间一对多的关系,就像交通信号灯,信号灯是目标(也就是一的一方),行人是多方,行人需要注意观察信号灯的变化,当信号灯变为绿色的时候(也就是目标发送了改变),行人就知道这时候该过马路了(也就是订阅者可以接受到改变)。观察者怎么处理,目标不会干涉,这就松散的他们之间的耦合。就像绿灯的时候行人怎么走,是直着走还是弯着走,是走得快还是走的慢,目标都不会管。
- 适配器模式:
Spring AOP
的增强通知使用到了适配器模式,Spring MVC
也用到了适配模式来适配Controller
。 - 等等…
@Component
和@Bean
的区别是什么?
- 作用对象不同
-
@Component
作用与类。 -
@Bean
作用与方法上。
- 可以用
@Component
注解扫描来自动侦测和自动装配到Spring
容器中。
- 可以使用
@ComponentScan
来定义要扫描的路径。
-
@Bean
注解通常实在标有该注解的方法中产生这个Bean
,告诉Spring
容器,当我需要他的时候在给我。 -
@Bean
比@Component
的自定义性强,很多地方只能用@Bean
来注册Bean
。比如当我们需要使用第三方类库的类的时候,就只能通过@Bean
来实现。
将一类声明为Spring
的Bean
的注解有哪些?
一般我们用@Autowired
注解去自动装配Bean
,而想要把一个类标识为可以使用@Autowired
注解自动装配的Bean
,可以使用如下注解:
-
@Component
注解:通用注解,可以标注任意类。 -
@Repository
注解:对应持久层,也就是dao
层,主要用于数据库的相关操作。 -
@Service
注解:对应服务层,也就是service
层,主要涉及一些复杂的逻辑,需要用到dao
层(@Autowired
注入) -
@Controller
注解:对应控制层,也就是Controller
层,主要用于接受用户请求并调用service
层的方法返回数据给前端页面。
什么是装配?
装配(Bean
装配)就是在Spring
容器中把Bean
组装到一起,前提是容器要知道Bean
的依赖关系,如何通过依赖注入把他们装配到一起。
什么是自动装配?
Spring
容器能够自动装配相互合作的Bean
,容器不需要使用<constructor-arg>
和<property>
配置,能通过Bean
工厂自动处理Bean
之间的协作。
不同方式的自动装配
有五种:
-
no
:默认不自动装配,可以通过ref属性来进行装配。 -
byName
:通过参数名自动装配。 -
byType
:通过参数类型自动装配。 -
construtor
:这个方式类似于byType
,是需要提供给构造器参数。 -
autodetect
:首先尝试使用construtor
来自动装配,如果无法工作,则使用byType
方式。
自动装配的局限性
- 重写:需要使和配置定义依赖,总要重写自动装配。
- 基本数据类型:不能自动装配简单的属性,如基本数据类型,String还有类。
- 模糊特性:自动装配不如显示装配精确,建议使用显示装配。
可以在Spring中注入一个null和一个空字符串吗?
答:可以。
yType`,是需要提供给构造器参数。
-
autodetect
:首先尝试使用construtor
来自动装配,如果无法工作,则使用byType
方式。
自动装配的局限性
- 重写:需要使和配置定义依赖,总要重写自动装配。
- 基本数据类型:不能自动装配简单的属性,如基本数据类型,String还有类。
- 模糊特性:自动装配不如显示装配精确,建议使用显示装配。
可以在Spring中注入一个null和一个空字符串吗?
答:可以。