Skip to content

四、Spring(15 题)

4.1 ★★★★Spring IoC 和 AOP 的核心原理是什么?IoC 和 DI 的区别是什么?

📖 点击查看答案

IoC(Inversion of Control,控制反转):

  • 思想:对象创建和依赖关系的管理从代码中转移到容器
  • 传统方式:对象自己 new 依赖对象
  • IoC:容器创建对象并注入依赖
java
// 传统
class UserService {
    UserDao dao = new UserDaoImpl(); // 自己创建
}

// IoC
class UserService {
    @Autowired UserDao dao; // 容器注入
}
  • "控制"指对象创建、生命周期管理的控制权
  • "反转"指控制权从代码转移到容器

DI(Dependency Injection,依赖注入):

  • IoC 的具体实现方式
  • 容器主动把依赖注入到对象中
  • 三种注入方式:构造器、setter、字段

IoC vs DI:

维度IoCDI
范畴思想/原则实现方式
关注点控制权转移如何注入依赖
关系IoC 是目标DI 是实现 IoC 的手段

AOP(Aspect-Oriented Programming,面向切面编程):

  • 思想:将横切关注点(日志、事务、安全)从业务代码中分离
  • 通过动态代理在方法前后插入逻辑

核心概念:

概念说明
Aspect(切面)横切关注点的模块化(如日志切面)
JoinPoint(连接点)程序执行点,Spring 中是方法执行
Pointcut(切点)匹配连接点的表达式
Advice(通知)在切点处执行的逻辑(前置、后置等)
Target(目标对象)被代理的对象
Proxy(代理)AOP 创建的代理对象
Weaving(织入)把切面应用到目标对象的过程

AOP 示例:

java
@Aspect
@Component
public class LogAspect {
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethod() {}

    @Before("serviceMethod()")
    public void before(JoinPoint jp) {
        log.info("调用方法: {}", jp.getSignature());
    }

    @Around("serviceMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        log.info("耗时: {}ms", System.currentTimeMillis() - start);
        return result;
    }
}

IoC 容器的核心接口:

  • BeanFactory:基础容器,延迟加载
  • ApplicationContext:扩展容器,启动时加载

IoC 的工作流程:

  1. 读取配置(XML、注解、Java Config)
  2. 解析为 BeanDefinition
  3. 实例化 Bean
  4. 注入依赖
  5. 初始化(BeanPostProcessor)
  6. 放入容器供使用

AOP 的应用场景:

  • 事务管理(@Transactional)
  • 日志记录
  • 权限校验
  • 性能监控
  • 缓存(@Cacheable)
  • 异步(@Async)

4.2 ★★Spring IoC 容器初始化的完整流程是怎样的?

📖 点击查看答案

IoC 容器初始化流程(三大阶段):

  1. 资源定位(Resource Localization)
  2. BeanDefinition 加载和解析
  3. BeanDefinition 注册

详细流程:

1. 资源定位

  • 通过 ResourceLoader 加载配置文件(XML、注解、Java Config)
  • Resource 抽象不同来源:FileSystemResource、ClassPathResource、UrlResource
java
// XML 方式
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");

// 注解方式
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

2. BeanDefinition 加载和解析

  • 通过 BeanDefinitionReader 读取配置
  • 解析为 BeanDefinition 对象(包含类名、scope、属性、依赖等元数据)
XML 配置 → XmlBeanDefinitionReader
注解配置 → AnnotatedBeanDefinitionReader / ClassPathBeanDefinitionScanner
  • BeanDefinition 是 Bean 的"图纸"

3. BeanDefinition 注册

  • 把 BeanDefinition 注册到 BeanDefinitionRegistry
  • 内部用 ConcurrentHashMap 存储
java
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

ApplicationContext 的初始化流程(AbstractApplicationContext.refresh()):

java
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备刷新(设置启动时间、活动标志、校验必要属性)
        prepareRefresh();

        // 2. 获取 BeanFactory(创建 DefaultListableBeanFactory)
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 3. BeanFactory 预准备(注册系统组件如 ApplicationContextAwareProcessor)
        prepareBeanFactory(beanFactory);

        // 4. 子类扩展点(注册特殊的 BeanPostProcessor)
        postProcessBeanFactory(beanFactory);

        // 5. 调用 BeanFactoryPostProcessor(修改 BeanDefinition)
        invokeBeanFactoryPostProcessors(beanFactory);

        // 6. 注册 BeanPostProcessor(用于 Bean 初始化时的扩展)
        registerBeanPostProcessors(beanFactory);

        // 7. 初始化 MessageSource(国际化)
        initMessageSource();

        // 8. 初始化事件广播器
        initApplicationEventMulticaster();

        // 9. 子类扩展点(初始化特殊 Bean,如 ThemeSource)
        onRefresh();

        // 10. 注册事件监听器
        registerListeners();

        // 11. 实例化所有非懒加载的单例 Bean
        finishBeanFactoryInitialization(beanFactory);

        // 12. 完成刷新(发布 ContextRefreshedEvent)
        finishRefresh();
    }
}

关键步骤说明:

  • invokeBeanFactoryPostProcessors:调用 BeanFactoryPostProcessor,最典型的是 ConfigurationClassPostProcessor,处理 @Configuration、@ComponentScan、@Import、@Bean

  • registerBeanPostProcessors:注册 BeanPostProcessor,用于 Bean 实例化后、初始化前后的处理

  • finishBeanFactoryInitialization:实例化所有非懒加载的单例 Bean(最耗时)

Bean 的实例化过程:

  1. 调用 getBean(name) 触发
  2. 从单例缓存查找(一、二、三级缓存)
  3. 没有 → 创建
  4. 实例化(反射调用构造器)
  5. 属性注入(解决循环依赖)
  6. 初始化(BeanPostProcessor 前置、InitializingBean、init-method、BeanPostProcessor 后置)
  7. 放入单例池

