01.01 Spring核心|IoC容器深度解析

01.01 Spring核心|IoC容器深度解析

导读

  • 目标:深入理解Spring IoC容器的核心机制,包括Bean定义、注册、生命周期、作用域等,掌握Spring的设计思想和实现原理。
  • 适用场景:Spring框架学习、源码阅读、面试准备、架构设计。
  • 前置知识:Java反射、设计模式(工厂模式、单例模式)

一、IoC核心概念

1.1 控制反转(Inversion of Control)

什么是控制反转?

控制反转(IoC)是一种设计原则,它将对象的创建、依赖关系的管理从应用程序代码中转移到框架或容器中。传统编程中,对象自己控制依赖的创建,而IoC让容器来控制这些依赖。

传统方式 vs IoC方式:

java 复制代码
// 传统方式(紧耦合)
public class UserService {
    // 问题1:硬编码依赖,无法替换实现
    private UserRepository userRepository = new UserRepositoryImpl();
    
    // 问题2:难以测试,无法mock依赖
    public void saveUser(User user) {
        userRepository.save(user);
    }
}

// IoC方式(松耦合)
public class UserService {
    // 依赖由容器注入,可以轻松替换实现
    private final UserRepository userRepository;
    
    // 构造函数注入(推荐)
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    // 或者使用setter注入
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    // 或者使用字段注入(不推荐,但常见)
    @Autowired
    private UserRepository userRepository;
}

IoC的优势:

  1. 解耦:对象不直接依赖具体实现,而是依赖接口
  2. 可测试:可以轻松注入mock对象进行单元测试
  3. 可维护:修改实现类不影响使用方
  4. 灵活性:运行时决定使用哪个实现

实战案例:订单服务

java 复制代码
// 接口定义
public interface PaymentService {
    boolean pay(Order order);
}

// 支付宝实现
@Component
public class AlipayService implements PaymentService {
    @Override
    public boolean pay(Order order) {
        // 支付宝支付逻辑
        return true;
    }
}

// 微信支付实现
@Component
public class WechatPayService implements PaymentService {
    @Override
    public boolean pay(Order order) {
        // 微信支付逻辑
        return true;
    }
}

// 订单服务(使用IoC)
@Service
public class OrderService {
    private final PaymentService paymentService;
    
    // 构造函数注入,Spring会自动选择合适的实现
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
    
    public void processOrder(Order order) {
        // 业务逻辑
        paymentService.pay(order);
    }
}

1.2 Bean容器

容器层次结构:

Spring提供了两个核心接口:BeanFactory 和 ApplicationContext。

复制代码
BeanFactory(基础容器接口)
    ├─ 延迟加载Bean
    ├─ 按需创建Bean
    └─ 轻量级容器
    
ApplicationContext(应用上下文,继承BeanFactory)
    ├─ 容器启动时创建所有单例Bean
    ├─ 国际化支持(MessageSource)
    ├─ 事件发布(ApplicationEventPublisher)
    ├─ 资源访问(ResourceLoader)
    └─ 自动注册BeanPostProcessor、BeanFactoryPostProcessor
        ├─ ConfigurableApplicationContext
        │   ├─ AbstractApplicationContext
        │   │   ├─ GenericApplicationContext
        │   │   ├─ AnnotationConfigApplicationContext(注解配置)
        │   │   └─ ClassPathXmlApplicationContext(XML配置)
        │   └─ WebApplicationContext(Web环境)
        │       └─ AnnotationConfigWebApplicationContext

BeanFactory vs ApplicationContext 对比:

特性 BeanFactory ApplicationContext
Bean加载时机 延迟加载(Lazy) 启动时加载所有单例Bean
国际化支持 ❌ ✅
事件发布 ❌ ✅
自动注册后处理器 ❌ ✅
性能 更快启动 启动稍慢,但运行时更快
使用场景 资源受限环境 大多数应用(推荐)

容器初始化方式:

java 复制代码
// 方式1:基于XML配置(传统方式,现在较少使用)
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean(UserService.class);

// 方式2:基于注解配置(Spring Boot默认方式)
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }
}

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

// 方式3:Spring Boot自动配置(最常用)
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

源码分析:容器启动流程

