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的方法时会导致事务失效,因为当前对象不是代理对象。解决方法可在当前被代理对象中自己注入自己获得代理对象,用代理对象调用另一个事务方法。
相关推荐
醒了就刷牙17 分钟前
黑马Java面试教程_P9_MySQL
java·mysql·面试
m0_7482336424 分钟前
SQL数组常用函数记录(Map篇)
java·数据库·sql
编程爱好者熊浪1 小时前
JAVA HTTP压缩数据
java
吴冰_hogan1 小时前
JVM(Java虚拟机)的组成部分详解
java·开发语言·jvm
白宇横流学长2 小时前
基于java出租车计价器设计与实现【源码+文档+部署讲解】
java·开发语言
数据小爬虫@4 小时前
Java爬虫实战:深度解析Lazada商品详情
java·开发语言
咕德猫宁丶4 小时前
探秘Xss:原理、类型与防范全解析
java·网络·xss
F-2H6 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
苹果酱05676 小时前
「Mysql优化大师一」mysql服务性能剖析工具
java·vue.js·spring boot·mysql·课程设计
武昌库里写JAVA6 小时前
【MySQL】7.0 入门学习(七)——MySQL基本指令:帮助、清除输入、查询等
spring boot·spring·毕业设计·layui·课程设计