1. 什么是Spring框架
Spring
是一款开源的Java开发框架,旨在于提高开发人员的效率,它是很多模块的集合,使用这些模块可以很方便地协助进行开发,基于IoC(Inversion of Control)控制反转
和AOP(Aspect-Oriented Programming)面向切面编程
,可以很方便地对数据库进行访问,可以很方便地集成第三方组件,支持RestfultJava
应用程序的开发
Spring
通过其内置的IoC
和AOP
功能实现了这些功能集成的开箱即用的特性
2. Spring中包含了什么模块?
两大核心模块:
AOP
+IoC
,而AOP
是依赖于IoC
执行的。- Spring-core:这是Spring框架最基础的部分,它提供了依赖注入
DI
特征来实现容器对Bean的管理,核心容器的主要组件就是BeanFactory
,是任何Spring
应用的核心,它将IoC
将应用配置和依赖从实际的应用代码中分离出来了。 - Spring-Context:如果说BeanFactory使得Spring成为容器的话,那么上下文模块就是Spring成为框架的原因。因为它扩展了BeanFactory,增加了对国际化消息,事件传播、验证的支持。
- SpringAOP:Spring在它的AOP模块中提供了面向切面编程的丰富支持,SpringAOP模块为基于Spring的应用程序提供了事务管理服务,提供使用SpringAOP,不用依赖于组件,就可以将声明性事务集成到应用程序中。可以自定义拦截器、切点、日志等操作
- SpringDAO:提供了JDBC的抽象层和异常层次结构,消除了繁琐的JDBC编码和数据库厂商特有的错误代码解析,用于简化JDBC
- SpringORM:Spring提供了ORM模块,Spring并不试图实现自己的ORM方案,而是为ORM框架提供了集成方案
- SpringWebMVC:Spirng为构建web应用提供了一个功能完全的MVC框架,使用IoC对控制逻辑和业务对象提供了完全的分离
- SpringWebFlux模块:SpringFramework支持反映流
ReactiveStream
背压
- Spring-core:这是Spring框架最基础的部分,它提供了依赖注入
3. Spring、SpringMVC、SpringBoot之间的关系
Spring
:包含了多个功能模块,其中最重要的是Spring-core(主要提供了IoC依赖注入功能的支持),Spring中的其他模块(例如Spring MVC)的功能基本都需要依赖于这个模块
SpringMVC:是Spring中一个很重要的模块,主要赋予了Spring快速构建MVC应用程序的能力,MVC是Model、View、Controller
的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码
SpringBoot的出现简化了Spring的配置,如果需要构建MVC架构的Web程序,那么还是需要使用MVC作为框架,但是SpringBoot为我们简化了MVC中的很多配置
4. 对SpringIoC的理解
IoC(Inversion of Control)控制翻转
:它是一种控制思想,而不是一个具体的技术实现,IoC思想简单来说就是将程序中手动创建对象的控制权,交给Spring框架来管理
控制翻转
控制
:指的是对象创建(实例化、管理)的权利反转
:控制权交给外部环境(Spring框架、IoC容器)
将对象之间的相互依赖关系交给IoC
容器来管理,并且由IoC
容器完成对象的注入,这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来,IoC
容器就像一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的
在实际项目中一个Service
类可能依赖了很多其他的类,假如我们需要实例化这个Service
,可能需要每次都搞清楚这个Service
所有底层类的构造函数,如果利用IoC
的话,就只需要配置好,你的这个Service
需要哪些实例作为依赖,然后在需要地方引用就可以了,增加了项目的可维护性而且降低了开发难度
IoC
容器本质上就是一个Map
,这个Map
是以{name} => {bean}
作为键值对格式的一个Map
,Spring
我们一般通过XML
配置来配置Bean
,后来SpringBoot
就以一种简略的注解配置
来替代了XML配置
5. 谈谈对SpringBean的理解
简单来说,Bean
就是那些被IoC
容器所管理的对象,我们需要告诉IoC
容器要注册那些对象,这个是通过配置元数据来实现的,配置元数据可以是XML
文件、注解
或者Java
配置类
如何将一个类声明为Bean
@Component
:通用的注解,可以标注任意一个 Spring组件,如果一个Bean不知道属于哪个层,就可以使用@Component
注解标注@Repository
:对应持久层也就是DAO层,主要用来执行对数据库的操作@Service
:对应服务层,主要涉及一些复杂的逻辑,需要使用到DAO
@Controller
:对应SpringMVC
控制层,主要用来接收用户的请求并且调用Service
层返回数据给前端页面
@Component和@Bean的区别是什么?
@Component
注解是用在类上的,而@Bean
是用在方法上的- SpringIoC容器管理一个或者多个
Bean
,这些Bean
都需要在@Configuration
注解下进行创建,在一个方法下使用@Bean
注解就表明这个方法需要交给Spring
进行管理 @Bean
是一个方法级别上的注解,添加的beanId
为方法名@Component
通常是通过类路径扫描来自动侦测以及自动装配到Spring
容器中,可以使用@ComponentScan
注解定义要扫描的路径,从中找出表示了需要装配的类,自动装配到Spring
的容器中,@Bean
注解通常是我们在标有该注解的方法中定义这个bean
,@Bean
告诉了Spring容器这是某个类的实例,当需要使用到的时候,就将这个Bean
返回来@Bean
注解比@Component
注解的自定义性更强,而且很多地方都只能够通过@Bean
来实现Bean的注册
6. @Autowired和@Resource的区别是什么?
Autowired
属于是Spring的内置注解,默认的注入方式为byType(根据类型进行注入)
,也就是说会优先根据接口类型去匹配并且注入Bean(接口的实现类)
byType
的最大问题在于,当接口存在多个实现类的话,byType
这种方式就无法正确注入对象了,因为这时候Spring会同时找到多个满足条件的选择,默认情况下它不知道选择哪一个
这种情况下,Autowired
的注入方式会自动变成byName
,这个名称通常就是类名首字母小写
比如说有这样的实现类
class SmsServiceImpl1 implements SmsService{}
class SsmServiceImpl2 implements SmsService{}
//正确注入了对象
@Autowired
private SmsService smsServiceImpl1;
//没有正确注入对象
@Autowired
private SmsService smsService;
//可以使用@Qualified
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
@Resource
是属于JDK
提供的注解,默认的注入方式为byName
,如果无法通过名称匹配到对应的Bean
的话,注入方式会变成byType
public @interface Resource {
String name() default "";
Class<?> type() default Object.class;
}
使用方法
@Resource
private SmsService smsService;//byName和byType都无法匹配到Bean
@Resource
private SmsService smsServiceImpl1;//可以正确注入
@Resource(name = "smsServiceImpl1")
private SmsService smsService;//可以正确注入
简单总结
@Autowired是Spring提供的注解,@Resource
是JDK提供的注解
@Autowired默认的注入方式为byType(根据类型进行匹配)
,当有多个候选的话,就会转换为byName
,@Resource
默认注入的方式为byName(根据名字进行匹配)
,在byName
不成功后,才会byType
当一个接口存在多个实现类,@Autowired和@Resource都需要通过名称才能正确匹配到对应的Bean,@Autowired可以通过@Qualified注解来显式指定名称,@Resource可以通过name来显式指定名称
7. Bean的作用域有哪些?
singleton:IoC容器中只有唯一的bean
实例,Spring中的bean默认都是单例的,是对单例设计模式的使用
prototype:每次获取都会创建一个新的bean
实例,也就是说,连续getBean()
两次,得到的bean
实例是不一致的
request(Web服务):每一次的HTTP请求都会产生一个新的bean(请求bean)
,这个bean仅在当前HTTP request
内有效
session(Web服务):每一次来自新Session
的Http
请求都会产生一个新的bean(会话bean)
,该bean仅在当前HTTP session
内有效
application/global-session(Web服务):每一个Web应用在启动的时候创建一个bean(应用该Bean)
,该Bean
仅在当前应用启动时间内有效
websocket:每一次webSocket
会话都会产生一个新的bean
8. 单例Bean存在线程安全问题吗?
大部分时候我们并没有在项目中使用多线程,因此很少会有人关注这个问题,单例Bean存在线程安全问题,主要是因为多个线程操作同一个对象的时候是存在资源的竞争的
常见的有两种解决办法:
- 在Bean中尽量避免定义可变的成员变量
- 在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中
不过大部分的Bean都是无状态的,也就是说没有实例变量,在这种情况下Bean是线程安全的
9. Bean的生命周期说一下
首先Bean
它首先是一个普通的Java对象
,那么Java
对象的初始化大致可以划分为:
类加载检查(如果类被加载过了,那么就不需要加载了,否则就会进入类的初始化流程)=>根据对象所属于的类,为这个对象分配内存空间,通常来说有指针碰撞法和空闲链表法
,在对象内存分配完毕后,先会设置对象头,比如说设置它的hashcode()等=>然后将对象的内存空间对应的变量设置为0值=>然后执行实例化=>该对象不再使用的时候被GC
而对于SpringBean来说,就是
实例化=>属性赋值=>初始化=>销毁
可以这样进行描述,首先对bean
进行初始化,需要设定一个init-method()
,然后在配置文件中显式的配置它,接着对于bean
的优雅删除策略,需要设定一个destory-method()
那么在Bean的初始化过程中,它首先设置了两个切入点,一个切入点是Bean后
处理器before()
方法,这个方法设定在init()
之前实现,然后在init()
之后,还设定了Bean后处理器的after()
方法
然后在执行init()
方法之前还会执行initalizing方法
同时,对于这个Bean
的一些基础属性,比如说设定Bean
的名称,它是在Bean后处理器before()
之前执行的,通过一些专属接口比如说BeanNameAware
、BeanClassLoaderAware
等这些接口来实现
在初始化完毕后,就会投入使用,当Bean
下线的时候,会有两个动作,第一个动作是先检查是否实现DisposableBean接口
,然后执行destory()
,当要销毁Bean
的时候,如果Bean
在配置文件中定义包含有destory-method
属性的时候,就会执行指定的方法
10. 谈谈对SpringAOP的理解
AOP
的作用主要是将我们在业务开发中的那些横向交叉业务进行分离,什么叫横向交叉业务?
就是那些与实际的业务无关,但是却必须在业务模块编写相关的业务代码,比如说日志,在执行之前都要执行日志的记录,比如说事务的开启,在执行数据库操作之前选择是否开启事务,在业务结束之后提交事务,权限控制,在某段特定的业务代码执行之前要判断这个代码的执行者是否有权限。
这样的话可以减少系统的重复代码,降低模块间的耦合度,并且有利于未来的扩张和维护
Spring AOP
是基于动态代理实现的,如果要带代理的对象实现了某个接口,那么Spring AOP
就会使用JDK Proxy
,去创建一个代理对象,而对于没有实现接口的对象,就无法使用JDK Proxy
去代理了,此时Spring AOP
就会使用CG lib
去生成一个被代理对象的子类来作为代理
连接点 Joinpoint
- 在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。
切点 Pointcut
- 在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)
通知 Advice
- 通知又叫增强,就是具体你要织入的代码。
- 通知包括:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
切面 Aspect
- 切点 + 通知就是切面。
织入 Weaving
- 把通知应用到目标对象上的过程。
代理对象 Proxy
- 一个目标对象被织入通知后产生的新对象。
目标对象 Target
11. SpringAOP和AspectJ AOP有什么区别
SpringAOP属于是运行时增强,AspectJ是编译时增强,SpringAOP基于代理实现的,而AspectJ是基于字节码操作实现的,SpringAOP已经集成了AspectJ,如果切面太多了最好使用AspectJ,它比SpringAOP要快很多
AspectJ定义的通知类型有哪些?
- Before(前置通知)
- After(后置通知)
- AfterReturning(返回通知):目标对象的方法调用完成,返回结果值之后触发
- AfterThrowing(异常通知):目标方法的对象在运行中抛出/触发异常后触发
- Around(环绕通知)
切面的执行顺序如何控制
- @Order
- 实现Ordered
12. 谈谈你对SpringMVC的理解
可以这样说,SpringMVC是Spring全家桶中对MVC
架构实现得最淋漓尽致的一个框架
要谈到SpringMVC
,那么就必须要先了解什么是MVC
,MVC
机制是这样的:
- Model:是数据层,在开发中就是处理数据/或者数据本身的组件,比如说DAO/Service等
- View:负责将产生的数据回显给用户,通过页面或者其他形式渲染数据,不负责数据的产生
- Controller:它是MVC架构的核心,实际上它并不参与实际数据的操作,而是说起到一个中转站的作用,它负责解析用户的请求,选定要用什么样的逻辑来处理这些请求参数,然后交给
Model
层来处理数据,等到Model
层返回数据后,将这些做好的数据返回给view
,然后view渲染数据即可
一般来说,MVC架构在开发体现为三层架构
第一层:View层
,也就是前面的页面
第二层:Service层
,负责具体的业务的处理,不涉及到底层数据的操作
第三层:DAO层
,负责具体的数据的处理,它负责与DB操作
第四层:DB,负责具体的数据的持久化工作
13. SpringMVC的核心组件有哪些?
它的核心组件其实也是围绕着Servlet
来进行布局的,首先是Controller
DispatcherServlet:核心的中央处理器,它的作用是将来自用户的请求进行分发,并且将解析完成的数据完成分发
HandlerMapping:在一个Web服务中,通常会有很多的URI,那么如何来确定这些URI
所对应的具体处理逻辑?此时就需要一个容器,通过这个容器就能够找到对应的处理器,然后基于这个处理器来执行相关的操作
HandlerAdapter:它是适配器,简单来说就是基于Handler
和HandlerMapping
之间的桥梁,通常的逻辑就是中央控制器
通过查询HandlerMapping
查询到了具体的Handler
,然后将参数等信息交给HandlerAdapter
,由这个组件完成参数的相关封装,然后将参数传给Handler
使用
Handler:真正业务逻辑的处理者,负责接收来自Adapter
的请求,并且将数据返回回去
ViewResolver:视图解析器,根据Handler
返回的逻辑视图/视图,解析并渲染真正的视图,并且渲染真正的视图,并且传递给DispatcherServlet
14. SpringMVC的执行流程是怎么样的?
首先是客户端发送一个HTTP
请求,然后Dispatcher
就拦截到了这个请求,然后Dispatcher
拿着这个请求所对应的URL,去查找内部的HandlerMapping
,然后就会找到这个URL
所对应的Handler以及一系列的拦截器
接着Dispatcher
就会将这个请求的相关参数给到HandlerAdapter
,这个适配器会将相关的参数进行封装,将封装好的参数传递给具体的Handler
,Handler
完成这些操作之后,会产生一个ModelAndView
,这个包含了具体的数据模型和逻辑视图,逻辑视图并不是用户看到的真正的视图,而是这个视图的一部分,因此Dispatcher
就还要将这个ModelAndView
基于ViewResolve
解析成真正的视图,接着还要完成Model
的渲染,也就是具体的数据体现,它会将数据传给View
,由View
将数据渲染上去,最终将View返回给用户
为什么SpringMVC要这样设计?
这是因为一个Web
服务非常复杂,比如说,基于HTTP协议传输的话,那么在HTTP/1.0
的时候,里面的数据都是文本类型的,那么如果只有简单的三层结构,比如说Controller
负责将请求丢给Service
完事,那么Service
就要负责将一个String解析成对象,还要将计算出来的结果转换为String,然而,这些和业务完全没有关系,那么能否提供一个中间层,它可以在Service都没有察觉的情况下,就将前台的参数转换为Service想要的参数,然后将Serivce计算结果转换成想要的结果呢?
这就是引入HandlerAdapter
以及各种解析器原因了
15. 统一异常处理要怎么做?
统一的异常处理通常是通过AOP+提前注册的Handler
实现的
AOP
是基于捕获到异常的通知实现的,当捕捉到异常后,就会执行相关的Handler
方法