java 复制代码
// AbstractApplicationContext.refresh() 方法核心流程
public void refresh() throws BeansException {
    // 1. 准备刷新上下文
    prepareRefresh();
    
    // 2. 获取BeanFactory并加载Bean定义
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    // 3. 准备BeanFactory(设置类加载器、后处理器等)
    prepareBeanFactory(beanFactory);
    
    try {
        // 4. 允许后处理BeanFactory
        postProcessBeanFactory(beanFactory);
        
        // 5. 调用BeanFactoryPostProcessor
        invokeBeanFactoryPostProcessors(beanFactory);
        
        // 6. 注册BeanPostProcessor
        registerBeanPostProcessors(beanFactory);
        
        // 7. 初始化MessageSource(国际化)
        initMessageSource();
        
        // 8. 初始化事件广播器
        initApplicationEventMulticaster();
        
        // 9. 初始化特殊Bean(如Web容器)
        onRefresh();
        
        // 10. 注册监听器
        registerListeners();
        
        // 11. 实例化所有单例Bean(核心步骤)
        finishBeanFactoryInitialization(beanFactory);
        
        // 12. 完成刷新
        finishRefresh();
    } catch (BeansException ex) {
        // 清理资源
        destroyBeans();
        cancelRefresh(ex);
        throw ex;
    }
}

1.3 Bean定义与注册

Bean定义方式对比:

Spring提供了三种方式定义Bean,各有适用场景:

java 复制代码
// 方式1:XML配置(传统方式,现在较少使用)
// applicationContext.xml
<bean id="userService" class="com.example.UserService">
    <property name="userRepository" ref="userRepository"/>
</bean>

// 方式2:注解配置(最常用,Spring Boot默认)
@Component  // 或 @Service、@Repository、@Controller
public class UserService {
    @Autowired
    private UserRepository userRepository;
}

// 方式3:Java配置(灵活,适合复杂配置)
@Configuration
public class AppConfig {
    @Bean
    public UserRepository userRepository() {
        return new UserRepositoryImpl();
    }
    
    @Bean
    public UserService userService(UserRepository userRepository) {
        // 方法参数会自动注入
        return new UserService(userRepository);
    }
}

Bean注册流程详解:

java 复制代码
// Spring容器启动完整流程
┌─────────────────────────────────────────────────┐
│ 1. 加载配置源                                    │
│    - XML文件、注解类、Java配置类                  │
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 2. 解析Bean定义(BeanDefinition)                │
│    - 解析XML/注解,生成BeanDefinition对象         │
│    - BeanDefinition包含:类名、作用域、依赖等     │
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 3. 注册BeanDefinition到BeanFactory               │
│    - DefaultListableBeanFactory.registerBeanDefinition() │
│    - 存储到beanDefinitionMap中                    │
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 4. 调用BeanFactoryPostProcessor                  │
│    - 可以修改BeanDefinition(如PropertyPlaceholderConfigurer)│
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 5. 实例化Bean(单例模式,非延迟加载)             │
│    - AbstractBeanFactory.getBean()              │
│    - 调用构造函数创建实例                         │
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 6. 属性赋值(依赖注入)                           │
│    - 通过反射设置@Autowired字段                  │
│    - 调用setter方法                              │
│    - 构造函数参数注入                            │
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 7. 初始化前处理                                  │
│    - BeanPostProcessor.postProcessBeforeInitialization() │
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 8. 初始化                                        │
│    - @PostConstruct方法                          │
│    - InitializingBean.afterPropertiesSet()      │
│    - init-method                                 │
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 9. 初始化后处理                                  │
│    - BeanPostProcessor.postProcessAfterInitialization() │
│    - AOP代理在此创建                              │
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 10. Bean可以使用                                 │
└─────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────┐
│ 11. 容器关闭时销毁                               │
│     - @PreDestroy方法                           │
│     - DisposableBean.destroy()                  │
│     - destroy-method                            │
└─────────────────────────────────────────────────┘

BeanDefinition源码分析:

java 复制代码
// BeanDefinition接口定义了Bean的元数据
public interface BeanDefinition {
    // Bean的类名
    void setBeanClassName(String beanClassName);
    
    // Bean的作用域
    void setScope(String scope);
    
    // 是否单例
    boolean isSingleton();
    
    // 是否原型
    boolean isPrototype();
    
    // 是否延迟加载
    void setLazyInit(boolean lazyInit);
    
    // 依赖的Bean名称
    void setDependsOn(String... dependsOn);
    
    // 工厂方法名
    void setFactoryMethodName(String factoryMethodName);
}

1.4 Bean作用域

Spring支持的Bean作用域:

作用域 说明 使用场景 线程安全
singleton(默认) 容器中只有一个实例 无状态服务、工具类 需保证线程安全
prototype 每次获取都创建新实例 有状态对象、需要隔离的场景 天然线程安全
request 每个HTTP请求一个实例 Web环境,请求级别的数据 天然线程安全
session 每个HTTP Session一个实例 Web环境,用户会话数据 天然线程安全
application ServletContext级别 Web环境,应用级单例 需保证线程安全
websocket WebSocket会话级别 WebSocket连接 天然线程安全

详细说明与实战案例:

java 复制代码
// 1. Singleton(单例,默认)- 最常用
@Service
@Scope("singleton")  // 可以省略,默认就是singleton
public class UserService {
    // 容器中只有一个实例,所有地方共享
    // 优点:节省内存,性能好
    // 缺点:必须保证线程安全
    private int counter = 0;  // 危险!多线程不安全
    
    public void increment() {
        counter++;  // 需要加锁或使用AtomicInteger
    }
}

// 2. Prototype(原型)- 每次创建新实例
@Component
@Scope("prototype")
public class TaskProcessor {
    // 每次getBean()都创建新实例
    // 适用场景:有状态对象、需要隔离处理
    private String taskId;
    
    public void process(String taskId) {
        this.taskId = taskId;
        // 处理任务
    }
}

// 使用示例
@Autowired
private ApplicationContext applicationContext;

public void processTasks() {
    // 每次获取都是新实例
    TaskProcessor processor1 = applicationContext.getBean(TaskProcessor.class);
    TaskProcessor processor2 = applicationContext.getBean(TaskProcessor.class);
    // processor1 != processor2
}

// 3. Request(Web环境)- 每个HTTP请求一个实例
@Component
@Scope("request")
public class RequestContext {
    // 每个HTTP请求都有独立的实例
    // 适用场景:存储请求级别的数据
    private String requestId;
    private Long userId;
    
    public void setRequestId(String requestId) {
        this.requestId = requestId;
    }
}

// 4. Session(Web环境)- 每个Session一个实例
@Component
@Scope("session")
public class UserSession {
    // 每个用户Session都有独立的实例
    // 适用场景:存储用户会话数据
    private String username;
    private List<String> permissions;
}

// 5. 自定义作用域(高级用法)
@Component
@Scope("thread")  // 需要自定义Scope实现
public class ThreadLocalContext {
    // 每个线程一个实例
}

作用域选择建议:

  1. 默认使用singleton:大多数Bean应该是无状态的单例
  2. 有状态对象用prototype:如果对象有可变状态且需要隔离
  3. Web相关用request/session:存储请求或会话级别的数据
  4. 注意线程安全:singleton Bean必须保证线程安全

1.5 Bean生命周期

完整的Bean生命周期(详细版):

java 复制代码
┌─────────────────────────────────────────────────────┐
│ 阶段1:实例化(Instantiation)                        │
│   - 调用构造函数创建Bean实例                          │
│   - 此时Bean还未完全初始化,不能使用                 │
└─────────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────────┐
│ 阶段2:属性赋值(Populate Properties)                │
│   - 依赖注入:@Autowired、@Resource、@Value          │
│   - 设置Bean属性值                                   │
└─────────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────────┐
│ 阶段3:初始化前(Before Initialization)              │
│   - BeanPostProcessor.postProcessBeforeInitialization() │
│   - 可以修改Bean实例(如AOP代理)                     │
└─────────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────────┐
│ 阶段4:初始化(Initialization)                       │
│   执行顺序:                                          │
│   1. @PostConstruct方法(优先级最高)                │
│   2. InitializingBean.afterPropertiesSet()          │
│   3. init-method(XML配置或@Bean(initMethod))      │
└─────────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────────┐
│ 阶段5:初始化后(After Initialization)               │
│   - BeanPostProcessor.postProcessAfterInitialization() │
│   - AOP代理通常在此阶段创建                           │
└─────────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────────┐
│ 阶段6:Bean就绪,可以使用                            │
└─────────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────────┐
│ 阶段7:容器关闭,销毁前(Before Destruction)        │
│   - @PreDestroy方法                                  │
└─────────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────────┐
│ 阶段8:销毁(Destruction)                           │
│   执行顺序:                                          │
│   1. @PreDestroy方法                                 │
│   2. DisposableBean.destroy()                       │
│   3. destroy-method(XML配置或@Bean(destroyMethod))│
└─────────────────────────────────────────────────────┘

完整生命周期示例:

java 复制代码
@Component
public class UserService implements InitializingBean, DisposableBean {
    
    @Autowired
    private UserRepository userRepository;
    
    // 构造函数(阶段1)
    public UserService() {
        System.out.println("1. 构造函数执行");
    }
    
