面试 - Spring Boot

面试 - 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加载可能用到的自动配置类
  • 去重,并将excludeexcludeName属性携带的类排除
  • 过滤,将满足条件(@Conditional)的自动配置类返回

5:有哪些常见的Spring Starter依赖

  • spring-boot-starter-web 嵌入tomcat和web开发需要servlet与jsp支持
  • spring-boot-starter-data-jpa 数据库支持
  • spring-boot-starter-data-redis redis数据库支持
  • 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;
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值