Spring依赖注入Bean流程深入解析

 更新时间:2026年01月15日 09:11:37   作者:未来龙皇小蓝  
本文主要介绍了Spring框架中依赖注入和Bean创建流程,包括注入方式、生命周期、代理模式、注入流程、构造函数参数处理以及创建Bean的详细过程,感兴趣的朋友跟随小编一起看看吧

1-注入方式

常见的方式:

一个类注入另一个类,其实本质都是使用的构造函数,这里以@RequiredArgsConstructor举例,常见的也有@AllArgsConstructor本质相差无几

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
    private final UserService userService;
}

@RequiredArgsConstructor:会为所有 final 修饰的字段生成构造函数参数

//等价于
@Configuration
public class SecurityConfig {
    private final UserService userService;
    public SecurityConfig(UserService userService) {
        this.userService = userService;
    }
}

优点:

  • 使用 final 关键字确保依赖不会被意外修改
  • 利用Lombok:减少重复代码,提高开发效率

推荐使用 @RequiredArgsConstructor + final 字段

不建议使用:@Autowired、@Resource等方式

2-生命周期

在singleton下:

SecurityConfig Bean 生命周期 == UserService Bean 生命周期
  • 创建时注入
  • 容器不销毁 → 引用一直有效
  • 不会被替换
  • 不会重新注入

运行时等价于:

// 在存在 AOP(如 @Transactional)时,为代理对象
private final UserService userService = UserService$$Proxy@7a3f21;

3-代理模式

3-1.没有代理

正常写业务

public interface UserService {
    void save();
}
public class UserServiceImpl implements UserService {
    public void save() {
        System.out.println("保存用户");
    }
}

调用方:

UserService userService = new UserServiceImpl();
userService.save();

3-2.简单代理

public class UserServiceProxy implements UserService {
    private final UserService target;
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    @Override
    public void save() {
        System.out.println("开启事务");
        try {
            target.save();
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            throw e;
        }
    }
}

使用:

UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.save();

用一个“长得一样”的对象,包住真正对象,在调用前后加逻辑

有点像包装类,但是本质不同:

  • 为了给这个对象多加点功能(如加密、压缩、缓存结果),那是包装,能访问到本体,且自己亲自放进包装类中
  • 为了控制权限、延迟加载、单例保证,那是代理,且访问不到本体
  • 包装:我帮你多做点事;代理:你只能通过我做这件事

3-3.Spring的代理

Spring的代理 = 自动生成的代理类,Spring只是帮你自动干了这件事

UserService proxy =
    (UserService) Proxy.newProxyInstance(
        loader,
        new Class[]{UserService.class},
        (obj, method, args) -> {
            System.out.println("开启事务");
            Object result = method.invoke(target, args);
            System.out.println("提交事务");
            return result;
        }
    );

4-注入流程

4-1.概念

【启动期】
1. 扫描 BeanDefinition
2. 创建原始对象
3. BeanPostProcessor 判断是否命中 Advisor(如@Transactional相关注解)
4. 命中 → 用代理对象替代原始对象
5. 代理 / 原始对象作为最终 Bean 放入 IOC(singleton)
【运行期】
6. 所有依赖注入,拿到的都是这个最终 Bean
7. 所有 AOP 能力,只存在于代理对象上

只有涉及到增强行为才会创建代理类,比如你在方法上包裹@Transactional,或者你自定义的@Aspect,以及@Async / @Cacheable等等

4-2.示例

@Configuration
public class SecurityConfig {
    private final UserService userService;// Bean 实例(通常是代理对象)的引用,并且在该 Bean 生命周期内长期存在
    public SecurityConfig(UserService userService) {
        this.userService = userService;
    }
    public void test(User user){
        userService.saveData(user);
    }
}
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    private final UserMapper userMapper;
    @Transactional
    public void saveData(User user){
        userMapper.save(user);
    }
}

这样的代码:

  • Spring会首先在IOC容器中,按类型(by type)解析依赖,找到所有符合UserService的Bean,选出一个(或一组),注入(如果存在多个实现,会用@Primary@Qualifier,否则直接报错)
  • 这里因为UserServiceImpl内部挂上了@Transactional注解,所以创建了UserServiceImpl的实例后,就创建一个代理的Bean,即Proxy(UserServiceImpl),后续的注入都将使用代理的Bean

具体如下:

1. 解析 BeanDefinition(UserServiceImpl)
2. new UserServiceImpl()               ← 原始对象
3. BeanPostProcessor 检查 @Transactional
4. 命中事务切点
5. 创建代理对象 Proxy(UserServiceImpl)
6. 将代理对象注册为 UserServiceImpl 的最终 Bean 实例

5-构造函数的参数

