面试问题
# 基础
# 什么是 Spring 框架?
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
Spring 官网列出的 Spring 的 6 个特征:
- 核心技术 :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。
- 测试 :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。
- 数据访问 :事务,DAO支持,JDBC,ORM,编组XML。
- Web支持 : Spring MVC和Spring WebFlux Web框架。
- 集成 :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
- 语言 :Kotlin,Groovy,动态语言。
# springboot模块结构
下图对应的是 Spring4.x 版本。目前最新的5.x版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。
- Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。
- Spring Aspects : 该模块为与AspectJ的集成提供支持。
- Spring AOP :提供了面向切面的编程实现。
- Spring JDBC : Java数据库连接。
- Spring JMS :Java消息服务。
- Spring ORM : 用于支持Hibernate等ORM工具。
- Spring Web : 为创建Web应用程序提供支持。
- Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。
# Bean相关
# Bean加载的流程
- getBean 一个bean在spring中只会加载一次,后面如果想再获取的时候,就会从缓存中获取
- 实例化 缓存中的bean是最原始的状态,需要进行实例化,使用createBeanInstance来进行实例化
- 填充属性
- 初始化
# Spring之中Bean的作用域有哪些?
有五种,最后一种已经没了。
- singleton:默认,单例模式
- prototype: 每次请求都会生成一个新的bean实例
- request: 每次请求都会产生一个bean,其仅在当前的HTTP request之中有效
- session: 每次请求都会生成一个bean,仅在当前的HTTP session之中有效。
- global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
# Spring 之中单例 bean 的线程安全问题
多个线程操作同一个对象的时候,对这个对象的非静态成员的变量的写操作会存在线程问题。说白了,如果你的bean之中是有”状态“——比如有一个变量,那么就会存在线程安全问题。
两种方法:
- 在Bean对象之中尽可能避免可变的成员变量——Servlet之中所有参数全在方法上,自然没有多线程问题
- 在类之中定义一个ThreadLocal,将需要可变的变量存在ThreadLocal之中。
# @Component 和 @Bean 区别在哪?
- 对象不同:@Component 注解在类上,@Bean 注解在方法上
- @Bean 注解比 @Component 的自定义性更强。比如引用第三方库的类需要装配到Spring容器之中时(比如@Configuration的),我们就只能使用@Bean
而且如果对于一个类,我们直接使用@Component, 那么可能内部有些不需要的类我们也生成了。@Bean的控制粒度更细
# FactoryBean 和 BeanFactory有什么区别?
BeanFactory 是 Bean 的工厂, ApplicationContext 的父类,IOC 容器的核心,负责生产和管理 Bean 对象。
FactoryBean 是 Bean,可以通过实现 FactoryBean 接口定制实例化 Bean 的逻辑,通过代理一个Bean对象,对方法前后做一些操作。
说的再直白一点就是,默认情况下我们可以直接bean factory来创建一个普通的对象。但是有些特殊情况,比如我们想自定义程度更高一点的话,我们可以使用FactoryBean来进行定义
参考
BeanFactory 简介以及它 和FactoryBean的区别(阿里面试) - aspirant - 博客园 (cnblogs.com) (opens new window)
# SpringBean的生命周期
- Bean 容器找到配置文件中 Spring Bean 的定义。
- Bean 容器利用 Java Reflection API 创建一个Bean的实例。
- 如果涉及到一些属性值 利用
set()
方法设置一些属性值。 - 如果 Bean 实现了
BeanNameAware
接口,调用setBeanName()
方法,传入Bean的名字。 - 如果 Bean 实现了
BeanClassLoaderAware
接口,调用setBeanClassLoader()
方法,传入ClassLoader
对象的实例。 - 与上面的类似,如果实现了其他
*.Aware
接口,就调用相应的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessBeforeInitialization()
方法 - 如果Bean实现了
InitializingBean
接口,执行afterPropertiesSet()
方法。 - 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
- 如果有和加载这个 Bean的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessAfterInitialization()
方法 - 当要销毁 Bean 的时候,如果 Bean 实现了
DisposableBean
接口,执行destroy()
方法。 - 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
# Spring IoC容器之中BeanFactory和ApplicationContext的区别
一般而言我们推荐使用ApplicationContext,原因是:
- ApplicationContext是利用Java反射机制,自动识别出配置文件之中定义的 BeanpostProcessor,InstantiationAwareBeanPostProcessor和 BeanFactoryPostProcessor(这些相当于是在Bean创建前后对Bean做点操作,比如赋个值啥的),并且自动注册到应用上下文之中。而BeanFactory需要在代码之中手动调用addBeanPostProcessor()来进行注册。
- ApplicationContext在初始化应用上下文的时候就实例化所有单实例的Bean,但是BeanFactory在初始化容器的时候没有实例化Bean,直到第一次访问某个Bean的时候才实例化目标bean
# spring中的单例bean的线程安全问题了解吗?
的确是存在安全问题的。因为,当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题。
但是,一般情况下,我们常用的 Controller
、Service
、Dao
这些 Bean 是无状态的。无状态的 Bean 不能保存数据,因此是线程安全的。
常见的有 2 种解决办法:
- 在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)。 - 改变 Bean 的作用域为 “prototype”:每次请求都会创建一个新的 bean 实例,自然不会存在线程安全问题。
# 将一个类声明为spring的 bean 的注解有哪些
我们一般使用 @Autowired
注解自动装配 bean,要想把类标识成可用于 @Autowired
注解自动装配的 bean 的类,采用以下注解可实现:
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个Bean不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。
# 注解相关
# spring的@Schedule注解是如何实现的
这个是spring内置的一个模块,整个调度模块完全依赖于TaskScheduler
实现,更底层的是JUC
调度线程池ScheduledThreadPoolExecutor
深入参考: 通过源码理解Spring中@Scheduled的实现原理并且实现调度任务动态装载 (opens new window)
# @Controller和@RestController区别?
@Controller是返回一个JSP页面,但是@RestController返回的是JSON或者XML格式的数据。
@RestController = @Controller + @ResponseBody
# @Transactional(rollback=Exception.class)作用是?
如果不加这个,那么只有遇到RuntimeException
才会回滚。加了的话,在非运行异常时候也会回滚。
# 使用同一个类
之中一个没有事务的方法去调用另一个有事务的方法,是否会有事务?
答案是不会。而且和事务的传播机制没关系。
# @Resource和@Autowired的区别
相同点 spring中都可以用来注入bean,同时都还可以作为注入属性的修饰。在接口仅有单一实现类时,两个注解的修饰效果是相同的,他们之间可以相互替换,不影响使用。
不同点
- @Resource是Java自己的注解,@Resource有两个主要的属性,分别是name和type;spring对@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略。而使用type属性,则是使用byType的自动注入策略。如果既不指定name属性也不指定type属性,这时将通过反射机制使用byName的自动注入策略。
- @Autowired是spring的注解,是spring2.5版本引入的,@Autowired注解只根据type进行注入,不会去匹配name。如果涉及到根据type无法辨别的注入对象,将需要依赖@Qualifier或@Primary注解一起来修饰。
平时的开发过程中呢,更常用的是@Autowired注解,因为平时百分之九十以上都是一个接口仅有一个实现类,所以用@Autowired比较方便,当然这种情况用两个中的哪一个都一样,没什么太大的区别,纯属个人习惯。但是如果是生产环境的话,架构师对这方面要求有比较严格的情况下,会让大家使用@Resource(name="xxx")这种,因为在生产环境就会要求更高的效率问题,这样使用效率是最高的,类似于根据id查询数据库一样,效率高一些。所以根据个人情况,选择性使用吧。。。
@Resource和@Autowired的区别 - 简书 (jianshu.com) (opens new window)
# 配置相关
# 自动装配流程
先简单说一下什么是自动装配吧,在没有springboot的时候,我们在写spring时,需要在xml文件中进行配置,但是有了springboot后我们就只需要加一个 @SpringBootApplication
注解就可以实现自动进行依赖注入,自动读取配置文件的功能,通过全局配置文件 application.properties
或application.yml
即可对项目进行设置
这个 @SpringBootApplication
里面有三个注解,这部分其实在启动流程哪里有详细的说明
启动流程和配置 | 面试问题浓缩总结 (xiaoyou66.com) (opens new window)
其实就是 @EnableAutoConfiguration注解 (开启自动配置),这个注解点进去后就可以看到导入自动装配选择器AutoConfigurationImportSelector
再点击这个类进入,就可以看到具体实现了
在loadFactoryNames方法在一个叫SpringFactoriesLoader类中,有一个地方可以加载资源
SpringBoot的自动配置就是通过自定义实现ImportSelector接口,然后通过SpringFactoriesLoader扫描autoconfigure包下的META-INF/spring.factories中所有路径下的类,并通过反射实例化成一个个的配置类并注入到Spring容器中,从而实现了自动装配,想到这里,我们其实也可以自定一个starter,然后交给SpringBoot自动来装配即可
参考
SpringBoot自动装配原理与启动流程_我的鱼要死了的博客-CSDN博客_springboot启动过程 (opens new window)
# 杂项
# SpringBoot和Spring区别
- SpringBoot基于spring,它帮你把spring的复杂的配置给简化了,我们可以直接使用
- SpringBoot内置了tomcat服务器,无需再进行配置,开箱即用
# spring中的设计模式
涉及到以下几种
1.简单工厂(非23种设计模式中的一种)
2.工厂方法
BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定
3.单例模式
依赖注入Bean实例默认是单例的
4.适配器模式
SpringMVC中的适配器HandlerAdatper(HandlerAdatper根据Handler规则执行不同的Handler)
5.装饰器模式
Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator,就是动态给一个对象添加一些额外的职责
6.代理模式
AOP底层就是使用代理模式来实现的(包括动态代理和静态代理)
7.观察者模式
spring的事件驱动模型使用的是 观察者模式 ,Spring中Observer模式常用的地方是listener的实现。
8.策略模式
Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
9.模版方法模式
JDBC的抽象和对Hibernate的集成,都采用了一种理念或者处理方式,那就是模板方法模式与相应的Callback接口相结合。
参考:Spring 中经典的 9 种设计模式,打死也要记住啊! - 知乎 (zhihu.com) (opens new window)
# Maven依赖关系原则与冲突解决办法
先说一下maven依赖处理的几个原则 :
- 最短路径优先 Maven 面对 D1 和 D2 时,会默认选择最短路径的那个 jar 包,即 D2。E->F->D2 比 A->B->C->D1 路径短 。
- 最先声明优先 如果路径一样的话,如: A->B->C1, E->F->C2 ,两个依赖路径长度都是 2,那么就选择最先声明。
如何解决呢
我们可以借助Maven Helper插件中的Dependency Analyzer分析冲突的jar包,然后在对应标红版本的jar包上面点击execlude,就可以将该jar包排除出去。
或者手动在pom.xml中使用<exclusion>
标签去排除冲突的jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
2
3
4
5
6
7
8
9
10
11
我们可以使用 mvn dependency:tree
来分析包冲突
参考: Maven中 jar包冲突原理与解决办法_noaman_wgs的博客-CSDN博客 (opens new window)
# maven的生命周期
一个典型的maven的生命周期有下面这几个阶段
验证 validate | 验证项目 | 验证项目是否正确且所有必须信息是可用的 |
---|---|---|
编译 compile | 执行编译 | 源代码编译在此阶段完成 |
测试 Test | 测试 | 使用适当的单元测试框架(例如JUnit)运行测试。 |
包装 package | 打包 | 创建JAR/WAR包如在 pom.xml 中定义提及的包 |
检查 verify | 检查 | 对集成测试的结果进行检查,以保证质量达标 |
安装 install | 安装 | 安装打包的项目到本地仓库,以供其他项目使用 |
部署 deploy | 部署 | 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程 |
为了完成 default 生命周期,这些阶段(包括其他未在上面罗列的生命周期阶段)将被按顺序地执行。
Maven 有以下三个标准的生命周期:
- clean:项目清理的处理
- default(或 build):项目部署的处理
- site:项目站点文档创建的处理
Maven 构建生命周期 | 菜鸟教程 (runoob.com) (opens new window)
# springboot不同目录的加载顺序
Spring Boot 启动会扫描以下位置的 application.properties
或者 application.yml/yaml
文件作为 Spring Boot 的默认配置文件
(1)当前项目下的 config
文件夹中;
(2)当前项目路径的根目录下;
(3)resource资源文件下的 config
文件夹中;
(4)resource资源文件下(项目默认创建的配置文件)。
SpringBoot配置文件、静态资源不同文件目录加载优先级 - 技术交流 - SpringBoot中文社区 (opens new window)
# 静态资源加载顺序
spring boot项目只有src目录,没有webapp目录,会将静态访问(html/图片等)映射到其自动配置的静态目录,如下
- /static
- /public
- /resources
- /META-INF/resources