Bean 的生命周期(详见 4.4):

实例化 → 属性赋值 → 各种 Aware 回调 → BeanPostProcessor.before → 初始化 → BeanPostProcessor.after → 使用 → 销毁

4.3 ★★BeanFactoryApplicationContext 的区别是什么?FactoryBeanBeanFactory 的区别?

📖 点击查看答案

BeanFactory vs ApplicationContext:

维度BeanFactoryApplicationContext
定位IoC 容器的根接口BeanFactory 的子接口,扩展功能
加载时机懒加载(getBean 时才创建)启动时预加载(默认)
国际化不支持支持(MessageSource)
事件机制不支持支持(ApplicationEvent)
资源访问不支持支持(ResourceLoader)
AOP不支持支持
注解支持不支持支持
性能启动快启动慢(初始化所有 Bean)
实际使用极少默认

ApplicationContext 的常用实现:

  • ClassPathXmlApplicationContext:从 classpath 加载 XML
  • FileSystemXmlApplicationContext:从文件系统加载 XML
  • AnnotationConfigApplicationContext:基于注解配置
  • GenericWebApplicationContext:Web 应用
  • AnnotationConfigServletWebServerApplicationContext:Spring Boot

BeanFactory 的核心方法:

java
public interface BeanFactory {
    Object getBean(String name) throws BeansException;
    <T> T getBean(Class<T> requiredType);
    boolean containsBean(String name);
    boolean isSingleton(String name);
    Class<?> getType(String name);
    // ...
}

FactoryBean vs BeanFactory:

维度FactoryBeanBeanFactory
类型接口接口
作用创建复杂 Bean 的工厂IoC 容器
关系BeanFactory 管理 FactoryBeanFactoryBean 被 BeanFactory 管理
调用getBean 返回 getObject()getBean 返回 Bean 实例

FactoryBean 的作用:

  • 用于创建复杂的 Bean(如需要特定初始化逻辑的 Bean)
  • 实现接口后,getBean(name) 返回 getObject() 的结果,而不是 FactoryBean 本身
  • getBean("&name") 返回 FactoryBean 本身
java
public interface FactoryBean<T> {
    T getObject() throws Exception;  // 返回真正的 Bean
    Class<?> getObjectType();
    boolean isSingleton();
}

FactoryBean 示例:

java
@Component
public class MyConnectionFactoryBean implements FactoryBean<Connection> {
    @Override
    public Connection getObject() throws Exception {
        // 复杂的创建逻辑
        return DriverManager.getConnection(url, user, pwd);
    }
    @Override
    public Class<?> getObjectType() { return Connection.class; }
    @Override
    public boolean isSingleton() { return false; } // 每次返回新实例
}

// 使用
Connection conn = ctx.getBean(Connection.class); // 调用 getObject()
MyConnectionFactoryBean fb = (MyConnectionFactoryBean) ctx.getBean("&myConnectionFactoryBean");

FactoryBean 的典型应用:

  • Spring AOP 的 ProxyFactoryBean
  • MyBatis 的 SqlSessionFactoryBeanMapperFactoryBean
  • Spring 集成第三方库时常用
  • Spring Boot 自动配置中的条件 Bean

FactoryBean 与 @Bean 的区别:

  • @Bean:方法返回值就是 Bean
  • FactoryBean:实现接口,getObject() 返回 Bean,可控制是否单例、类型推断

4.4 ★★★★Spring Bean 的完整生命周期是怎样的?BeanPostProcessor 在其中起什么作用?

📖 点击查看答案

Bean 的完整生命周期(4 大阶段):

1. 实例化(Instantiation)

2. 属性赋值(Populate Properties)

3. 初始化(Initialization)
   ├─ Aware 接口回调
   ├─ BeanPostProcessor.postProcessBeforeInitialization
   ├─ InitializingBean.afterPropertiesSet / @PostConstruct / init-method
   └─ BeanPostProcessor.postProcessAfterInitialization

4. 使用(In Use)

5. 销毁(Destruction)
   ├─ @PreDestroy
   ├─ DisposableBean.destroy
   └─ destroy-method

详细流程:

1. 实例化

  • 容器调用构造器或工厂方法创建对象
  • 此时对象已分配内存但属性未注入
java
@Service
public class UserService {
    public UserService() { System.out.println("1. 实例化"); }
}

2. 属性赋值

  • 根据 @Autowired、@Resource、XML 配置注入依赖
  • 解决循环依赖(通过三级缓存)
java
@Autowired
private UserDao userDao; // 此阶段注入

3. 初始化

  • 3.1 Aware 接口回调:注入容器组件
java
@Component
public class MyBean implements BeanNameAware, ApplicationContextAware {
    public void setBeanName(String name) { /* 注入 Bean 名 */ }
    public void setApplicationContext(ApplicationContext ctx) { /* 注入上下文 */ }
}
  • 3.2 BeanPostProcessor 前置处理postProcessBeforeInitialization
    • 此处处理 @PostConstruct、@Autowired(其实是后置)
java
@Component
public class MyPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String name) {
        // 初始化前处理
        return bean;
    }
}
  • 3.3 初始化方法(按顺序):
    1. @PostConstruct 注解方法
    2. InitializingBean.afterPropertiesSet()
    3. XML/@Bean(initMethod=) 指定的 init-method
java
@PostConstruct
public void init() { System.out.println("3.1 @PostConstruct"); }

public class MyBean implements InitializingBean {
    public void afterPropertiesSet() { System.out.println("3.2 afterPropertiesSet"); }
}
  • 3.4 BeanPostProcessor 后置处理postProcessAfterInitialization
    • 此处生成 AOP 代理(AbstractAutoProxyCreator)

