Spring依赖注入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 时:
- 解析构造函数参数
public PushDataStrategyManager(List<PushDataStrategy> strategies) // Spring通过反射拿到泛型信息 // 参数类型:List // 泛型参数:PushDataStrategy
- 发现这是一个
集合类型依赖
isCollectionType(parameterType)// 是 List
- 解析集合的泛型元素类型
ResolvableType.forConstructorParameter(...) // 得到 elementType = PushDataStrategy.class
- 去IOC容器中查找所有候选Bean
// 等价于 applicationContext.getBeansOfType(PushDataStrategy.class)
只要同时满足下方条件就收集:
- 是 Bean(
@Component/@Service/@Bean) - 类型是
PushDataStrategy或其子类 - 没被
@Conditional排除 - 没被
@Profile排除
- 排序(如果有顺序规则)
AnnotationAwareOrderComparator.sort(list)
- 注入到构造函数
// 最终等价于: 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 核心容器本质是两张表:
- 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
}
}
- 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 执行流程:
- 需要创建
PushDataStrategyManager
→ 发现构造器参数: List<PushDataStrategy>
- Spring尝试解析这个参数
→ 发现需要: 所有 PushDataStrategy 类型的 Bean
- Spring去查容器中已有的BeanDefinition
发现: testStrategy emailStrategy smsStrategy
- 如果这些Bean尚未实例化
Spring 会: 先创建它们(递归)
- 将这些实例组成List
List.of(testStrategy, emailStrategy, smsStrategy)
- 调用Manager构造器
new PushDataStrategyManager(list)
- Manager实例化完成
总结:
- Spring不是先“批量创建所有Bean”,而是在创建某个Bean时,递归创建它所依赖的Bean
- 但由于:
singleton、non-lazy,看起来像是“启动时全部创建完”
到此这篇关于Spring依赖注入Bean流程及其理解的文章就介绍到这了,更多相关Spring依赖注入Bean内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