    // 依赖注入(阶段2)
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        System.out.println("2. 依赖注入: " + userRepository);
        this.userRepository = userRepository;
    }
    
    // 初始化前(阶段3)- 通过BeanPostProcessor
    // 需要自定义BeanPostProcessor
    
    // 初始化(阶段4)
    @PostConstruct
    public void postConstruct() {
        System.out.println("4.1 @PostConstruct执行");
    }
    
    @Override
    public void afterPropertiesSet() {
        System.out.println("4.2 InitializingBean.afterPropertiesSet()执行");
    }
    
    // 初始化后(阶段5)- 通过BeanPostProcessor
    
    // 业务方法
    public void doSomething() {
        System.out.println("6. Bean使用中");
    }
    
    // 销毁前(阶段7)
    @PreDestroy
    public void preDestroy() {
        System.out.println("7. @PreDestroy执行");
    }
    
    // 销毁(阶段8)
    @Override
    public void destroy() {
        System.out.println("8. DisposableBean.destroy()执行");
    }
}

// 自定义BeanPostProcessor示例
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof UserService) {
            System.out.println("3. BeanPostProcessor.postProcessBeforeInitialization()执行");
        }
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof UserService) {
            System.out.println("5. BeanPostProcessor.postProcessAfterInitialization()执行");
        }
        return bean;
    }
}

生命周期回调方法选择:

方法 优点 缺点 推荐度
@PostConstruct/@PreDestroy 标准JSR-250,不依赖Spring 需要JSR-250支持 ⭐⭐⭐⭐⭐
InitializingBean/DisposableBean Spring原生接口 耦合Spring框架 ⭐⭐⭐
init-method/destroy-method 灵活,可配置 需要额外配置 ⭐⭐⭐⭐

实战案例:资源初始化与清理:

java 复制代码
@Service
public class DatabaseConnectionPool implements InitializingBean, DisposableBean {
    
    private ConnectionPool pool;
    
    @PostConstruct
    public void init() {
        System.out.println("初始化数据库连接池");
        // 初始化连接池
        pool = new ConnectionPool();
        pool.init();
    }
    
    @Override
    public void afterPropertiesSet() {
        // 验证连接池是否正常
        if (!pool.isHealthy()) {
            throw new IllegalStateException("连接池初始化失败");
        }
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("开始清理资源");
    }
    
    @Override
    public void destroy() {
        if (pool != null) {
            pool.close();
            System.out.println("连接池已关闭");
        }
    }
}

高频面试问答(深度解析)

1. Spring IoC容器的实现原理?

标准答案:

  • BeanDefinition:Bean定义信息,存储Bean的元数据(类名、作用域、依赖等)
  • BeanFactory:Bean工厂接口,负责Bean的创建和管理
  • ApplicationContext:应用上下文,继承BeanFactory,提供更多企业级功能
  • 依赖注入:通过反射机制实现,支持构造函数、setter、字段注入

深入追问与回答思路:

Q1: BeanFactory vs ApplicationContext?

java 复制代码
// BeanFactory:延迟加载,按需创建Bean
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
// 此时Bean还未创建
UserService userService = factory.getBean("userService");  // 此时才创建

// ApplicationContext:容器启动时创建所有单例Bean
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 容器启动时已经创建了所有单例Bean,启动稍慢但运行时更快
UserService userService = context.getBean("userService");  // 直接获取

对比总结:

  • BeanFactory:轻量级,延迟加载,适合资源受限环境
  • ApplicationContext:功能丰富,启动时加载,适合大多数应用(推荐)

Q2: 循环依赖如何解决?

Spring通过三级缓存机制解决单例Bean的循环依赖问题:

java 复制代码
// 三级缓存定义(DefaultSingletonBeanRegistry)
// 1. singletonObjects:一级缓存,存放完整的Bean(已初始化完成)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 2. earlySingletonObjects:二级缓存,存放早期Bean(未完成属性注入)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 3. singletonFactories:三级缓存,存放Bean工厂(ObjectFactory)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

// 解决流程(A依赖B,B依赖A):
// 步骤1:创建A,调用getSingleton("A")
//   - 一级缓存:无
//   - 二级缓存:无
//   - 三级缓存:无
//   - 创建A实例(未完成属性注入),放入三级缓存

// 步骤2:A需要注入B,调用getBean("B")
//   - 创建B实例(未完成属性注入),放入三级缓存