4. 使用

  • Bean 完全初始化,放入单例池
  • 应用代码使用

5. 销毁

  • 容器关闭时调用
  • 顺序:
    1. @PreDestroy 注解方法
    2. DisposableBean.destroy()
    3. destroy-method 指定的方法
java
@PreDestroy
public void cleanup() { /* 释放资源 */ }

BeanPostProcessor 的作用:

  • 在 Bean 初始化前后插入扩展逻辑
  • 所有 Bean 都会经过 BeanPostProcessor
  • 典型应用:
    • AutowiredAnnotationBeanPostProcessor:处理 @Autowired
    • CommonAnnotationBeanPostProcessor:处理 @PostConstruct、@PreDestroy、@Resource
    • AbstractAutoProxyCreator:生成 AOP 代理
java
public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }
    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean; // 可返回代理对象
    }
}

关键细节:

  • @Autowired 注入实际上发生在 populateBean 阶段,通过 AutowiredAnnotationBeanPostProcessor 的 postProcessProperties 方法
  • AOP 代理生成在 postProcessAfterInitialization 阶段
  • 三级缓存解决循环依赖,让提前暴露的半成品 Bean 也能被代理

完整代码示例:

java
@Component
public class LifeCycleBean implements InitializingBean, DisposableBean {
    @Autowired private UserDao userDao;

    public LifeCycleBean() { log.info("1. 实例化"); }

    @PostConstruct
    public void postConstruct() { log.info("3.1 @PostConstruct"); }

    @Override
    public void afterPropertiesSet() { log.info("3.2 afterPropertiesSet"); }

    @PreDestroy
    public void preDestroy() { log.info("5.1 @PreDestroy"); }

    @Override
    public void destroy() { log.info("5.2 destroy"); }
}

4.5 ★★★Bean 的作用域有哪些?单例 Bean 是线程安全的吗?

📖 点击查看答案

Bean 的作用域(Scope):

作用域说明
singleton(默认)整个容器中只有一个实例
prototype每次获取都创建新实例
request每个 HTTP 请求一个实例(Web)
session每个 HTTP Session 一个实例(Web)
application每个 ServletContext 一个实例(Web)
websocket每个 WebSocket 一个实例(Web)

作用域的指定:

java
@Component
@Scope("singleton")  // 默认
public class SingletonBean { }

@Component
@Scope("prototype")
public class PrototypeBean { }

// JDK 8+ 也可以用常量
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

singleton vs prototype:

维度singletonprototype
实例数1每次新建
生命周期容器管理只创建,不管理销毁
状态应保持无状态可有状态
性能高(复用)较低

singleton 注入 prototype 的问题:

  • singleton Bean 只初始化一次,注入的 prototype 也是一次性的
  • prototype 后续的修改在 singleton 中感知不到
java
@Component
@Scope("singleton")
public class SingletonService {
    @Autowired
    private PrototypeBean proto; // 只在初始化时注入一次

    public void doWork() {
        proto.process(); // 每次都是同一个实例
    }
}

解决方案:

  1. 使用 @Lookup 注解让 Spring 每次返回新的 prototype 实例
java
@Component
public abstract class SingletonService {
    @Lookup
    public abstract PrototypeBean getPrototype(); // 每次返回新实例

    public void doWork() {
        getPrototype().process();
    }
}
  1. 注入 ObjectFactoryProvider
java
@Autowired
private ObjectFactory<PrototypeBean> prototypeFactory;

public void doWork() {
    prototypeFactory.getObject().process();
}
  1. 通过 ApplicationContext.getBean() 获取

单例 Bean 是线程安全的吗?

  • 不是! 单例 Bean 默认不是线程安全的
  • 单例 = 所有线程共享同一个实例
  • 如果 Bean 有可变状态(成员变量),多线程访问会有并发问题
java
@Component
public class UnsafeService {
    private int count = 0;  // 可变状态,线程不安全

    public void increment() { count++; } // 非原子操作
}

保证单例 Bean 线程安全的方法:

  1. 无状态 Bean(推荐):不定义可变成员变量
java
@Component
public class SafeService {
    public String process(String input) { // 无状态,线程安全
        return input.toUpperCase();
    }
}
  1. 使用 ThreadLocal:每个线程独立副本

  2. 加锁:synchronized 或 ReentrantLock

  3. 使用并发容器:ConcurrentHashMap 等

  4. 改用 prototype 作用域:每个请求一个实例(性能差)


4.6 ★★★Spring 中有哪些依赖注入方式?@Autowired@Resource@Inject 的区别是什么?

📖 点击查看答案

三种依赖注入方式:

1. 构造器注入(推荐)

java
@Component
public class UserService {
    private final UserDao userDao;

    @Autowired  // 单构造器可省略
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}
  • 优点:依赖不可变(final)、不会出现 NPE、便于单元测试、避免循环依赖
  • 缺点:依赖多时构造器臃肿

2. Setter 注入

java
@Component
public class UserService {
    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
  • 优点:灵活,可重新注入
  • 缺点:可能被多次修改,非 final

3. 字段注入(不推荐)

java
@Component
public class UserService {
    @Autowired
    private UserDao userDao;
}
  • 优点:简洁
  • 缺点:无法 final、隐藏依赖关系、不便单元测试、容易 NPE

推荐顺序:构造器 > Setter > 字段

@Autowired、@Resource、@Inject 区别:

注解来源匹配方式默认策略是否 Spring
@AutowiredSpring按类型 → 按名称required=true
@ResourceJSR-250按名称 → 按类型required=true否(标准)
@InjectJSR-330按类型 → 按名称required=true否(标准)

@Autowired(Spring 提供):

