【Spring Boot】一文带你掌握面试题
【引言】
最近一段时间,公司招人,面试了不少人,主要是需要做过Spring Boot项目,所以,利用这个机会,结合一些面试题,做了一些整理。
【问题整理】
一. 什么是Spring Boot?
SpringBoot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
二. Spring Boot 有什么优势?
1.Spring Boot 让开发变得更简单
Spring Boot 对开发效率的提升是全方位的,下面做一个简单的对比:
在没有Spring Boot之前,我们开发一个web项目需要做的一些工作:
- 配置web.xml,加载spring 和 spring mvc
- 配置数据库连接,配置spring 事务
- 配置加载配置文件的读取,开启注解
- 配置日志文件
… - 配置完成之后部署tomcat调试
可能还需要考虑各个版本的兼容性,jar 包冲突的可行性。
那么如果使用Spring Boot之后,我们开发一个web项目只需要以下操作(以IDEA开发工具为例):
- 直接新建项目,如下:
下一步,选择需要的依赖:
最后finish,就可以直接进行开发工作。
2.Spring Boot 让测试变得更简单
Spring Boot 内置了7种强大的测试框架:
- Junit: 一个java语言的单元测试框架
- Spring Boot Test: 为Spring Boot应用提供集成测试和工具支持
- AssertJ: 支持流式断言的java测试框架
- Hamcrest: 一个匹配器库
- Mockito: 一个java mock框架
- JSONassert: 一个针对JSON的断言库
- JsonPath: Json XPath库
我们只需要在项目中引入spring-boot-starter-test,就可以对数据库、Mock、Web等各种情况进行测试。
3.Spring Boot 让配置变得更简单
Spring Boot的核心思想:约定优于配置,即按约定编程,是一种软件设计范式,旨在减少软件开发人员需要做决定的数量,获得简单的好处,而又不失灵活性。
本质是说,开发人员仅需要规定应用中不符约定的部分。
4.Spring Boot 让部署变得更简单
Spring Boot项目不需要像传统web项目那样,需要配置tomcat,内嵌tomcat、jetty容器使得开发人员不需要关心容器的环境问题。
Jenkins是目前持续构建领域使用最广泛的工具之一,使用Jenkins部署Spring Boot项目也非常简单,只需要前期做一些简单的配置,当我们需要发布项目时只需要点击项目对应的发布按钮,就可以将项目从版本库中拉取、打包、发布到目标服务器中,大大简化了运维后期的维护工作。
我们也可以利用容器化技术,将Spring Boot项目做成镜像,根据容器集群的策略实现弹性扩容、动态部署等。所以Spring Boot + Docker + Jenkins会将Spring Boot项目的部署做的更简单化、智能化。
5.Spring Boot 让监控变得更简单
Spring Boot自带监控组件,即Spring Boot Actuator,通过它,我们可以查看应用配置的详细信息,例如自动化配置信息、创建的Spring beans以及一些环境属性等。
但它只能监控一个Spring Boot应用的缺点不能满足监控多服务的实际情况,所以,基于它,又产生了一个更强大的监控开源软件,Spring Boot Admin。每个Spring Boot应用都认为是一个客户端,通过HTTP或者使用Eureka注册到admin server中进行展示。
Spring Boot Admin 向我们提供了所有被监控Spring Boot项目的基本信息,详细的Health信息、内存信息、JVM信息、垃圾回收信息,各种配置信息(比如数据源、缓存列表)等,还可以直接修改logger的level.
三. Spring Boot 核心配置有哪几个,有什么区别?
Spring Boot 中有以下两种配置文件:
1. bootstrap (.yml 或者 .properties)
2. application (.yml 或者 .properties)
(yml和properties两种格式,主要是书写上格式不同,另外,yml不知@PropertySource 注解导入配置。)
区别:
在 Spring Boot 中有两种上下文,一种是 bootstrap,另外一种是 application。
bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。
bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。
这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。
BootStarp的应用场景:
- 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
- 一些固定的不能被覆盖的属性
- 一些加密/解密的场景
四. Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是**@SpringBootApplication**,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
- @ComponentScan:Spring组件扫描。
五. 运行 Spring Boot 有哪几种方式?
- 打包用命令或者放到容器中运行
- 用 Maven/Gradle 插件运行
- 直接执行 main 方法运行
六. Spring Boot 自动配置原理?
@EnableAutoConfiguration实现了Spring Boot应用的自动装配。在@EnableAutoConfiguration注解上,@Import({AutoConfigurationImportSelector.class}) 注解会引入AutoConfigurationImportSelector类,在AutoConfigurationImportSelector类中,完成了自动装配的功能。
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
getCandidateConfigurations(annotationMetadata, attributes)方法为获取自动装配的候选类集合,而此方法实现如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
其中主要方法是SpringFactoriesLoader.loadFactoryNames,其原理是查找所有的META-INF/spring.factories资源内容,文件内容如下:
也就是说,Spring Boot会默认给我们加载此文件中的配置项,这也就是为什么我们在使用Spring Boot应用的过程中,只需要在application.properties配置相关的配置项即可,而不需要做过多的其他配置。
七. Spring Boot 读取配置文件的方式有哪几种?
1. @Value注解读取
/**
* 配置文件application.properties
*/
spring.datasource.db.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db.username=***
spring.datasource.db.password=***
/**
* 数据源配置
*/
@Configuration
@Data
public class DataSourceConfig {
@Value("${spring.datasource.db.url}")
String url;
@Value("${spring.datasource.db.username}")
String username;
@Value("${spring.datasource.db.password}")
String password;
}
2. @ConfigurationProperties注解读取
/**
* 配置文件application.properties
*/
#数据源1
spring.datasource.db1.url=jdbc:mysql://localhost:3306/demo1?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db1.username=***
spring.datasource.db1.password=***
#数据源2
spring.datasource.db2.url=jdbc:mysql://localhost:3306/demo2?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db2.username=***
spring.datasource.db2.password=***
/**
* 多数据源配置
*/
@Configuration
public class DataSourceConfig {
/**
* 数据源1
* spring.datasource.db1 :application.properteis中对应属性的前缀
* @return
*/
@Bean(name = "datasource1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
/**
* 数据源1
* spring.datasource.db2 :application.properteis中对应属性的前缀
* @return
*/
@Bean(name = "datasource2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
* @return
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource1());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap();
dsMap.put("datasource1", dataSource1());
dsMap.put("datasource2", dataSource2());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
}
3. @PropertySource+@Value注解读取指定文件
例如,在资源目录下新建config/db-config.properties:
/**
* 配置文件db-config.properties
*/
spring.datasource.db.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db.username=***
spring.datasource.db.password=***
/**
* 数据源配置
*/
@Configuration
@Data
@PropertySource(value = {"config/db-config.properties"})
public class DataSourceConfig {
@Value("${spring.datasource.db.url}")
String url;
@Value("${spring.datasource.db.username}")
String username;
@Value("${spring.datasource.db.password}")
String password;
}
4. @PropertySource+@ConfigurationProperties注解读取指定文件
/**
* 配置文件db-config.properties
*/
spring.datasource.db.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db.username=***
spring.datasource.db.password=***
/**
* 数据源配置
*/
@Configuration
@Data
@ConfigurationProperties(prefix = "spring.datasource.db")
@PropertySource(value = {"config/db-config.properties"})
public class DataSourceConfig {
String url;
String username;
String password;
}
5. Environment读取
以上所有加载出来的配置都可以通过Environment注入获取到:
@Autowired
private Environment environment;
String url = environment.getProperty("spring.datasource.db.url");
...
八. 如何在Spring Boot启动的时候运行一些特定的代码?
- 实现接口 ApplicationRunner或者 CommandLineRunner
- 使用@PostConstruct注解
九. 如何理解Spring Boot中的Starters?
1. 是什么
Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成Spring及其他技术,而不需要到处找示例及依赖的jar包。如你想使用Spring JPA访问数据库,只要加入spring-boot-starter-data-jpa启动器依赖就能使用了。
2. 命名
Spring Boot官方的启动器都是以spring-boot-starter-命名的,代表了一个特定的应用类型。
第三方的启动器不能以spring-boot开头命名,它们都被Spring Boot官方保留。一般一个第三方的应用这样命名,像mybatis的mybatis-spring-boot-starter.
3. 分类
- 应用
- 生产
- 技术
- 其他第三方
十. Spring Boot 2.X新特性有哪些?
1. 配置变更
废弃了1.X中的一些配置,增加了很多新的配置,详细说明:
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Configuration-Changelog
2. JDK升级
2.X至少需要JDK8的支持,2.X里面许多方法应用了JDK8的高级新特性,并且开始了对JDK9的支持
3. 第三方类库升级
2.X对第三方类库升级了所有能升级的稳定版本:
如Spring Framework 5+ ;Tomcat 8.5 + ; Hibernate 5.2 +
4. Data支持
- 2.X 默认使用了HiKariCP连接池
- 更加合理化的优化了数据库初始化逻辑
- 提供了新配置spring.jdbc.template 方便分页和排序
- 可以高级定制MongoDB客户端
…
5. Web加强
- 使用内嵌式容器时,context path会和端口一起记录并打印出来
- 所有支持的容器都支持过滤器的初始化
- Thymeleaf开始支持javax.time类型
- 提供了一个spring-boot-starter-json启动器对JSON的读写的支持
6. Quartz支持
2.X提供了一个spring-boot-starter-quartz启动器对定时任务框架Quartz的支持
十一. 保护Spring Boot 应用的方法有哪些?
1. 生产中使用HTTPS
@Configuration
public class WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel().requiresSecure();
}
}
2. 启用CSRF保护
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
3. 使用内容安全策略防止XSS攻击
内容安全策略(CSP)是一个增加的安全层,可帮助缓解XSS(跨站点脚本)和数据注入攻击。要启用它,你需要配置应用程序以返回Content-Security-Policy标题。
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/");
}
}
4. 使用OpenID Connect进行身份验证
OpenID Connect(OIDC)是一个OAuth 2.0扩展,提供用户信息,除了访问令牌之外,它还添加了ID令牌,以及/userinfo可以从中获取其他信息的端点,它还添加了发现功能和动态客户端注册的端点。
如果使用OIDC进行身份验证,则无需担心如何存储用户、密码或对用户进行身份验证。相反,你可以使用身份提供商(IdP)为你执行此操作,你的IdP甚至可能提供多因素身份验证(MFA)等安全附加组件。
5. 管理密码 使用密码哈希
以纯文本格式存储密码是最糟糕的事情之一。幸运的是,Spring Security默认情况下不允许使用纯文本密码。它还附带了一个加密模块,可用于对称加密,生成密钥和密码散列(也就是密码编码)。
Spring Security提供了几种实现,最受欢迎的是BCryptPasswordEncoder和Pbkdf2PasswordEncoder。
【总结】
Spring Boot应用已成为很多公司的首选,项目应用是一方面,面试中理论也是很重要的一方面。很多人都是可能用过但并不了解其内部一些原理性的知识,对于问题的回答并不尽人意,便很难表现出自己的优势,还是需要多一些深入才行,这也是自己整理此篇博客的原因。
还没有评论,来说两句吧...