面试 - Spring Boot
一:概述相关
1:何为Spring Boot
Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案
Spring Boot 主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器[xxxx_starter],使开发者能快速上手。
spring boot是如何简化spring的:
- maven导入依赖starter启动器
- 简化冗余配置
- 内嵌tomcat -> springMVC父子容器 & 多个Tomcat进程
2:核心注解@SpringBootApplication
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。本质上是
@Configuration的特化版本 - @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,Spring Boot 会根据类路径中的 jar 包、类自动配置 Bean
// java 如关闭数据源自动配置功能:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
- @ComponentScan:Spring组件扫描 - 自动扫描并注册当前包及其子包下的组件(
@Component,@Service,@Repository,@Controller等)
在sprinBoot启动时由@SpringBootApplication注解会自动去maven中读取每个starter中的spring.factories文件
该文件里配置了所有需要被创建spring容器中的bean,并且进行自动配置把bean注入SpringContext中
4:自动配置原理是什么
主要是Spring Boot的启动类上的核心注解SpringBootApplication注解主配置类
有了这个主配置类启动时就会为SpringBoot开启一个@EnableAutoConfiguration注解自动配置功能
有了这个EnableAutoConfiguration的话就会:
- 从配置文件
META_INF/Spring.factories加载可能用到的自动配置类 - 去重,并将
exclude和excludeName属性携带的类排除 - 过滤,将满足条件(
@Conditional)的自动配置类返回
5:有哪些常见的Spring Starter依赖
spring-boot-starter-web嵌入tomcat和web开发需要servlet与jsp支持spring-boot-starter-data-jpa数据库支持spring-boot-starter-data-redisredis数据库支持mybatis-spring-boot-starter第三方的mybatis集成starter- 自定义的starter(如果自己开发过就可以说出来)
6:spring-boot-starter-parent
新创建一个 Spring Boot 项目,默认都是有 parent 的,这个 parent 就是 spring-boot-starter-parent
spring-boot-starter-parent 主要有如下作用:
- 定义了 Java 编译版本为 1.8
- 使用 UTF-8 格式编码
- 继承自 spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号
- 执行打包操作的配置。
- 自动化的资源过滤。
- 自动化的插件配置。
- 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。
总结就是打包用的
7:SpringBoot打成的jar 和 普通的jar
Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 java -jar xxx.jar 命令来运行
这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。
Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。
普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes 目录下才是我们的代码,因此无法被直接引用。
如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。
二:使用相关
1:实现热部署
热部署就是可以不用重新运行SpringBoot项目可以实现操作后台代码自动更新到以运行的项目中
主要有两种方式:Spring Loaded & Spring-boot-devtools
2:实现本地事务部署
首先使用注解EnableTransactionManagement开启事务
然后在Service方法上添加注解@Transactional
3:实现异步调用部署
在启动类加入@EnableAsync使异步调用
在方法上使用@Async注解即可实现方法的异步调用。
4:启动的时候运行一些特定的代码
1️⃣ 可以实现接口ApplicationRunner或者CommandLineRunner
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyStartupRunner1 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// 在这里编写启动时需要执行的代码
System.out.println("CommandLineRunner执行,应用启动完成!");
}
}
2️⃣ 使用@PostConstruct注解
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
@Component
public class MyStartupBean {
@PostConstruct
public void init() {
System.out.println("@PostConstruct方法执行");
}
}
3️⃣ 实现ApplicationListener监听ContextRefreshedEvent事件
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class MyStartupListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("应用上下文初始化或刷新完成");
}
}
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
// 启动前执行的代码
System.out.println("应用即将启动...");
ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);
// 启动后执行的代码
System.out.println("应用启动完成!");
}
}
5:Session共享实现
常见的方案就是 Spring Session + Redis 来实现 session 共享。
将所有微服务的 session 统一保存在 Redis 上,当各个微服务对 session 有相关的读写操作时,都去操作 Redis 上的 session。这样就实现了 session 共享
Spring Session 基于 Spring 中的代理过滤器实现,使得 session 的同步操作对开发人员而言是透明的,非常的方便
6:如何实现定时任务
在 Spring Boot 中使用定时任务主要有两种不同的方式
一个就是使用 Spring 中的 @Scheduled 注解,另一-个则是使用第三方框架 Quartz
使用 Spring 中的 @Scheduled 的方式主要通过 @Scheduled 注解来实现
三:项目配置文件相关
1:读取配置的方式
Spring Boot 可以通过@PropertySource, @Value, @Environment, @ConfigurationPropertie注解来绑定变量
@PropertySouce是spring3.1开始引入的基于java config的注解。
通过@PropertySource注解将properties配置文件中的值存储到Spring的 Environment中
Environment接口提供方法去读取配置文件中的值,参数是properties文件中定义的key值。
# 假设有如下的jdbc.properties
jdbc.driver = oracle.jdbc.driver.OracleDriver
jdbc.url = xxxxxxxxxx
jdbc.username= sassy
jdbc.password = password
@PropertySource和@Value
@Configuration
@PropertySource("classpath:jdbc.properties") // 指定properties的位置
public class PropertiesWithJavaConfig {
@Value(${jdbc.driver}) // @拿到指定的聂荣
private String driver;
@Value(${jdbc.url})
private String url;
@Value(${jdbc.username})
private String username;
@Value(${jdbc.password})
private String password;
}
@PropertySource和Environment
@Configuration
@PropertySource("classpath:jdbc.properties")
public class PropertiesWithJavaConfig {
@Autowired
private Environment env;
// 下面可以使用env.getProperty("jdbc.driver")得到相应的属性值
}
2:bootstrap & application
单纯做 Spring Boot 开发,可能不太容易遇到 bootstrap 配置文件
但是在结合 Spring Cloud 时,这个配置就会经常遇到了,特别是在需要加载一些远程配置文件的时侯
- bootstrap (. yml 或者 . properties):
- boostrap 由父 ApplicationContext 加载的
- 比 applicaton 优先加载,配置在应用程序上下文的引导阶段生效
- 一般来说我们在 Spring Cloud 配置就会使用这个文件。且 boostrap 里面的属性不能被覆盖
- application (. yml 或者 . properties)
- 由ApplicatonContext 加载
- 用于 spring boot 项目的自动化配置
3:Spring Profiles
在项目的开发中,有些配置文件在开发、测试或者生产等不同环境中可能是不同的,例如数据库连接、redis的配置等等。
Spring Profiles 允许用户根据配置文件(dev,test,prod 等)来注册 bean。
因此,当应用程序在开发中运行时,只有某些 bean 可以加载,而在 PRODUCTION中,某些其他 bean 可以加载。
假设我们的要求是 Swagger 文档仅适用于 QA 环境,并且禁用所有其他文档。这可以使用配置文件来完成。
Spring Boot 使得使用配置文件非常简单。
4:多数据源处理
固定多数据源
1️⃣ 首先在yaml或者properties配置多个数据源
spring:
datasource:
primary:
jdbc-url: jdbc:mysql://localhost:3306/db1
username: user1
password: pass1
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
jdbc-url: jdbc:mysql://localhost:3306/db2
username: user2
password: pass2
driver-class-name: com.mysql.cj.jdbc.Driver
2️⃣ 对两个数据源进行配置:
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
@Primary // 标记为主数据源
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "primarySqlSessionFactory")
@Primary
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return bean.getObject();
}
@Bean(name = "primaryTransactionManager")
@Primary
public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
@Configuration
@MapperScan(basePackages = "com.example.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryDataSourceConfig {
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/secondary/*.xml"));
return bean.getObject();
}
@Bean(name = "secondaryTransactionManager")
public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
3️⃣ 然后使用就可以了
@Service
public class UserService {
@Autowired
@Qualifier("primarySqlSessionTemplate")
private SqlSessionTemplate primarySqlSessionTemplate;
@Autowired
@Qualifier("secondarySqlSessionTemplate")
private SqlSessionTemplate secondarySqlSessionTemplate;
public List<User> getPrimaryUsers() {
return primarySqlSessionTemplate.selectList("com.example.mapper.primary.UserMapper.findAll");
}
public List<User> getSecondaryUsers() {
return secondarySqlSessionTemplate.selectList("com.example.mapper.secondary.UserMapper.findAll");
}
}
动态多数据源
1️⃣ 配置动态多数据源
package cn.com.chnsys.datasource;
/**
* <p>
* 功能描述:datasource context holder
* </p>
*
* @author cui haida
* @date 2025/05/14/20:36
*/
public class DataSourceContextHolder {
// 使用ThreadLocal保证线程安全
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
package cn.com.chnsys.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* <p>
* 功能描述:dynamicDataSource config
* </p>
*
* @author cui haida
* @date 2025/05/14/20:35
*/
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
package cn.com.chnsys.datasource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 功能描述:dynamic DataSource Config
* </p>
*
* @author cui haida
* @date 2025/05/14/20:39
*/
@Configuration
public class DynamicDataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
/**
* 配置动态数据源
*
* 该方法配置了一个动态数据源,它可以根据需要切换到不同的实际数据源
* 主要用于在一个应用中集成多个数据库的情况,以便于灵活地进行数据访问和管理
*
* @param primaryDataSource 主数据源,作为默认数据源使用
* @param secondaryDataSource 辅助数据源,用于特定情况下的数据访问
* @return DynamicDataSource 返回配置好的动态数据源实例
*/
@Bean
@Primary
public DataSource dynamicDataSource(
@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
// 创建一个映射,用于存储所有可能的目标数据源
Map<Object, Object> targetDataSources = new HashMap<>();
// 将主数据源和辅助数据源添加到映射中
targetDataSources.put("primary", primaryDataSource);
targetDataSources.put("secondary", secondaryDataSource);
// 创建动态数据源实例
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 设置所有目标数据源
dynamicDataSource.setTargetDataSources(targetDataSources);
// 设置默认目标数据源为主数据源
dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
// 返回配置好的动态数据源实例
return dynamicDataSource;
}
}
2️⃣ 使用即可
@Service
public class OrderService {
@Transactional
public void processOrderWithDynamicDS() {
// 使用主数据源
DataSourceContextHolder.setDataSourceType("primary");
// 执行主数据源操作
// 切换到次数据源
DataSourceContextHolder.setDataSourceType("secondary");
// 执行次数据源操作
// 清理
DataSourceContextHolder.clearDataSourceType();
}
}
3️⃣ 对于动态数据源,需要自定义事务管理器
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Autowired
private DynamicDataSource dynamicDataSource;
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource);
}
}
四:安全管理相关
1:保护Spring Boot应用方法
- 在生产中使用HTTPS
- 使用Snyk检查你的依赖关系
- 升级到最新版本
- 启用CSRF保护
- 使用内容安全策略防止XSS攻击
2:Spring Security & Shiro
由于 Spring Boot 官方提供了大量的非常方便的开箱即用的 Starter ,包括 Spring Security 的 Starter ,使得在 Spring Boot 中使用 Spring Security 变得更加容易,甚至只需要添加一个依赖就可以保护所有的接口
如果是 Spring Boot 项目,一般选择 Spring Security。【单纯从技术上来说,无论怎么组合,都是没有问题的。】
Shiro 和 Spring Security 相比,主要有如下一些特点:
- Spring Security 是一个重量级的安全管理框架;Shiro 则是一个轻量级的安全管理框架
- Spring Security 概念复杂,配置繁琐;Shiro 概念简单、配置简单
- Spring Security 功能强大;Shiro 功能简单
3:如何解决跨域问题
可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截
registry.addInterceptor(new TokenInterceptor())
// 对所有请求进行拦截
.addPathPatterns("/**")
// 排除 login请求
.excludePathPatterns("/login");
}
//解决跨域问题
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 对所有的内容执行配置
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
/**
* cors配置
*/
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 允许所有的来源,正常此处配置的是前端服务器
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
// 允许所有的head & method
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
}
2万+

被折叠的 条评论
为什么被折叠?