  • 默认按类型匹配
  • 类型找不到或多个实现时,回退到按名称匹配字段名
  • 可配合 @Qualifier 指定 bean 名称
  • required = false 允许不注入(值可为 null)
java
@Autowired
@Qualifier("mysqlUserDao")  // 指定 bean 名称
private UserDao userDao;

@Autowired(required = false)  // 可选注入
private Optional<Cache> cache;

@Resource(JSR-250 标准):

  • 默认按名称匹配(先 name,再 type)
  • 通过 name 和 type 属性指定
java
@Resource(name = "mysqlUserDao")
private UserDao userDao;

@Resource  // 先按字段名 "userDao" 找,找不到再按类型
private UserDao userDao;

@Inject(JSR-330 标准):

  • 默认按类型匹配
  • 类似 @Autowired 但不支持 required = false
  • 配合 @Named 指定名称
java
@Inject
@Named("mysqlUserDao")
private UserDao userDao;

多实现选择策略:

  1. @Primary 标注某个实现为优先
java
@Primary
@Component
public class MysqlUserDao implements UserDao { }
  1. @Qualifier 指定名称

  2. 字段名与 bean 名匹配


4.7 ★★★★Spring 的三级缓存是如何解决循环依赖的?为什么需要三级而不是两级缓存?

📖 点击查看答案

循环依赖问题:

  • A 依赖 B,B 依赖 A
  • 创建 A 时需要 B,创建 B 时又需要 A,导致无法完成初始化
java
@Component
class A {
    @Autowired B b;
}
@Component
class B {
    @Autowired A a;
}

Spring 三级缓存:

java
// DefaultSingletonBeanRegistry
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一级
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 二级
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级
缓存名称内容
一级singletonObjects完全初始化好的 Bean
二级earlySingletonObjects提前暴露的半成品 Bean(已实例化但未初始化)
三级singletonFactoriesBean 的 ObjectFactory(工厂,可生成代理)

解决循环依赖的流程(A 依赖 B,B 依赖 A):

  1. 创建 A:实例化 A,把 A 的 ObjectFactory 放入三级缓存
java
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  1. A 注入 B:从缓存找 B,没有 → 创建 B
  2. 创建 B:实例化 B,把 B 的 ObjectFactory 放入三级缓存
  3. B 注入 A:从缓存找 A
    • 一级缓存没有
    • 二级缓存没有
    • 三级缓存有 → 调用 ObjectFactory.getObject() 获取早期 A
    • 把早期 A 放入二级缓存,从三级缓存移除
  4. B 拿到 A 的早期引用,完成属性注入和初始化
  5. B 完全初始化,放入一级缓存
  6. A 拿到 B,完成属性注入和初始化
  7. A 完全初始化,放入一级缓存

核心源码:

java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName); // 一级
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName); // 二级
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 三级
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

为什么需要三级而不是两级?

关键在于 AOP 代理

  • 如果用两级缓存,Spring 必须在实例化后就立刻判断是否需要代理、是否创建代理
  • 但 Spring 的设计原则是:AOP 代理在初始化后生成(postProcessAfterInitialization)
  • 三级缓存存储的是 ObjectFactory,延迟生成代理:
    • 没有循环依赖时:代理在初始化后正常生成,ObjectFactory 不被调用
    • 有循环依赖时:ObjectFactory 被调用,提前生成代理(getEarlyBeanReference)
java
// AbstractAutoProxyCreator.getEarlyBeanReference
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean); // 标记已提前代理
    return wrapIfNecessary(bean, beanName, cacheKey); // 生成代理
}
  • 如果只有两级缓存,且二级直接存半成品对象(非代理),那么循环依赖中拿到的就不是代理对象
  • 三级缓存的 ObjectFactory 让 Spring 在需要时才生成代理,且保证只生成一次

简而言之:

  • 一级缓存:完整 Bean
  • 二级缓存:早期 Bean(可能是代理)
  • 三级缓存:ObjectFactory,决定是否生成代理

循环依赖的限制:

  • 只能解决单例 setter 注入的循环依赖
  • 不能解决构造器循环依赖(实例化时就需要依赖)
  • prototype 作用域不能解决(不使用缓存)

4.8 ★★★★Spring AOP 的实现原理是什么?JDK 动态代理和 CGLIB 代理分别在什么情况下使用?

📖 点击查看答案

Spring AOP 实现原理:

  • 基于动态代理:运行时生成代理对象,在方法调用时插入切面逻辑
  • 核心:ProxyFactoryAopProxy 接口
  • 两种代理方式:JDK 动态代理、CGLIB 代理

AOP 的核心组件:

  • Advisor:切面,包含 Pointcut 和 Advice
  • Pointcut:切点,决定拦截哪些方法
  • Advice:通知,决定拦截后做什么(前置、后置、环绕)
  • Advised:被代理的目标对象

代理创建流程:

  1. Bean 实例化后,AbstractAutoProxyCreator.postProcessAfterInitialization 检查是否需要代理
  2. 如果匹配切点,创建代理:ProxyFactory.getProxy()
  3. 根据 proxyTargetClass 配置和目标类是否实现接口,选择 JDK 或 CGLIB
java
// DefaultAopProxyFactory
public AopProxy createAopProxy(AdvisedSupport config) {
    if (config.isOptimize() || config.isProxyTargetClass()
        || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);  // JDK 动态代理
        }
        return new ObjenesisCglibAopProxy(config);   // CGLIB 代理
    } else {
        return new JdkDynamicAopProxy(config);       // JDK 动态代理
    }
}

JDK 动态代理使用场景:

  • 目标类实现了至少一个接口
  • 默认情况下(proxyTargetClass=false)
  • Spring Boot 2.0+ 默认 proxyTargetClass=true,强制 CGLIB