是Spring在创建Bean时会调用其构造函数,然后根据构造函数的参数,会进行注入:

@Component
@Slf4j
public class PushDataStrategyManager {
    private final Map<Integer, PushDataStrategy> strategyMap = new HashMap<>();
	// 这里构造函数的参数是List<PushDataStrategy> strategies,Spring会找出所有相关的Bean放进来
    public PushDataStrategyManager(List<PushDataStrategy> strategies) {
        // 自动注册所有策略实现
        for (PushDataStrategy strategy : strategies) {
            strategyMap.put(strategy.getType(), strategy);
            log.info("注册推送策略: type={}, class={}", strategy.getType(), strategy.getClass().getSimpleName());
        }
    }
}

常见的Spring支持的构造函数如下,Spring会自动的进行操作:

构造参数Spring的行为
PushDataStrategy按类型查找Bean;必须唯一,否则报错
List<PushDataStrategy>查找容器中所有PushDataStrategy类型的Bean,按顺序注入
Set<PushDataStrategy>同上,不保证顺序
Map<String, PushDataStrategy>key = BeanName,value = Bean 实例

具体流程:

当 Spring 创建 PushDataStrategyManager 时:

  1. 解析构造函数参数
public PushDataStrategyManager(List<PushDataStrategy> strategies)
// Spring通过反射拿到泛型信息
// 参数类型:List
// 泛型参数:PushDataStrategy
  1. 发现这是一个集合类型依赖
isCollectionType(parameterType)// 是 List
  1. 解析集合的泛型元素类型
ResolvableType.forConstructorParameter(...)
// 得到
elementType = PushDataStrategy.class
  1. 去IOC容器中查找所有候选Bean
// 等价于
applicationContext.getBeansOfType(PushDataStrategy.class)

只要同时满足下方条件就收集:

  • 是 Bean(@Component / @Service / @Bean
  • 类型是 PushDataStrategy 或其子类
  • 没被 @Conditional 排除
  • 没被 @Profile 排除
  1. 排序(如果有顺序规则)
AnnotationAwareOrderComparator.sort(list)
  1. 注入到构造函数
// 最终等价于:
new PushDataStrategyManager(allStrategies);

6-创建Bean流程

大致的创建流程:

@Component
public class TestStrategy implements PushDataStrategy {}

启动期-注册阶段:

BeanDefinition:
- beanName = "testStrategy" # BeanName默认为类名首字母小写
- beanClass = TestStrategy.class
- scope = singleton
- lazy = false
- autowire = constructor
# 此时 还没有 this 实例地址
# Spring 存的是 BeanName → BeanDefinition(包含 class 信息)

IOC内部结构,Spring 核心容器本质是两张表:

  1. BeanDefinition Map(启动期)
Map<String, BeanDefinition>
"testStrategy" -> BeanDefinition(TestStrategy.class)

也可以去直接看代码,查看启动期注册信息,示例:

// Springboot主启动类:
@SpringBootApplication  // SpringBoot 核心注解,标记这是一个启动类
@EnableDiscoveryClient  // 开启 Nacos 注册发现
@ComponentScan(basePackages = {"org.myproject.user", "org.myproject.common"})// 公共模块扫描
public class UserServiceApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(UserServiceApplication.class, args);
        // debug查看beanDefinitionMap
        // 获取 BeanFactory
        DefaultListableBeanFactory beanFactory =
                (DefaultListableBeanFactory) context.getBeanFactory();
        // 查看所有 BeanDefinition 名称
        String[] beanNames = beanFactory.getBeanDefinitionNames();
        System.out.println("BeanDefinition 数量:" + beanNames.length);
        // 注意bean名字是类名的小写
        BeanDefinition bd = beanFactory.getBeanDefinition("userServiceImpl");
        // 查看某一个 BeanDefinition 的“元信息”
        System.out.println(bd.getBeanClassName());
        System.out.println(bd.getScope());
        System.out.println(bd.isLazyInit());
        // 看到类似:
        //org.myproject.user.service.impl.UserServiceImpl
        //singleton
        //false
    }
}
  1. Singleton Objects Map(运行期)
Map<String, Object>
"testStrategy" -> TestStrategy@5f3a4d

也可以去直接看代码,查看真正的实例,示例:

@SpringBootApplication  // SpringBoot 核心注解,标记这是一个启动类
@EnableDiscoveryClient  // 开启 Nacos 注册发现
@ComponentScan(basePackages = {"org.myproject.user", "org.myproject.common"})// 公共模块扫描
public class UserServiceApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(UserServiceApplication.class, args);
        DefaultListableBeanFactory beanFactory =
                (DefaultListableBeanFactory) context.getBeanFactory();
        // 强转为可访问单例池的类型
        DefaultSingletonBeanRegistry registry =
                (DefaultSingletonBeanRegistry) beanFactory;
        // 查看当前已经创建的单例 Bean
        String[] singletonNames = registry.getSingletonNames();
        System.out.println("已创建的 singleton 数量:" + singletonNames.length);
        // 查看某个 Bean 的真实实例
        Object bean = registry.getSingleton("userServiceImpl");
        System.out.println(bean);
        System.out.println(bean.getClass());
        //已创建的 singleton 数量:410
        //org.myproject.user.service.impl.UserServiceImpl@6f26e775
        //class org.myproject.user.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$9d7c1b34
        //user使用了事务注解,会创建代理类
        //包含 $$EnhancerBySpringCGLIB$$ 标识
        //这是 Spring CGLIB 动态代理的典型命名模式
    }
}

7-创建顺序

实际创建顺序假设:

@Component
public class PushDataStrategyManager {
    public PushDataStrategyManager(List<PushDataStrategy> strategies) {}
}

Spring 执行流程:

  1. 需要创建PushDataStrategyManager
→ 发现构造器参数:
List<PushDataStrategy>
  1. Spring尝试解析这个参数
→ 发现需要:
所有 PushDataStrategy 类型的 Bean
  1. Spring去查容器中已有的BeanDefinition
发现:
testStrategy
emailStrategy
smsStrategy
  1. 如果这些Bean尚未实例化
Spring 会:
先创建它们(递归)
  1. 将这些实例组成List
List.of(testStrategy, emailStrategy, smsStrategy)
  1. 调用Manager构造器
new PushDataStrategyManager(list)
  1. Manager实例化完成

总结:

  • Spring不是先“批量创建所有Bean”,而是在创建某个Bean时,递归创建它所依赖的Bean
  • 但由于:singletonnon-lazy,看起来像是“启动时全部创建完”

到此这篇关于Spring依赖注入Bean流程及其理解的文章就介绍到这了,更多相关Spring依赖注入Bean内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java使用BIO和NIO进行文件操作对比代码示例

    Java使用BIO和NIO进行文件操作对比代码示例

    这篇文章主要介绍了Java使用BIO和NIO进行文件操作对比代码示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • Java语言面向对象编程思想之类与对象实例详解

    Java语言面向对象编程思想之类与对象实例详解

    这篇文章主要介绍了Java语言面向对象编程思想之类与对象实例详解,还是十分不错的,这里给大家分享下,需要的朋友可以参考。
    2017-10-10
  • Java动态数组的实现过程

    Java动态数组的实现过程

    本文介绍了如何实现一个简单的动态数组,包括基础结构设计、核心功能实现、性能分析、实现特点以及改进建议,通过这个实现,我们能够更好地理解动态数组的工作原理和核心操作
    2026-01-01
  • Java 虚拟机栈详解分析

    Java 虚拟机栈详解分析

    在线程创建时,JVM会为每个线程创建一个单独的栈空间。JVM的栈内存不需要是连续的。JVM在栈上会进行两个操作:压入和弹出栈帧。对于一个特定的线程来说,栈被称为运行时栈。这个线程调用的每个方法会被存储在响应的运行时栈里,包括了参数,局部变量,计算媒介和其他数据
    2021-11-11
  • Mybatis-Plus条件构造器的具体使用方法

    Mybatis-Plus条件构造器的具体使用方法

    这篇文章主要介绍了Mybatis-Plus条件构造器的具体使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • mybatis-plus 自定义 Service Vo接口实现数据库实体与 vo 对象转换返回功能

    mybatis-plus 自定义 Service Vo接口实现数据库实体与 vo

    这篇文章主要介绍了mybatis-plus 自定义 Service Vo接口实现数据库实体与 vo 对象转换返回功能,本文通过实例图文相结合给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-08-08
  • MyBatis-Plus逆向工程——Generator的使用

    MyBatis-Plus逆向工程——Generator的使用

    这篇文章主要介绍了MyBatis-Plus逆向工程——Generator的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • 学习java多线程

    学习java多线程

    本文运用了大量的代码讲解了java多线程,它可以提高程序并行执行的速度,更快的响应程序。感兴趣的小伙伴一起来看看吧
    2021-08-08
  • Java实现批量向mysql写入数据的方法

    Java实现批量向mysql写入数据的方法

    这篇文章主要介绍了Java实现批量向mysql写入数据的方法,涉及java基于JDBC连接mysql数据库及写入数据的相关操作技巧,非常简单实用,需要的朋友可以参考下
    2017-12-12
  • 基于 SpringBoot 实现 MySQL 读写分离的问题

    基于 SpringBoot 实现 MySQL 读写分离的问题

    这篇文章主要介绍了基于 SpringBoot 实现 MySQL 读写分离的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-02-02

最新评论