spring 创建bean的过程

spring 创建bean的过程

生成BeanDefinition -- > 合并BeanDefinition --> 创建单例Bean对象 (非懒加载) --> 依赖注入 (属性赋值) --> 初始化前 (@PostConstruct)-->初始化 (InitializingBean)-->初始化后 (AOP)-->bean

AbstractAutowireCapableBeanFactory.doCreateBean (创建bean) --> instanceWrapper.getWrappedInstance (实例化bean) --> populateBean (填充属性)-->initializeBean (初始化)-->applyBeanPostProcessorsAfterInitialization(AOP)

生成BeanDefinition

spring启动时会进行扫描,会先调用

org.Springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComonents(String basePackage) 扫描某个包路径,并得到BeanDefinition的Set集合.

  1. 首先,通过ResourcePatternResolver获得指定包路径下的所有.class文件(封装成Resource对象)
  2. 遍历每个Resource对象
  3. 利用MetadataReaderFactory解析Resourece对象,得到MetadataReader
  4. 利用MetadataReader的具体实现类进行excludeFilters和includeFilters,以及条件注解@Conditional的筛选
  5. 筛选通过后,基于metadataReader生成ScannedGenericBeanDefinition
  6. 在基于metadataReader判断对应的是否是接口或者抽象类
  7. 如果筛选通过则表示扫描到一个bean,将ScannedGenericBeanDefinition加入结果集
  8. Definition对象的加载采用ASM技术,并没有加载到jvm
  9. MetadataReader表示类的元数据读取器,主要包含了一个AnnotationMetadata,可以获取
    类名
    父类名
    所以实现接口名
    所有内部类名
    判断是不是抽象类
    判断是不是接口
    判断是不是注解
    获取拥有某个注解的方法集合
    获取类上所有注解信息
    获取类上添加的所有注解类型集合

在创建bean时,会创建BeanDefiniton对象,该对象会存储bean的一些注解信息,bean信息等。

模拟创建BeanDefinition:

复制代码
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(UserService.class);
beanDefinition.setScope("prototype");
//注册bean
context.registerBeanDefinition("userService",beanDefinition);

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(context);
reader.register(UserService.class);

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.scan("com.example");

合并BeanDefinition

spring支持创建父子bean,则子bean对应的BeanDefinition需要合并父Bean的BeanDefinition的信息。

创建单例Bean对象

  • 这一步就会进行实例化bean,如果.class文件还没有加载,会在实例化前进行加载。

  • 在类加载完后,可以实现InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(),该方法会在加载完所有类,但是在bean实例化前被调用

  • postProcessBeforeInstantiation可以有返回值,如果返回某个对象,则spring不会对该对象进行实例化和依赖注入,会直接进入初始化后。

    @Component
    public class UserBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
    throws BeansException {
    if ("userService".equals(beanName)) {
    System.out.println("实例化前");
    return new UserService();
    }
    return null;
    }
    }

调用构造方法

推断构造方法:

  • bean实例化默认调用无参构造方法
  • bean实例化时如果有一个非无参构造方法则调用该方法
  • bean实例化时如果有多个构造方法则调用无参方法
  • bean实例化时如果有多个构造方法且没有无参方法,抛出异常
  • bean实例化时有参构造方法的入参必须是对象,spring容器中如果没有对应bean,抛出异常。
  • bean实例化时有参构造会,spring容器进行参数注入时会先根据类型查找过滤,然后再根据bean的名称过滤。 所以:
    1.如果存在多个该类型的bean,一个同名的bean则注入该bean;
    2.如果存在一个该类型的bean,bean名称和入参不同,注入该bean;
    3.如果存在多个该类型的bean,bean名称和入参都不相同,抛出异常。

依赖注入(属性赋值)

例如@Autowired,初始化前会通过遍历object.class.getFields(),判断field.isAnnotationPresent(Autowired.class),拿到对应的field进行依赖注入。

初始化前

@PostConstruct:初始化前会通过遍历object.class.getMethods(),判断method.isAnnotationPresent(PostConstruct.class),然后method.invoke()

所以我们可以在bean中的方法上加上@PostConstruct方法,使该方法可以在bean初始化前被调用。

初始化

InitalizingBean:初始化时会判断bean instance of InitalizingBean(是否实现InitalizingBean接口),并且 调用其afterPropertiesSet()。

所以可以在Bean上实现InitializingBean接口,并实现afterPropertiesSet方法,使其在bean初始化时被调用。

BeanPostProcessor:BeanPostProcessor有初始化前和初始化后的方法,同样可以自己实现

复制代码
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String value() default "";
}

@Component
public class UserService {


    @TestAnnotation("test")
    private String test;

}

@Component
public class TestBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof UserService){
            for (Field field : bean.getClass().getFields()) {
                if(field.isAnnotationPresent(TestAnnotation.class)){
                    try {
                        field.set(bean,field.getAnnotation(TestAnnotation.class));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return bean;
    }
}

初始化后

  • 如果进行AOP会产生一个代理对象,代理对象会成为spring的AOP
  • 该代理对象不会有依赖注入的bean
  • 该代理对象会继承被代理的对象,并且代理对象会先调用AOP逻辑
  • 该代理对象会放入被代理对象,在AOP逻辑完成后,使用被代理对象调用方法(被代理对象拥有依赖注入)。
  • AOP中 joinPoint.getTarget()可以获取被代理对象
  • 遍历所有的切面bean,遍历切面bean中方法,找到被代理对象的方法时则表示该被代理对象经行了AOP,则将其缓存到map,被代理对象触发AOP时使用
  • spring事务也会生成代理对象:1.判断是否有注解@Transactional;2.创建一个数据库连接(事务管理器dataSource创建);3.conn.autocommit = false(修改属性,关闭自动提交事务);4.执行被代理对象方法;5.提交事务或者回滚。
  • 当在上一点的第四点中,被代理对象中调用另一个带有@Trasactional的方法时会导致事务失效,因为当前对象不是代理对象。解决方法可在当前被代理对象中自己注入自己获得代理对象,用代理对象调用另一个事务方法。
相关推荐
我是无敌小恐龙1 小时前
Java基础入门Day10 | Object类、包装类、大数/日期类、冒泡排序与Arrays工具类 超详细总结
java·开发语言·数据结构·算法·贪心算法·排序算法·动态规划
极客先躯1 小时前
高级java每日一道面试题-2025年12月07日-实战篇[Dockerj]-Docker daemon 的配置文件在哪里?常用的配置项有哪些?
java·docker·配置文件的实际位置·配置文件的格式规则·常用配置项全景与分类·配置如何生效·daemon 配置折射架构思维
云烟成雨TD1 小时前
Spring AI Alibaba 1.x 系列【49】状态图运行时引擎:CompiledGraph 源码解析
java·人工智能·spring
Tutankaaa1 小时前
从10队到50队:知识竞赛软件的高并发场景如何设计?
java·经验分享·后端·spring
下次再写2 小时前
微服务架构实战:Spring Boot + Spring Cloud 从入门到精通
java·spring boot·spring cloud·微服务架构·服务注册与发现·分布式系统·api网关
bang冰冰2 小时前
Trae工具安装和使用教程(新手零基础入门,全程无坑)
java·人工智能·python
阿丰资源2 小时前
基于Spring Boot的网上摄影工作室系统(源码一键运行)
java·spring boot·后端
阿维的博客日记2 小时前
容器是怎么管理 Bean 的?
java·bean
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第40题:Java中的深拷贝和浅拷贝有什么区别
java·开发语言·后端·面试
@小匠2 小时前
云之家表单数据解析 skills (yzj-form-parser)
java