CGLIB 代理使用场景:

  • 目标类没有实现任何接口
  • proxyTargetClass=true 显式指定
  • @Configuration 类(保证 @Bean 方法间调用返回同一实例)
  • Spring Boot 2.0+ 默认

JDK 动态代理原理:

  • 代理类实现目标类的接口
  • 调用转发给 InvocationHandler
java
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        if (chain.isEmpty()) {
            return method.invoke(target, args); // 直接调用
        }
        // 执行拦截器链
        MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
        return invocation.proceed();
    }
}

CGLIB 代理原理:

  • 通过 ASM 生成目标类的子类
  • 重写方法,插入 MethodInterceptor 拦截逻辑
  • FastClass 机制加速方法调用(避免反射)

性能对比:

  • 创建:JDK 快,CGLIB 慢
  • 调用:CGLIB 略快(FastClass 优化)
  • 内存:CGLIB 占用更多

Spring AOP 的限制:

  • 只能代理方法,不能代理字段
  • 只能代理 public 方法(CGLIB 不能代理 private/final)
  • 只能代理 Spring 容器管理的 Bean
  • 方法内部调用(this.xxx)不经过代理
java
@Service
public class UserService {
    @Transactional
    public void methodA() { ... }

    public void methodB() {
        this.methodA(); // 不走代理,@Transactional 失效!
    }
}

解决方案:

  • 注入自身代理:@Autowired UserService self; self.methodA();
  • AopContext.currentProxy():((UserService) AopContext.currentProxy()).methodA();

4.9 ★★Spring AOP 的通知类型有哪些?执行顺序是怎样的?

📖 点击查看答案

五种通知类型:

通知注解说明
前置通知@Before方法执行前
后置通知@AfterReturning方法正常返回后
异常通知@AfterThrowing方法抛异常后
最终通知@After方法执行后(无论成功失败,类似 finally)
环绕通知@Around环绕方法,可控制是否执行

示例:

java
@Aspect
@Component
public class LogAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Around before");
        try {
            Object result = pjp.proceed();
            System.out.println("Around after-returning");
            return result;
        } catch (Throwable e) {
            System.out.println("Around after-throwing");
            throw e;
        } finally {
            System.out.println("Around after");
        }
    }

    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint jp) {
        System.out.println("Before");
    }

    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        System.out.println("AfterReturning: " + result);
    }

    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint jp, Throwable ex) {
        System.out.println("AfterThrowing: " + ex.getMessage());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void after(JoinPoint jp) {
        System.out.println("After");
    }
}

正常返回时的执行顺序:

Around before
  Before
  目标方法执行
  AfterReturning
  After
Around after-returning
Around after

抛出异常时的执行顺序:

Around before
  Before
  目标方法执行(异常)
  AfterThrowing
  After
Around after-throwing
Around after

多个切面的执行顺序:

  • 通过 @Order(n) 指定切面优先级,n 越小优先级越高
  • 类似过滤器链:外层切面先进入后退出
切面 A @Order(1): Around before → Before → [切面 B...] → AfterReturning → After → Around after
切面 B @Order(2): Around before → Before → [目标方法] → AfterReturning → After → Around after

执行顺序总结:

  • 外层切面的 Before 在内层之前
  • 外层切面的 After 在内层之后
  • 形成洋葱模型:A-before → B-before → target → B-after → A-after

Spring 5.2.7+ 的顺序变化:

  • 之前:@After 在 @AfterReturning/@AfterThrowing 之前
  • 之后:@After 在 @AfterReturning/@AfterThrowing 之后(更符合 finally 语义)

4.10 ★★★Spring 事务的传播行为有哪些?举例说明 REQUIREDREQUIRES_NEWNESTED 的区别?

📖 点击查看答案

事务传播行为(Propagation):

  • 当事务方法被另一个事务方法调用时,如何处理事务的传播
  • 定义在 Propagation 枚举中,共 7 种
传播行为说明
REQUIRED(默认)有事务加入,没有就新建
SUPPORTS有事务加入,没有就非事务运行
MANDATORY必须在事务中,否则抛异常
REQUIRES_NEW总是新建事务,挂起当前事务
NOT_SUPPORTED非事务运行,挂起当前事务
NEVER非事务运行,有事务则抛异常
NESTED嵌套事务,没有就新建,有就创建保存点

REQUIRED(默认):

  • 有事务:加入当前事务
  • 无事务:新建一个事务
java
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    methodB(); // 加入 A 的事务
    insertA();
}

@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    insertB();
}
// 调用 methodA():A 和 B 在同一事务中,B 失败则 A 也回滚

REQUIRES_NEW:

  • 总是新建事务
  • 如果当前有事务,先挂起当前事务
java
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    insertA();
    methodB();  // 新开事务
    insertA2();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    insertB();
}
// 调用 methodA():
// - A 开启事务1
// - B 挂起事务1,开启事务2
// - B 提交事务2
// - 恢复事务1,继续执行 insertA2
// - B 失败不影响 A(独立事务)
  • 适用场景:日志记录(无论业务成功与否,日志都要写入)

NESTED:

  • 有事务:创建保存点(SAVEPOINT),形成嵌套事务
  • 无事务:等价于 REQUIRED
java
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    insertA();
    try {
        methodB();  // 嵌套事务
    } catch (Exception e) {
        // B 回滚到保存点,A 可以继续
    }
    insertA2();
}

@Transactional(propagation = Propagation.NESTED)
public void methodB() {
    insertB();
}
// 调用 methodA():
// - A 开启事务
// - B 在 A 内创建保存点
// - B 失败回滚到保存点,A 不受影响
// - A 继续执行 insertA2
// - A 失败则 B 也回滚(因为 B 在 A 的事务内)

三者区别:

