我们会在这里介绍我所涉及到的Spring相关的面试点内容,本篇内容持续更新
我们会介绍下述Spring的相关面试点:
ApplicationContent 是 Spring 中的核心容器,而refresh方法是对Application的初始化,我们下面来学习一下
我们首先给出整体流程以及相关操作:
/*refresh源码展示*/
// refresh只是一个框架,里面一共包含了十二个方法,我们称为十二个步骤
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
/* 第一阶段:整体准备 */
// 1.准备刷新上下文
prepareRefresh();
/* 第二阶段:BeanFactory准备 */
// 2.创建并初始化beanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3.装配beanFactory
prepareBeanFactory(beanFactory);
try {
// 4.在当前上下文环境下对beanFactory进行后置处理操作(子类扩展BeanFactory)
postProcessBeanFactory(beanFactory);
// 5.激活各种BeanFactory的处理器(后处理器扩展BeanFactory)
invokeBeanFactoryPostProcessors(beanFactory);
// 6.注册拦截Bean创建的Bean处理器,即注册BeanPostProcessor(为Bean准备后处理器)
registerBeanPostProcessors(beanFactory);
/* 第三阶段:Applicationcontent准备 */
// 7.为上下文环境初始化MessageSource,即不同语言的消息体,国际化处理操作(为ApplicationContent提供国际化功能)
initMessageSource();
// 8.初始化上下文事件广播器(为ApplicationContent提供事务发布器)
initApplicationEventMulticaster();
// 9.此处是留给子类来初始化其他的bean(留给子类扩展)
onRefresh();
// 10.在所有bean中查找listener bean,然后注册到广播器中(为ApplicationContent提供监听器)
registerListeners();
/* 11 是单独一个阶段:创造单例对象 */
// 11.初始化单例Bean,执行Bean后处理器扩展(第6步创建的Bean后处理器)
finishBeanFactoryInitialization(beanFactory);
// 12.完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
//销毁已经创建的单例bean
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// 抛异常
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
该阶段展示图:
该阶段任务:
Environment环境变量解释:
总体作用:
该阶段展示图:
该阶段任务:
BeanDefinitionMap解释:
总体作用:
该阶段展示图:
该阶段任务:
图中参数解释:
总体作用:
该阶段任务:
总体作用:
该阶段展示图:
该阶段任务:
处理器解析:
该阶段展示图:
该阶段任务:
后处理器介绍:
该阶段展示图:
该阶段任务:
该阶段展示图:
该阶段任务:
该阶段任务:
总体作用:
该阶段展示图:
该阶段任务:
该阶段展示图:
该阶段任务:
详细解释:
该阶段展示图:
该阶段任务:
详细解释:
下面我们来具体介绍一下Spring bean的生命周期
我们首先给出Springbean生命周期的整体阶段展示:
/*生命周期*/
来自AbstractBeanFactory类的doGetBean方法
/* 第一阶段 */
处理名称,检查缓存
/* 第二阶段 */
检查父工厂
/* 第三阶段 */
检查DependsOn
/* 第四阶段 */
按scope创建bean
- 创建singleton
- 创建prototype
- 创建其他scope
/* 第五阶段(重点) */
创建bean实例:@Autowired,唯一带参构造,默认构造
依赖注入:@Autowired,@Value,@Resource,ByName,ByType,精准指定
初始化:Aware接口处理,@PostConstruct,InitializingBean,InitMethod,创建AOP代理
登记可销毁bean
/* 第六阶段 */
类型转换
/* 第七阶段 */
销毁bean
第一阶段作用:
第一阶段注意点:
第二阶段作用:
第二阶段注意点:
第三阶段作用:
第三阶段注意点:
第四阶段作用:
第四阶段注意点:
第五阶段作用:
第五阶段主要分为四个阶段:
我们介绍创建bean实例的两种方法(还有其他方法,下面不做介绍):
要点 | 总结 |
---|---|
AutowiredAnnotationBeanPostProcessor选择构造 | - 优先选择带@Autowired注解的构造- 若带有唯一的带参构造,也会入选 |
采用默认构造 | 如果上述后处理器和BeanDefiniation都没有找到构造,就采用默认构造 |
我们介绍依赖注入的五种方法:
要点 | 总结 |
---|---|
AutowiredAnnotationBeanPostProcessor(注解匹配) | 识别@Autowired和@Value标注的成员封装为InjectionMetadata进行依赖注入 |
CommonAnnotationBeanPostProcessor(注解匹配) | 识别@Resource注解的成员封装为InjectionMetadata进行依赖注入 |
AUTOWIRE_BY_NAME(根据名称匹配) | 根据成员名字找bean对象,修改mbd的propertyValues不考虑简单类型的成员 |
AUTOWIRE_BY_TYPE(根据类型匹配) | 根据成员类型执行resolveDependency找到依赖注入的值修改mbd的propertyValues |
applyPropertyValues(即xml中<proprty name ref>) | 根据mbd的propertyValues进行依赖注入 |
我们需要了解上面五种依赖注入的优先级:
我们来介绍初始化的相关操作:
Aware接口 | 总结 |
---|---|
内置Aware接口的装配 | 包括BeanNameAware,BeanFactoryAware等 |
扩展Aware接口的装配 | 由ApplicationContextAwareProcessor解析执行时机在postProcessBeforeInitialization |
初始化 | 总结 |
---|---|
@PostConstruct | 由CommonContextAwareProcessor解析执行时机在postProcessBeforeInitialization |
InitializingBean | 通过接口回调执行初始化 |
InitMethod(即<bean init-method>或@Bean(initMethod)) | 根据BeanDefinition得到的初始化方法执行初始化 |
AOP代理 | 总结 |
---|---|
创建AOP代理 | 由AnnotationAwareAspectJAutoProxyCreator创建执行时机在postProcessAfterInitialization |
首先我们需要得知可销毁bean的判定条件:
然后我们需要知道可销毁bean的存储位置:
最后我们需要注意一点:
第六阶段作用:
第六阶段注意点:
第七阶段作用:
第七阶段注意点:
同时我们还需要注意同一bean不同形态想回方法的调用次序:
下面我们来介绍Spring 事务失效的多种情况以及解决方法
下面我们给出Spring事务失效的八种案例及解决方法:
/* 1.事务内部手动抛出异常未回滚 */
// 问题原因
Spring默认只会回滚非检查异常
// 解决方法
配置rollbackFor属性即可(rollbackFor = Exception.class)
// 解决方法示例
@Transactional(rollbackFor = Exception.class)
public void transfer(...){}
/* 2.事务内部手动trycatch捕捉异常导致无法检测到异常,无法进行回滚 */
// 问题原因
事务通知只有捕捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标处理掉异常,自然不会回滚
// 解决方法
1. 将异常原样抛出
2. 手动设置TransactionStatus.setRollbackonly(),表示修改事务状态将其修改为进行回滚操作
// 解决方法示例
@Transactional(rollbackFor = Exception.class)
public void transfer(...){
try{
...
} catch (Exception e){
e.printStackTrace();
// 下面处理方法选择其一即可
// 1.直接原样抛出(可以包装成正在运行异常抛出)
throw new RuntimeException(e);
// 2.修改该事务处理方式
TransactionInterceptor.currentTransactionStatus.setRollbackOnly();
}
}
/* 3.aop切面顺序导致事务不能正确回滚*/
// 问题原因
事务切面优先级最低,如果自定义的切面优先级相同,且自定义切面在内层,只是可能就会吞掉异常(当切面存在trycatch方法时)
// 解决方法
1. 同情况2
2. 修改切面优先级(不推荐)
/* 4.非public方法导致事务失效 */
// 问题原因
Spring未方法创建代理,添加事务通知的前提条件是该方法为public
// 解决方法
1. 修改为public方法
2. 修改配置类
// 解决方法
public TransactionAttributeSource transactionAttributeSource(){
// 设置是否当public才可以设置事务
return new AnnotationTransactionAttributeSource(false);
}
/* 5.父子容器导致事务失效 */
// 问题原因
子容器扫描范围过大,把未加事务配置的service扫描进来
// 解决方法
1. 修改扫描范围,精确到具体文件夹
2. 不采用父子容器,直接放在同一容器下
/* 6. 调用本类方法导致传播失效 */
// 问题原因
本类方法调用不经过代理,因此无法增强
// 解决方法
1. 依赖注入自己(代理)来调用
2. 通过AopContext拿到代理对象,来调用
3. 通过CTW,LTW实现功能增强
/* 7.@Transactional没有保证原子行为 */
// 问题原因
事物的原子性仅覆盖insert,delete,select...for update语句
// 解决方法
数据库:将select语句修改为select...for update语句
/* 8.@Transactional方法导致的synchronized失效 */
// 问题原因
synchronized保证的仅仅是目标方法的原子性,commit方法并未包括其中,可能方法执行完毕,但并未执行commit就转交权限
// 解决方法
1. 数据库:将select语句修改为select...for update语句
2. Java:将synchronized范围扩大到代理方法调用
下面我们来介绍Spring WebMvc的执行流程
首先我们给出Spring WebMvc的第一个阶段操作:
/* 初始化阶段操作 */
1. 在Web容器第一次用到DispatcherServlet的时候,会创建其对象并执行方法
2. init方法内会创建spring Web容器,并调用容器refresh方法
3. refresh过程中会创建并初始化SpringMVC中的重要组件
4. 容器初始化后,会将上一步初始化好的重要组件,赋值给DispatcherServlet的成员变量,留待后用
/* 重要组件展示 */
SpringWeb容器:
1. HandlerMapping:请求映射
2. HandlerAdapter:调用方法响应请求
3. HandlerExceptionResolver:用于异常处理
4. ViewResolver:字符串解析为引用对象
5. MultipartResolver:处理表单数据(非必要)
DispatcherServlet容器:
包含上述组件的数组类型,比如HandlerMappings,HandlerAdapters等用于存储SpringWeb容器的各项组件
第二阶段是匹配阶段:
/* 匹配阶段 */
1. 用户发送的请求统一到达前端控制器DispatcherServlet
2. DispatcherServlet遍历所有HandlerMapping,找到与路径匹配的处理器
- HandlerMapping有多个,每个HandlerMapping会返回不同的处理器对象,谁先匹配,返回谁的处理器
(其中识别@RequestMapping优先级最高)
- 对应@RequestMapping的处理器是HandlerMethod,它包含了控制器对象和控制器方法信息
- 其中路径与处理器的映射关系在Handlermapping初始化时就会建好
3. 将HandlerMethod连同匹配到的拦截器,生成调用链对象HandlerExecutionChain返回
4. 遍历HandlerAdapter处理器适配器,找到能够处理HandlerMethod的适配器对象,开始调用
最后一个阶段是执行阶段:
/* 执行阶段 */
1. 执行拦截器preHandle
2. 由HandlerAdapter调用HandlerMethod
- 调用前处理不同类型的参数
- 调用后处理不同类型的返回值
3-1. 若第二步没有异常:
- 返回ModeAndView
- 执行拦截器postHandler方法
- 解析视图,得到View对象,进行视图渲染
3-2. 若第二步出现异常:
- 进入HandlerExceptionResolver异常处理流程
4. 最后执行拦截器的HandlerExceptionResolver
注意点:
- 如果控制器方法标注了@ResponseBody注解,则第二步就会生成json数据,并标记ModelAndview已处理,不需要执行3-1最后一步
/* 问题补充 */
拦截器分为三个部分:
- preHandler:方法开始前拦截器,若通过就执行
- postHandler:方法开始时拦截器,中间操作
- afterHandler:方法结束后拦截器,必定执行
HandlerMethod: 对应@RequestMapping的处理器是HandlerMethod,它包含了控制器对象和控制器方法信息,用于处理请求
下面我们来介绍我们SSM框架中的常用注解
下面我们来介绍Spring的常用注解类型:
/* 事务 */
@EnableTransactionManagement:开始事务支持注解
@Transactional:开启方法事务
/* 核心 */
@order:定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序(数字越小,优先级越高)
/* 切面 */
@EnableAspectJAutoProxy:Spring AOP开启的标志,在启动类标记此注解,即可加载对应的切面类逻辑
/* 组件扫描和配置类 */
@Component:用于组件扫描类修饰
@Controller:用于服务层组件扫描类修饰
@Service:用于业务层组件扫描类修饰
@Repository:用于数据层组件扫描类修饰
@ComponentScan:用于启动类扫描指定包中使用组件扫描修饰的类
@Conditional:条件装配,首先需要判断是否符合条件,若符合再进行装配
@Configuration:配置类修饰
@Bean:设置为Bean
@Import:导入指定配置类
@Lazy:标记在类上,表示延迟实例初始化
@PropertySource:读取外部property文件
/* 依赖注入 */
@Autowired:自动装配
@Qualifer:自动装配出现相同类型的bean时,采用@Qualifer来采用名字查找对应bean
@Value:用@Value表示赋值,可以赋值property中的数据
下面我们来介绍Spring WebMvc的常用注解:
/* mapping */
@Mapping:用于标识数据层,用于代替数据层映射
@RequestMapping:设置映射URL,可以作用与类或方法上
@GetMapping:请求方式为Get的@RequestMapping
@PostMapping:请求方式为Post的@RequestMapping
@PutMapping:请求方式为Put的@RequestMapping
@DeleteMapping:请求方式为Delete的@RequestMapping
/* rest */
@RequestBody:请求体设置为Json数据
@ResponseBody:响应体设置为Json数据
@ResonseStatus:响应体状态设置为Json数据
/* 异常统一处理 */
@ControllerAdvice:
- ControllerAdvice本质上是一个Component,因此也会被当成组建扫描
- ControllerAdvice具有拦截功能,提供了多种指定Advice规则的定义方式,默认什么都不写,则是Advice所有Controller
- 搭配@ExceptionHandler使用可以变为异常处理器
@ExceptionHandler:在方法上使用,后面跟异常.class,用于处理对应异常
@RestControllerAdvice:用于处理所有使用REST风格的Controller的异常处理
/* 参数 */
@PathVariable:用于取请求路径上的数据
@RequestParm:用于匹配名称对应的数据,正常情况下省略
/* Ajax */
@CrossOrigin:用于开启Ajax跨域
下面我们来介绍SpringBoot常用注解:
/* properties */
@EnableConfigurationProperties:让使用@ConfigurationProperties 注解的类生效,并且将该类注入到 IOC 容器中一同管理
@ConfigurationProperties:标注在Class上,这样SpringBoot就会从Environment中获取属性对应的属性值将其注入
/* Condition */
ConditionOnClass:需要该类路径下包含某个类才能满足条件
ConditionOnMissingBean:只有缺失某个Bean才能满足条件(用于默认属性设置,当不存在手动输入属性,将其设置为默认属性)
ConditionOnProperty:保证符合某Property才能满足条件
/* Auto */
@SpringBootApplication:
- 用于SpringBoot的启动类,其中包含三个注解:@EnableAutoConfiguration,@SpringBootConfiguration,@componentscan
@EnableAutoConfiguration:
- 帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot
- 并创建对应配置类的Bean,并把该Bean实体交给IoC容器进行管理
@SpringBootConfiguration:
- 允许在 Spring 上下文中注册额外的 bean 或导入其他配置类(表示配置类,基于@Configuration)
下面我们介绍@Configuration注解的注意点:
/* 注意点1 */
@Configuration标注的配置类相当于一个工厂,标注@Bean注解的方法相当于工厂方法
/* 注意点2 */
@Bean不支持方法重载,若含有多个重载方法,会选择参数最多的一个作为工厂方法
/* 注意点3 */
@Configuration默认为标记的类生成代理,当多次创建时,还是会出现默认的Bean,保证@Bean方法互相调用时,保持其单例
/* 问题与解决方法 */
// 问题
若我们在配置类中使用了@Value标注一个值,并将其作用于一个@Bean方法里面,若我们在里面设置一个后处理器,会导致@Value无法显示?
// 问题原因
因为我们该配置类中含有后处理器,后处理器在refresh的第五步进行,这时就需要创建类,但@Value的赋值在refresh的第十一步,就会导致无法赋值,也就导致@Value的值无法显示
// 解决方法
1. 将后处理器设置为static类型,让他自动默认生成,这样第五步时就不会生成类而是直接调用类中默认方法(推荐!!!)
2. 直接在方法中使用@Value标注值
下面我们介绍Import的机制:
/* 类名 */
@Import(类名.class)加载单个Bean
/* 配置类名 */
@Import(配置类名.class)联通配置类和内部Bean加载
/* Selector名 */
@Import(Selector.class)通过Selector引入多个类
/* beanDefinition注册器 */
@Import(beanDefinition.class)通过beanDefinition引入多个类
下面我们来介绍SpringBoot的自动装配机制
SpringBoot自动装配机制主要依赖于@SpringBootApplication:
/* SpringBootApplication组成 */
@SpringBootConfiguration
@ComponentScan
@EnableAutoConfiguration
/* @SpringBootConfiguration */
基于Configuration注解形成,用于表示配置类
有且仅有一个,表示启动类唯一标识
/* @ComponentScan */
用于扫描组件
内置excludeFilter过滤器,用于排除所不需要的类
内置Filter过滤器,用于排除Springboot默认配置类(会在其他地方添加)
/* @EnableAutoConfiguration */
分为@AutoConfigurationPackage和@Import注解两大块
@AutoConfigurationPackage:记录标注的类的所在包,采用AutoConfigurationPackages类的get方法获得包
@Import:用于导入类,实现主从配置分离,实现优先级降低(优先自己配置类,若没有配置类,再使用默认配置类)
下面我们介绍Spring所使用的设计模式
单例模式Singleton:
/* Spring中的Singleton Bean是否为单例模式??? */
不是!
/* 解释 */
Spring中的Singleton bean并非实现了单例模式,singleton bean仅仅保证每个容器里面相同id的bean的单例
/* 具体体现 */
org.springframework.aop.TruePointcut#INSTANCE
建造者模式Builder:
/* 定义 */
将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示
/* 使用场景 */
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式
/* 主要亮点 */
1. 较为灵活的构建产品对象
2. 在不执行最后build方法之前,产品对象均不可用
3. 构造过程采用链式调用
/* 具体体现 */
org.springframework.beans.factory.support.BeanDefinitionBuilder
工厂方法FactoryMethod:
/* 定义 */
定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method 使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类
/* 应用场景 */
通过“对象创建” 模式绕开 new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定
在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化
/* 具体使用 */
Spring中的ApplicationContext与BeanFactory的getBean方法都可以视为工厂方法
适配器模式Adapter:
/* 定义 */
如何组合类与对象,以获得更大的结构
/* 应用场景 */
将一个类的接口转换成客户希望的另一个接口。
Adapter 模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作。
/* 具体使用 */
org.springframework.web.servlet.HandlerAdapter:
- @RequestMapping标注的控制器实现
- 传统的Controller接口
- 较新的RoyteFunction接口
- 由于处理方法不同,我们采用适配器Adapter来统一接口
我们之前学习的DisposableBeanAdapter,用于bean的销毁,由于具有不同的销毁方法,我们采用DisposableBean统一调用销毁方法
组合模式Composite:
/* 定义 */
用于创造容器结构,大容器可以存放小容器和物品,小容器可以存放更小的容器
将分散的调用集合起来,统一调用入口
与具体干活的实现实现同一个接口,当调用Composite对象的接口方法时,其实是委托具体干活的实现来完成
/* 应用场景 */
文件和文件夹
/* 具体使用 */
org.springframework.web.servlet.view.ViewResolverComposite
装饰器Decorator:
/* 定义 */
动态地给一个对象添加一些额外的职责。
就增加功能来说,Decorator 模式相比生成子类更为灵活。
注重功能增强!!!!!
/* 应用场景 */
为父类设计子类,且子类不用继承父类繁杂的方法
/* 具体使用 */
org.springframework.web.util.ContentCachingRequestWrapper
代理模式Proxy:
/* 定义 */
Proxy代理设计模式是一种控制对象访问的设计模式,类似于网络代理
注重控制目标访问!!!!!
/* 应用场景 */
远程代理
虚拟代理
安全代理
/* 具体使用 */
org.springframework.aop.framework.jdkDynamicAopProxy
责任链模式Responsibility:
/* 定义 */
通过一条长链,将多个请求对象连接起来,将请求沿链传递,不断判断该请求对象是否满足该请求,若满足交付
/* 应用场景 */
拦截器等
/* 具体使用 */
org.springframework.web.servlet.handlerInterceptor
观察者模式Observer:
/* 定义 */
Observer设计模式是为了定义对象间的一种一对多的依赖关系
以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。
Observer模式是一种松耦合的设计模式
/* 具体使用 */
org.springframework.context.ApplicationListener
策略模式Strategy:
/* 定义 */
定义一系列算法,并把它们独立封装到类中,使它们可以相互替换,独立于使用它们的客户而变化。
/* 应用场景 */
我们在JVM所学习的线程池的应对方案,针对超出线程池限制的线程我们根据用户和当前情况选择不同的处理方案
/* 具体使用 */
org.springframework.beans.factory.support.InstantiationStraregy
模板方法Template Method:
/* 定义 */
定义一个操作算法中的框架,而将这些步骤延迟加载到子类中。
它的本质就是固定算法框架。
/* 应用场景 */
让父类控制子类方法的调用顺序
模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
/* 具体使用 */
大部分以Template命名的类
大部分以Abstract命名的类
目前关于Spring的面试点就总结到这里,该篇文章后续会持续更新~
参考资料: