SpringBoot极简教程 · Spring Boot SpringCloud

spring中Bean的创建流程

2022-01-05  本文已影响0人  杯叔书

spring中的Bean是如何创建的?

带着这个问题,从一个简单的例子开始一探究竟。

  public class TestSpring {

    public static void main(String[] args){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = (UserService) context.getBean("userService");
        userService.test();
    }
}

这是一个基于JavaConfig方式来创建spring容器,spring容器创建完之后就开始尝试获取UserService对象,接着调用userService的test方法,很简单的一个例子。
先贴出UserService的代码:

@Service
@LogAround(bizTag = "UserService")
public class UserService implements BeanNameAware,InitializingBean {

    @Autowired
    private OrderService orderService;

    private User adminUser;

    private String beanName;

    /**
     * 构造函数
     */
    public UserService(){
        System.out.println("创建普通对象-构造函数");
    }

    /**
     * BeanNameAware自动给UserService设置beanName
     * @param beanName
     */
    @Override
    public void setBeanName(String beanName) {
        System.out.println("aware自动设值-BeanNameAware");
        this.beanName = beanName;
    }

    /**
     * 初始化前
     */
    @PostConstruct
    public void loadAdminUser(){
        System.out.println("初始化前-PostConstruct");
        User user = new User();
        user.setId(1L);
        user.setName("admin");
        this.adminUser = user;
    }

    /**
     * 初始化
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化-afterPropertiesSet");
    }

    public String getBeanName(){
        System.out.println(beanName);
        return beanName;
    }

    public User getAdminUser() {
        return adminUser;
    }
    public void test(){
        System.out.println("test");
    }
}

该如何入手分析UserService这个Bean是如何创建的呢?
第一步,先想下spring帮我们创建的Bean和我们自己在程序中new UserService()有什么区别?可能区别有很多,比如spring帮我们创建的bean是由spring管理的,默认是单例的等。但是不管怎么样spring要想创建Bean,第一步肯定是要拿到构造函数通过反射的方式在Spring容器中先new出这么一个对象,我们暂且叫它普通对象,此时和我们自己在程序中new UserService()并没有什么区别,就是创建一个普通的对象。
那第二歩会做什么呢?此时这个普通对象里面的属性是没有值的。bean和new出的对象还有一个区别就是spring会帮我们的bean中的属性做注入(依赖注入),这样bean中的属性才会有值,第二歩就是会对@Autowired注解修饰的属性进行依赖入。
依赖注入之后,还会aware接口设值,有初始化前,初始化和初始化后的动作,这些步骤都做完之后才会生成一个真正的Bean而不是普通的对象。
实践是检验真理的唯一标准,run一下上面的main方法,先看下bean创建的主要步骤后在逐个解析。


控制台输出

如上图:

  1. 创建普通对象-构造函数
  2. 对@Autowired注解的属性进行赋值(依赖注入)
  3. aware自动设值-BeanNameAware
  4. 初始化前-PostConstruct
  5. 初始化-afterPropertiesSet
  6. 其实还有一个初始后的动作

Bean的创建流程

通过上面的分析和代码的运行结果可以大致的看出spring中bean的创建有以下几个主要流程:


创建流程
  1. 利用该类的构造方法来实例化得到一个对象(但是如果一个类中有多个构造方法, Spring则会进行选择,这个叫做推断构造方法,下文在详细介绍)
  2. 得到一个对象后,Spring会判断该对象中是否存在被@Autowired注解了的属 性,把这些属性找出来并由Spring进行赋值(依赖注入)
  3. 依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、 BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前 对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、 setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数
  4. Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解 了,如果存在,Spring会调用当前对象的此方法(初始化前),上面的代码中初始化了一个adminUser的数据。
  5. 紧接着,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就 表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当
    前对象中的afterPropertiesSet()方法(初始化)
  6. 最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完 了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化 后)
    关于第6步控制台没办法打出日志,因为初始后涉及到spring的源码操作,但是可以通过断点看一下,提一句例子中UserService是被LogAspect切面切的。


    AOP最终生成代理对象

    如果UserService类@LogAround(bizTag = "UserService")注解去掉,就说明UserService类不需要AOP,那最终生成的Bean就是构造函数所得到的对象。
    有AOP的话那么Bean就是UserService的代理类所实例化得到的对象。

这样,一个Bean就创建完了,创建完之后呢?
如果当前Bean是单例Bean,那么会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象。这样下次getBean时就可 以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就 是单例池)
如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次 getBean时会再次执行上述创建过程,得到一个新的Bean对象。

推断构造方法

至此,我们清楚了Bean的创建流程,那如果UserService中有多个构造函数呢?第一步还能顺利的创建一个普通对象吗?这里面涉及到一个概念推断构造方法,就是spring会去推断用哪个构造方法来创建出普通对象。
做几个小实验:

public UserService(){
        System.out.println("创建普通对象-构造函数");
    }
    public UserService(OrderService orderService){
        System.out.println("创建普通对象-构造函数有orderService参数");
    }
    public UserService(OrderService orderService,String beanName){
        System.out.println("创建普通对象-构造函数有orderService和beanName参数");
    }

初始有这三个构造方法,一个是无参构造方法,一个是只有一个参数,一个是有两个参数。
场景1:和初始情况一样,Bean能够正常创建,并且打印"创建普通对象-构造函数"
场景2:注释掉第1个构造方法,创建Bean的时候就报异常了No default constructor found具体如下
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in file [D:\work\project\test\target\classes\com\test\service\UserService.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.test.service.UserService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.test.service.UserService.<init>()
场景3:注释掉第1个和第3个构造方法,能创建成功并打印"创建普通对象-构造函数有orderService参数"
场景4:注释掉第1个和第2个构造方法,创建失败,第二个参数String类型没地方拿到值。
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in file [D:\work\project\test\target\classes\com\test\service\UserService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
以上几个场景总结下:

  1. 如果一个类只有一个构造方法,那么没得选择,只能用这个构造方法。但是有参的构造方法,参数必须是spring的Bean这样spring才能拿到进行赋值。
  2. 如果一个类存在多个构造方法,Spring不知道如何选择,就会看是否有无参的构 造方法,因为无参构造方法本身表示了一种默认的构造方法。
  3. 如果都没有构造方法,就是用默认的无参构造方法来创建。
    其实多个构造函数,也可以手动指定告诉spring用哪个构造函数来创建,那就是加了@Autowired注解


    指定构造方法

    我们通常说的依赖注入,
    属性注入
    @Autowired
    private OrderService orderService;
    这边的构造方法
    public UserService(OrderService orderService){
    System.out.println("创建普通对象-构造函数有orderService参数");
    }
    就是构造函数注入

依赖注入流程

不管是属性注入还是构造方法注入,能提供的信息只有两个一个是类型OrderService ,一个是名字orderService。那到底是根据类型注入的还是根据名字注入的呢?
假设根据名字注入的那刚好有一个其他类型的Bean名字也叫orderService那注入的时候岂不是会类型不匹配异常。比如说刚好有一个OrderBaseService类但是beanName也叫orderService,如果根据名字注入的话拿到的是OrderBaseService对象显然类型不匹配。所以注入通常是先根据类型来查找的:

  1. 先根据入参类型找,如果只找到一个不用管name,那就直接用来作为入参
  2. 如果根据类型找到多个,则再根据入参名字来确定唯一
  3. 最终如果没有找到,则会报错,无法创建当前Bean对象

代理对象生成

代理对象通常是AOP的时候会生成代理对象还有一种就是开启事务的时候也会生成代理对象。否则的话Bean都是直接根据构造函数生成对象在进行依赖注入和初始化等流程。

AOP代理对象生成

AOP就是进行动态代理,在创建一个Bean的过程中,Spring在最后一步会去判断当前正在 创建的这个Bean是不是需要进行AOP,如果需要则会进行动态代理。

如何判断当前Bean对象需不需要进行AOP:

  1. 找出所有的切面Bean
  2. 遍历切面中的每个方法,看是否写了@Before、@After等注解
  3. 如果写了,则判断所对应的Pointcut是否和当前Bean对象的类是否匹配
  4. 如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行AOP

利用cglib进行AOP的大致流程:

  1. 生成代理类UserServiceProxy,代理类继承UserService
  2. 代理类中重写了父类的方法,比如UserService中的test()方法
  3. 代理类中还会有一个target属性,该属性的值为被代理对象(也就是通过 UserService类推断构造方法实例化出来的对象,进行了依赖注入、初始化等步骤的 对象)
  4. 代理类中的test()方法被执行时的逻辑如下:
    a. 执行切面逻辑(@Before)
    b. 调用target.test()
    当我们从Spring容器得到UserService的Bean对象时,拿到的就是UserServiceProxy所生 成的对象,也就是代理对象。
    UserService代理对象.test()--->执行切面逻辑--->target.test(),注意target对象不是代理 对象,而是被代理对象。

事务代理对象生成

Spring事务 当我们在某个方法上加了@Transactional注解后,就表示该方法在调用时会开启Spring事 务,而这个方法所在的类所对应的Bean对象会是该类的代理对象。
Spring事务的代理对象执行某个方法时的步骤:

  1. 判断当前执行的方法是否存在@Transactional注解
  2. 如果存在,则利用事务管理器(TransactionMananger)新建一个数据库连接
  3. 修改数据库连接的autocommit为false
  4. 执行target.test(),执行程序员所写的业务逻辑代码,也就是执行sql
  5. 执行完了之后如果没有出现异常,则提交,否则回滚
    注意:Spring事务是否会失效的判断标准:某个加了@Transactional注解的方法被调用时,要判 断到底是不是直接被代理对象调用的,如果是则事务会生效,如果不是则失效。

总结

Spring中Bean的创建过程其实就是从一个普通对象蜕变成Bean的一个过程,蜕变包括依赖注入,初始化等步骤。最后在看下这个类是否有被AOP或开启事务有的话会额外生成代理对象作为Bean。

上一篇下一篇

猜你喜欢

热点阅读