维度REQUIREDREQUIRES_NEWNESTED
事务数1 个2 个(独立)1 个(嵌套)
B 失败影响 A否(catch 后)
A 失败影响 B
底层机制同一事务独立事务保存点(Savepoint)

其他传播行为:

java
// SUPPORTS:有事务就加入,没有就非事务运行
@Transactional(propagation = Propagation.SUPPORTS)
public void query() { ... }

// MANDATORY:必须在事务中
@Transactional(propagation = Propagation.MANDATORY)
public void mustInTransaction() { ... }

// NOT_SUPPORTED:挂起当前事务,非事务运行
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void nonTransactional() { ... }

// NEVER:不能在事务中运行
@Transactional(propagation = Propagation.NEVER)
public void mustNoTransaction() { ... }

4.11 ★★★★Spring 事务失效的常见场景有哪些?@Transactional 注解常见的坑有哪些?

📖 点击查看答案

Spring 事务失效的常见场景:

1. 方法非 public

  • Spring AOP 默认只代理 public 方法
  • private、protected、default 方法上的 @Transactional 无效
java
@Transactional
private void method() { } // 失效!

2. 自调用(this 调用)

  • 通过 this 调用的方法不经过代理,事务失效
java
@Service
public class UserService {
    public void methodA() {
        this.methodB(); // 不走代理,@Transactional 失效
    }

    @Transactional
    public void methodB() { }
}
  • 解决:
    • 注入自身:@Autowired UserService self; self.methodB();
    • AopContext:((UserService) AopContext.currentProxy()).methodB();

3. 异常被吞掉

  • try-catch 吞掉异常,事务无法感知,不会回滚
java
@Transactional
public void method() {
    try {
        dao.insert(...);
        throw new RuntimeException("error");
    } catch (Exception e) {
        log.error("error", e); // 异常被吞,事务不回滚
    }
}

4. 异常类型不匹配

  • Spring 默认只对 RuntimeExceptionError 回滚
  • 受检异常(如 IOException)默认不回滚
java
@Transactional
public void method() throws IOException {
    dao.insert(...);
    throw new IOException("error"); // 默认不回滚
}

// 解决:指定 rollbackFor
@Transactional(rollbackFor = Exception.class)
public void method2() throws IOException { }

5. 传播行为错误

  • NOT_SUPPORTED、NEVER 会让事务失效
java
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void method() { } // 非事务运行

6. 数据库不支持事务

  • MySQL 的 MyISAM 引擎不支持事务
  • 必须用 InnoDB

7. Bean 未被 Spring 管理

  • new 出来的对象不在 Spring 容器中,没有代理
java
UserService service = new UserService();
service.method(); // 不走代理,事务失效

8. 事务管理器未配置

  • 没有配置 DataSourceTransactionManager 或 PlatformTransactionManager

9. 多线程

  • 事务绑定在当前线程,多线程中跨线程操作不在同一事务
java
@Transactional
public void method() {
    new Thread(() -> {
        dao.insert(...); // 不在主线程的事务中
    }).start();
}

10. 嵌套调用中的 REQUIRES_NEW

  • 内层 REQUIRES_NEW 独立提交,外层失败不影响内层

@Transactional 的常见坑:

  1. 不指定 rollbackFor
java
// 反例
@Transactional  // 默认只回滚 RuntimeException
public void method() throws Exception { }

// 正例
@Transactional(rollbackFor = Exception.class)
public void method() throws Exception { }
  1. long 类型字段精度丢失
  • 事务本身没问题,但要注意事务中的数据一致性
  1. 大事务导致性能问题
  • 事务范围过大,占用数据库连接时间长
  • 解决:缩小事务范围,把查询移到事务外
java
// 反例
@Transactional
public void method() {
    // 大量查询(不需要事务)
    List<User> users = userDao.findAll();
    // 少量更新
    userDao.update(...);
}

// 正例
public void method() {
    List<User> users = userDao.findAll(); // 事务外
    updateInTransaction(users);
}

@Transactional
public void updateInTransaction(List<User> users) { }
  1. 事务方法调用静态方法或 final 方法
  • final 方法不能被重写,CGLIB 无法代理
  1. rollbackFor 指定过宽
  • rollbackFor = Exception.class 会回滚所有异常,包括业务上不该回滚的

4.12 ★★@Transactional 注解的实现原理是什么?rollbackFor 属性为什么很重要?

📖 点击查看答案

@Transactional 的实现原理:

1. AOP 代理

  • @Transactional 通过 AOP 实现
  • Spring 创建事务方法的代理对象
  • 调用事务方法时,代理拦截并管理事务

2. 核心组件:

  • TransactionInterceptor:事务拦截器,实现 MethodInterceptor
  • PlatformTransactionManager:事务管理器(如 DataSourceTransactionManager)
  • TransactionStatus:事务状态

3. 执行流程:

1. 调用代理方法
2. TransactionInterceptor.invoke() 拦截
3. 获取事务管理器,开启事务
4. 执行目标方法
5. 成功 → 提交事务
   异常 → 根据 rollbackFor 判断是否回滚

TransactionInterceptor 源码简化:

java
public Object invoke(MethodInvocation invocation) throws Throwable {
    // 1. 获取事务属性(@Transactional 的配置)
    TransactionAttributeSource tas = getTransactionAttributeSource();
    TransactionAttribute txAttr = tas.getTransactionAttribute(method, targetClass);

    // 2. 获取事务管理器
    PlatformTransactionManager tm = determineTransactionManager(txAttr);

    // 3. 创建事务
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, methodId);

    try {
        // 4. 执行目标方法
        Object retVal = invocation.proceed();

        // 5. 提交事务
        commitTransactionAfterReturning(txInfo);
        return retVal;
    } catch (Throwable ex) {
        // 6. 回滚事务(根据 rollbackFor 判断)
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    } finally {
        cleanupTransactionInfo(txInfo);
    }
}

