Spring 是什么?
是一个 轻量级的控制反转和面向切面的容器框架。
- 控制反转(IOC):一个对象所依赖的其他对象的创建,不由这个对象负责,而是由 容器负责,容器会在对象初始化时就将所需依赖传递给它。实现了 松耦合。
- 面向切面(AOP):可以 分离应用的业务逻辑与系统级服务,如日志。
- 容器:Spring 管理对象的配置和生命周期,从这个意义上说它是一种容器。
- 框架:Spring 将简单的组件配置、组合成复杂的应用 —— 组件组合的典型做法是写在一个 XML 配置文件里。另一方面,Spring 也提供了很多基础功能(事务管理、持久化框架集成等),将 应用逻辑 的开发留给开发者。
Spring 主要有哪些模块?
- 数据访问:JDBC、ORM、Transaction、JMS(Java 消息服务)
- Web:Web、WebMVC、WebFlux(响应式)、WebSocket
- 切面:AOP
- 核心:Beans、Core、Context、Expression
- 测试:Test
IOC 是什么?
IOC 容器用来实现相互依赖的对象之间的解耦,对象不会自己主动去创建所依赖的其他对象,而是 将对象之间的依赖交给 IOC 容器来管理。Spring 通过依赖注入来实现控制反转,常用注入方法有三种:构造器注入、setter 注入、基于注解的注入。
依赖注入指的是 容器动态地将所需依赖注入到组件之中,提升了组件重用的概率。通过 DI,只需要通过简单的配置,无需任何代码就能指定目标所需的资源,完成自身的业务逻辑,而不需要关系资源具体来自何处。
三种依赖注入方式的比较
- 属性注入:对于 IoC 容器以外的环境,除非使用反射才能获取它所需要的依赖,因为该类没有提供该属性的 setter 方法或者响应的构造方法来完成该属性的初始化。也就是说,在 IoC 容器之外,不能完成相关依赖的注入。另外,如果出现循环依赖问题,即 A 注入了 B,B 又注入了 A,那么在编译阶段是不能发现问题的,只有使用到这个 Bean 时才会发现。而使用构造器注入,在项目启动时就会报错。
- 构造器注入:能够保证注入的 依赖不可变、不为空、总是能够在返回组件代码时保证 完全初始化 的状态。
- 依赖不可变:指定了
final
关键字。 - 依赖不为空:因为是构造器注入,所以在实例化当前类时,会调用这个构造函数,从而需要传入相关依赖,如果这个依赖指向的 Bean 为空,则会报错。
- 依赖对象为完全初始化状态:调用构造器时,需要传入相关依赖,那么这个依赖需要首先完成实例化,所以返回来的依赖都是初始化之后的状态。
- 依赖不可变:指定了
什么是 AOP?
面向切面编程,可以看成面向对象编程的补充。
面向对象 引入封装、继承、多态等概念来建立一种 对象层次结构,模拟同一类对象的公共行为。而如果需要为分散的无关对象引入公共行为时,OOP 就没有办法了。例如日志代码往往水平地散布在所有对象层次中,与它所处对象的核心功能毫无关系。这种散布各处的无关代码被称为 横切代码,OOP 中横切代码会大量重复,不利于模块的重用。
面向切面将这些散布各处的公共行为封装到一个可重用的模块,将其命名为 切面,以减少代码重复,降低模块耦合。
Spring AOP 和 AspectJ 是什么关系?
- AspectJ 是一个 Java 实现的 AOP 框架,是实际上的 AOP 标准。
- Spring 使用 AspectJ 来做切入点的解析和匹配。
- AspectJ 的织入方式为 静态织入,即在编译期将 aspect 类编译成字节码,在 java 目标类编译时织入;而 Spring AOP 使用 动态织入,即在运行时将 aspect 代码织入到目标类中,使用 JDK 的动态代理来实现。
常用的 Bean 作用域有哪些?
-
singleton:默认为单例,每次请求该 Bean 都会获得同一个实例,容器跟踪它的状态,维护它的生命周期。
-
prototype:每次请求该 Bean 都会返回新的实例,容器只负责创建这个实例,不会跟踪它的状态。
-
request:(Spring MVC)每个 HTTP 请求都会创建一个 Bean。
-
session:(Spring MVC)每个 session 中,每个 Bean 对应一个实例。
单例 Bean 有线程安全问题吗?
当多个线程操作同一个对象时,对非静态成员的写操作 存在线程安全问题,解决方法通常是在类中 定义一个 ThreadLocal 成员,将可变成员保存在 ThreadLocal 中。
Bean 的生命周期是怎样的?
- Spring 根据配置文件中 Bean 的定义,利用反射方式实例化 Bean。
- 将值和依赖的 Bean 注入到属性中。
- 初始化
- Aware 相关接口:若 Spring 检测到 Bean 实现了 Aware 接口,则会为其注入相应的依赖。
BeanNameAware
、BeanClassLoaderAware
、BeanFactoryAware
EnvironmentAware
、EmbeddedValueResolverAware
、ApplicationContextAware
- BeanPostProcessor 为修改 Bean 提供了入口。
- InitializingBean 和 init-method 用于初始化 Bean。
- Aware 相关接口:若 Spring 检测到 Bean 实现了 Aware 接口,则会为其注入相应的依赖。
- 销毁
BeanFactory 和 ApplicationContext 有什么区别?
- 这是两个核心接口,都可以当做 Spring 容器,其中 ApplicationContext 是 BeanFactory 的子接口。
- 功能:
- BeanFactory 是 Spring 的底层接口,维护着 Bean 的定义,负责 Bean 的加载、实例化,控制 Bean 的生命周期,维护 Bean 之间的依赖关系。
- ApplicationContext 是 BeanFactory 的派生,除了支持后者的所有功能,还提供了其他功能:
- 国际化
- 统一的资源文件访问方式
- 同时加载多个配置文件
- 载入多个上下文,使得每个上下文专注于一个特定的层次。
- 加载方式:
- BeanFactory 采用 延迟加载 的方式来注入 Bean,只有在调用
getBean()
时才会对这个 Bean 进行实例化。这样就不能及时发现当前存在的配置问题。 - ApplicationContext 在容器启动时 一次性创建所有 Bean,那么在容器启动时就可以发现存在的配置错误。
- BeanFactory 采用 延迟加载 的方式来注入 Bean,只有在调用
Spring MVC 运行的流程是什么?
- 浏览器发送请求,请求被 DispatcherServlet 捕获;
- DispatcherServlet 调用 HandlerMapping,后者根据请求的 URL,以及注解或者 XML 配置,寻找匹配的 Handler 信息;
- HandlerAdapter 根据 Handler 信息执行具体的 Handler(Controller),返回相应的数据和视图信息,封装至 ModelAndView 对象中,返回给 DispatcherServlet;
- DispatcherServlet 请求 ViewResolver 对 ModelAndView 进行解析,ViewResolver 根据 View 信息匹配到相应的视图结果(template),返回给 DispatcherServlet;
- DispatcherServlet 将 Model 中的数据填充到结果视图中,生成最终的视图;
- 将渲染结果返回给用户。
Spring 框架中用到了哪些设计模式?
-
工厂模式:通过
BeanFactory
、ApplicationContext
创建 Bean。 -
代理模式:AOP 的实现。
-
单例模式:Bean 默认都是单例的。
-
模板方法模式:
jdbcTemplate
操作数据库。 -
适配器模式:Spring MVC 中的 HandlerAdapter。
统一异常处理怎么做?
使用 @ControllerAdvice
+ @ExceptionHandler
这两个注解。
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(BaseException.class)
public ResponseEntity<?> handleAppException(BaseException ex,
HttpServletRequest request) {
//......
}
@ExceptionHandler(value = ResourceNotFoundException.class)
public ResponseEntity<ErrorReponse> handleResourceNotFoundException(
ResourceNotFoundException ex, HttpServletRequest request) {
//......
}
}
这种异常处理方式下,会给所有或者指定的 Controller
织入异常处理的逻辑(AOP),当 Controller
中的方法抛出异常的时候,由被@ExceptionHandler
注解修饰的方法进行处理。
Spring Boot 的核心注解是哪个?
@SpringBootApplication
是核心注解,包含三个注解:
@SpringBootConfiguration
:指定当前类为配置类。@EnableAutoConfiguration
:自动配置所有需要的组件。@ComponentScan
:让 Spring 自动发现所有 Bean,并将它们注册为组件。
@Autowired
和 @Resource
的区别是什么?
-
@Resource
不是 Spring 的注解,全限定名为javax.annotation.Resource
,但是 Spring 支持该注解。 -
@Resource
可用于 字段、setter 方法 上;而@Autowired
可以用在 字段、setter 方法、构造器 上。 -
注入方式:
-
@Autowired
- 按照类型装配依赖对象(如果根据类型找到了多个 Bean,则默认根据字段名来选择。)
- 有一个
required
属性,默认为true
,表示依赖对象必须存在,如果允许null
值,可将其设置为false
。 - 如果想要按照名称来装配,可以 结合
@Qualifier
注解使用。
@AutoWired // 按照 UserDao 类型寻找 Bean 来注入 @Qualifier("user") // Bean 的名字是 user public setUserDao(UserDao user) { this.userDao = user; }
-
@Resource
:- 有两个参数:
name
和type
,如果不指定参数,则 默认按照名称来装配依赖对象。如果使用type
参数,则按照类型来装配依赖对象。整体装配顺序为:- 如果同时指定
name
和type
,则从上下文中寻找 同时满足这两个条件 的 Bean,找不到则抛出异常。 - 如果仅指定
name
,则寻找该名称的 Bean,找不到则抛出异常。 - 如果仅指定
type
,则寻找该类型的 Bean,找不到或者 找到多个 均抛出异常。 - 如果没有指定
name
和type
,则按照 byName 方式进行装配。
- 如果同时指定
- 有两个参数:
-
@Component
和 @Bean
的区别是什么?
- 作用对象:
@Component
注解于类,@Bean
注解于方法。 @Component
是通过自动扫描来装配到 Spring 容器中,@Bean
表示当前方法中可以产生 Bean,告诉 Spring 需要用到这个 Bean 的时候,从这里创建。- 很多地方只能通过
@Bean
来注册 Bean,比如第三方库中的类需要装配到 Spring 容器中时。
Spring Boot 怎么开启事务?
-
在
@Configuration
类上(通常是主类)注解@EnableTransactionManagement
; -
在 Service 方法上添加
@Transactional
注解。
Spring 的事务传播行为有哪些?
事务传播行为描述的是,当一个指定了事务传播行为的方法被另一个方法调用时,事务如何传播。
public void methodA() {
methodB();
// ...
}
@Transactional(Propagation=XXX)
public void methodB() {
// ...
}
外层方法
methodA
是否开启事务是可选的。
- PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事物;否则就加入已存在的事务。
- 外部方法未开启事务时,被
REQUIRED
修饰的内部方法会 开启自己的事务,且不同内部方法开启的事务相互独立,外部方法的异常不会影响内部方法的事务。 - 外部方法开启事务时,被
REQUIRED
修饰的内部方法会 加入外部方法的事务中,内部方法和外部方法同属于一个事务,只要任何一个方法(内部/外部)出现回滚,整个事务(其他所有方法)均需要回滚。
- 外部方法未开启事务时,被
- PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;否则就以非事务执行。
- PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;否则抛出异常。
- PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事物。
- 外部方法未开启事务时,被
REQUIRES_NEW
修饰的内部方法会 开启自己的事务,和其他内部方法的事务相互独立。 - 外部方法开启事务时,被
REQUIRES_NEW
修饰的内部方法 仍然会开启自己的事务,和外部事务相互独立。
- 外部方法未开启事务时,被
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,就抛出异常。
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。
- 外部方法未开启事务时,
NESTED
和REQUIRED
行为相同,内部方法都会开启自己的事务,且彼此独立。 - 外部方法开启事务时,被
NESTED
修饰的内部方法 成为外部事务的子事务,外部事物回滚,子事务一定回滚;子事务内部出现回滚时,如果外部事务察觉不到,就不会影响外部事务,进而不会影响到其他子事务;但若子事务的异常抛到了外部且未被处理,则会导致外部事务一同回滚,进而让其他子事务也回滚。
- 外部方法未开启事务时,