// 步骤3:B需要注入A,调用getBean("A")
//   - 一级缓存:无
//   - 二级缓存:无
//   - 三级缓存:有A的ObjectFactory
//   - 从三级缓存获取A的早期对象,放入二级缓存,返回给B

// 步骤4:B注入A完成,B初始化完成
//   - B从三级缓存移除,放入一级缓存

// 步骤5:A注入B完成,A初始化完成
//   - A从二级缓存移除,放入一级缓存

循环依赖的限制:

  • ✅ 支持:单例Bean的构造函数注入循环依赖(通过三级缓存)
  • ❌ 不支持:原型Bean的循环依赖(每次都创建新实例)
  • ❌ 不支持:构造函数注入的循环依赖(无法解决,会抛出异常)

Q3: 依赖注入的方式有哪些?优先级如何?

java 复制代码
// 方式1:构造函数注入(推荐,Spring官方推荐)
@Service
public class UserService {
    private final UserRepository userRepository;
    
    // 优点:强制依赖、不可变、便于测试
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

// 方式2:setter注入
@Service
public class UserService {
    private UserRepository userRepository;
    
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

// 方式3:字段注入(不推荐,但常见)
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
}

// 方式4:方法注入(特殊场景)
@Service
public class UserService {
    @Autowired
    public void injectDependencies(UserRepository userRepository, 
                                   OrderRepository orderRepository) {
        // 可以注入多个依赖
    }
}

注入优先级(当有多种注入方式时):

  1. 构造函数注入
  2. setter注入
  3. 字段注入

Q4: @Autowired、@Resource、@Inject的区别?

注解 来源 默认注入方式 是否支持required 是否支持name
@Autowired Spring byType,找不到则byName ✅ ❌
@Resource JSR-250 byName,找不到则byType ❌ ✅
@Inject JSR-330 byType ✅ ✅(需配合@Named)
java 复制代码
// @Autowired:Spring专用,按类型注入
@Autowired
private UserRepository userRepository;  // 按UserRepository类型查找

@Autowired(required = false)  // 允许为null
private Optional<UserRepository> userRepository;

// @Resource:JSR-250标准,按名称注入
@Resource(name = "userRepositoryImpl")  // 按名称查找
private UserRepository userRepository;

// @Inject:JSR-330标准,功能类似@Autowired
@Inject
private UserRepository userRepository;

Q5: 如何自定义Bean的作用域?

java 复制代码
// 1. 实现Scope接口
public class ThreadScope implements Scope {
    private final ThreadLocal<Map<String, Object>> threadLocal = 
        ThreadLocal.withInitial(HashMap::new);
    
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> scope = threadLocal.get();
        return scope.computeIfAbsent(name, k -> objectFactory.getObject());
    }
    
    @Override
    public Object remove(String name) {
        return threadLocal.get().remove(name);
    }
    
    // ... 其他方法
}

// 2. 注册自定义作用域
@Configuration
public class AppConfig {
    @Bean
    public static CustomScopeConfigurer customScopeConfigurer() {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        configurer.addScope("thread", new ThreadScope());
        return configurer;
    }
}

// 3. 使用自定义作用域
@Component
@Scope("thread")
public class ThreadLocalBean {
    // 每个线程一个实例
}

延伸阅读

相关推荐
Lambert281几秒前
SAA 停摆的答案:前团队的 ARGI 我上手跑了一遍,缝了三针才跑通
后端·openai·ai编程
布吉岛的石头1 分钟前
Java 程序员第 49 阶段11:Java 接 BERT:用 ONNX Runtime 做句向量推理
java·人工智能·python·深度学习·bert·transformer
niucloud-admin10 分钟前
JAVA V6 多商户商城 开发文档——JAVA服务端
java·开发语言
alexlifexyz24 分钟前
写了个 Claude Code 插件:AI 写的 Java 违反阿里规约,就写不进文件
java
轻舟技术A25 分钟前
Pi 的 Rust 实现来了:rpi,不止一个终端 Agent
开发语言·后端·rust
站大爷IP34 分钟前
Python的字典遍历把我坑惨了,原来items和keys的区别这么大
后端
威哥爱编程37 分钟前
【AI全栈12-01】Spring Boot 为何是 AI 全栈后端优选
java·spring boot·后端
汉堡大王952738 分钟前
GPT-6 上线 48 小时,我扒开了 Intelligent UI 的运行机制:DIL、沙箱 Worker 和一个 React 式协调器
前端·javascript·后端
用户EasyAdminBlazor39 分钟前
EasyAdminBlazor 实战:从 Entity 到完整 Admin Blazor 后台详解
后端