回滚判断逻辑:

java
protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
    if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
        // 回滚
        txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
    } else {
        // 提交(异常不匹配 rollbackFor)
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
}

// DefaultTransactionAttribute.rollbackOn
public boolean rollbackOn(Throwable ex) {
    return (ex instanceof RuntimeException || ex instanceof Error);
}

rollbackFor 属性的重要性:

  • 默认情况下,Spring 只对 RuntimeExceptionError 回滚
  • 受检异常(IOException、SQLException、ClassNotFoundException)不会触发回滚
java
// 默认行为
@Transactional
public void method() throws IOException {
    userDao.update(...);
    throw new IOException("文件不存在"); // 不回滚!数据已更新
}
  • 指定 rollbackFor 后,匹配的异常也会回滚
java
@Transactional(rollbackFor = Exception.class)
public void method() throws IOException {
    userDao.update(...);
    throw new IOException("文件不存在"); // 回滚
}

为什么默认只回滚 RuntimeException?

  • 设计哲学:受检异常通常是可恢复的业务异常,不应回滚
  • RuntimeException 通常是不可恢复的程序错误,应该回滚
  • 但实际业务中,受检异常(如 IOException)也常常需要回滚

最佳实践:

  • 总是指定 @Transactional(rollbackFor = Exception.class)
  • 或自定义 RuntimeException 包装所有异常

事务传播的实现:

  • TransactionSynchronizationManager 用 ThreadLocal 保存当前事务
  • 同一线程内调用其他事务方法时,根据传播行为决定加入/新建/挂起
java
private static final ThreadLocal<Object> actualTransactionStatus = new NamedThreadLocal<>("Actual transaction status");

4.13 ★★★Spring MVC 的请求处理完整流程是什么?DispatcherServlet 在其中起什么作用?

📖 点击查看答案

Spring MVC 请求处理流程:

客户端请求

DispatcherServlet (前端控制器)

HandlerMapping (处理器映射)
   ↓ 找到 HandlerExecutionChain
HandlerAdapter (处理器适配器)

Interceptor.preHandle

Controller 方法

返回 ModelAndView

Interceptor.postHandle

ViewResolver (视图解析器)

View (视图渲染)

Interceptor.afterCompletion

响应客户端

详细流程:

  1. 客户端发送请求到 DispatcherServlet

  2. DispatcherServlet 调用 HandlerMapping 查找 Handler

    • 根据 URL 找到对应的 Controller 方法
    • 返回 HandlerExecutionChain(包含 Handler 和拦截器链)
  3. DispatcherServlet 调用 HandlerAdapter 执行 Handler

    • HandlerAdapter 适配不同类型的 Handler(如 @RequestMapping 方法)
    • 执行拦截器的 preHandle
  4. HandlerAdapter 执行 Controller 方法

    • 参数解析(@RequestParam、@RequestBody 等)
    • 调用 Controller 方法
    • 处理返回值(@ResponseBody → JSON,ModelAndView → 视图)
  5. DispatcherServlet 调用 ViewResolver 解析视图(如果是视图渲染)

    • 把逻辑视图名解析为物理 View
  6. View 渲染,填充 Model 数据

  7. 执行拦截器的 postHandle 和 afterCompletion

  8. 返回响应给客户端

核心组件:

组件作用
DispatcherServlet前端控制器,统一入口
HandlerMappingURL → Handler 映射
HandlerAdapter适配不同类型的 Handler
HandlerInterceptor拦截器,前置/后置处理
ViewResolver视图解析器
View视图渲染
HandlerExceptionResolver异常解析
MultipartResolver文件上传

DispatcherServlet 的核心方法 doDispatch:

java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    // 1. 查找 Handler
    HandlerExecutionChain mappedHandler = getHandler(processedRequest);

    // 2. 查找 HandlerAdapter
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // 3. 执行拦截器 preHandle
    if (!mappedHandler.applyPreHandle(processedRequest, response)) return;

    // 4. 执行 Handler
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    // 5. 执行拦截器 postHandle
    mappedHandler.applyPostHandle(processedRequest, response, mv);

    // 6. 处理结果(视图渲染或直接响应)
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

@ResponseBody 的处理:

  • 现代应用多用 RESTful API,返回 JSON
  • RequestMappingHandlerAdapter 通过 HandlerMethodReturnValueHandler 处理返回值
  • RequestResponseBodyMethodProcessor 处理 @ResponseBody
  • 使用 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)转换为 JSON

DispatcherServlet 的作用:

  • 统一入口:所有请求经过 DispatcherServlet
  • 协调者:调度各组件协作
  • 解耦:Handler、ViewResolver 等组件可替换
  • 扩展性:通过实现接口或继承可扩展功能

4.14 ★★★Spring 中用到了哪些设计模式?请至少列举 6 种并说明其应用场景。

📖 点击查看答案

Spring 中的设计模式:

1. 单例模式(Singleton)

  • Spring Bean 默认作用域是 singleton
  • 通过 ConcurrentHashMap 缓存 Bean 实例
  • 注意:Spring 的单例是容器级单例,不是 JVM 级单例
java
// DefaultSingletonBeanRegistry
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

2. 工厂模式(Factory)

  • BeanFactory 是 IoC 容器的根接口,工厂模式
  • FactoryBean 用于创建复杂 Bean
java
public interface FactoryBean<T> {
    T getObject() throws Exception;
}

3. 代理模式(Proxy)

  • Spring AOP 基于动态代理实现
  • JDK 动态代理:基于接口
  • CGLIB:基于继承
java
// JdkDynamicAopProxy
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, handler);

4. 模板方法模式(Template Method)

  • JdbcTemplate、RestTemplate、RedisTemplate 等
  • 父类定义骨架,子类实现具体步骤
java
public abstract class JdbcTemplate {
    public <T> T execute(ConnectionCallback<T> action) {
        Connection con = DataSourceUtils.getConnection(getDataSource());
        try {
            return action.doInConnection(con);
        } finally {
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }
}

5. 观察者模式(Observer)

  • Spring 的事件机制:ApplicationEvent + ApplicationListener
  • ApplicationContext 发布事件,监听器响应
java
// 发布事件
context.publishEvent(new UserCreatedEvent(user));

// 监听事件
@EventListener
public void handleUserCreated(UserCreatedEvent event) { }

6. 适配器模式(Adapter)

  • HandlerAdapter 适配不同类型的 Handler
  • Spring MVC 中适配 @RequestMapping、SimpleController 等
java
public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler);
}

7. 装饰器模式(Decorator)

  • BeanWrapper 包装 Bean,提供属性访问功能
  • HttpServletRequestWrapper 等

8. 责任链模式(Chain of Responsibility)

  • Spring AOP 的拦截器链
  • Spring MVC 的 HandlerInterceptor 链
java
public interface HandlerInterceptor {
    boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler);
    void postHandle(...);
    void afterCompletion(...);
}

9. 策略模式(Strategy)

  • Resource 接口的不同实现:ClassPathResource、FileSystemResource
  • InstantiationStrategy:SimpleInstantiationStrategy、CglibSubclassingInstantiationStrategy
  • 事务传播行为 Propagation
java
public interface Resource extends InputStreamSource {
    InputStream getInputStream() throws IOException;
}

10. 委派模式(Delegate)

  • DispatcherServlet 委派请求给具体 Controller
  • 不是 GOF 23 种模式之一,但 Spring 大量使用

11. 原型模式(Prototype)

  • Spring Bean 的 prototype 作用域
  • 每次获取都创建新实例

总结表:

设计模式Spring 应用
单例Bean 默认作用域
工厂BeanFactory、FactoryBean
代理AOP(JDK/CGLIB)
模板方法JdbcTemplate、RestTemplate
观察者ApplicationEvent
适配器HandlerAdapter
装饰器BeanWrapper
责任链拦截器链
策略Resource、InstantiationStrategy
委派DispatcherServlet
原型prototype 作用域

4.15 ★★Spring 6 / Spring Boot 3 有哪些重要变化?GraalVM Native Image 支持意味着什么?

📖 点击查看答案

Spring 6 / Spring Boot 3 的重要变化:

1. 基线升级

  • 要求 JDK 17+(不再支持 JDK 8/11)
  • Jakarta EE 9+(javax.* → jakarta.*)
java
// 之前 (javax)
import javax.servlet.http.HttpServletRequest;
import javax.persistence.Entity;

// 现在 (jakarta)
import jakarta.servlet.http.HttpServletRequest;
import jakarta.persistence.Entity;
  • 所有 javax 包名改为 jakarta(Servlet、JPA、Validation 等)

2. GraalVM Native Image 支持

  • Spring Boot 3 原生支持 AOT 编译
  • 应用可编译为本地可执行文件

3. 其他重要特性:

  • HTTP Interface(声明式 HTTP 客户端)
java
public interface UserClient {
    @GetExchange("/users/{id}")
    User getUser(@PathVariable("id") Long id);
}
// 自动生成实现类
  • Micrometer Observation API(统一可观测性)
  • 虚拟线程支持(JDK 21)
  • ProblemDetails(RFC 7807 错误响应标准)

GraalVM Native Image 意味着什么:

1. 极速启动

  • 传统 Spring Boot 启动:1-3 秒
  • Native Image:0.01-0.1 秒
  • 适合 Serverless、FaaS 场景

2. 低内存占用

  • 传统 JVM:200MB-1GB
  • Native Image:50MB-100MB
  • 适合容器化、微服务

3. 原理:AOT 编译

  • 编译时分析,把字节码编译为本地机器码
  • 不需要 JIT,启动即最高性能
  • 但失去 JIT 的运行时优化能力

4. 工作流程:

1. 编译 Java 代码为字节码
2. Spring AOT 处理:生成反射、资源、代理等元数据
3. GraalVM native-image 工具编译为本地可执行文件
4. 运行时不需要 JVM
bash
# Spring Boot 3 构建 Native Image
mvn -Pnative native:compile

# 或用 buildpacks
mvn spring-boot:build-image -Pnative

5. Native Image 的限制:

  • 不支持运行时动态类加载:所有类必须在编译时确定
  • 反射需配置:通过 reachability metadata 声明
  • 动态代理受限:需提前生成代理类
  • CGLIB、字节码增强需适配:Spring AOT 会处理
  • 构建时间长:几分钟到十几分钟
  • 调试困难:无 JVM 调试支持

6. Spring AOT 的作用:

  • 在编译时处理 Spring 应用:
    • 解析 Bean 定义
    • 生成 Bean 注册代码(避免运行时反射)
    • 处理 @Configuration、@Bean
    • 生成代理类
  • 把运行时的工作提前到编译时

7. 适用场景:

  • Serverless / FaaS:Lambda、Azure Functions
  • 微服务:快速启动、低内存
  • CLI 工具:命令行应用
  • 容器化:镜像更小

8. 不适用场景:

  • 长期运行的服务(JIT 优化更好)
  • 大量动态特性的应用
  • 需要运行时加载类的应用

Spring Boot 3 其他亮点:

  • @ConfigurationProperties 支持 Record
  • WebMvcConfigurer 改进
  • Log4j2 增强
  • 可观测性大幅增强(Micrometer、OpenTelemetry)

基于 4 份主流面试题库整合精选,共 200 题