Spring中Bean的生命周期

在Spring中创建一个Bean,会经历其生命周期,这里有侧重的介绍一下Bean的创建生命周期,总体思路为:
UserService.class--->推断构造方法--->普通对象--->依赖注入--->初始化前--->初始化--->初始化后--->Bean对象--->放入单例池Map
我们将其拆开,一个步骤一个步骤来看:

推断构造方法

什么是推断构造方法?顾名思义,是由Spring去推断我们的类的构造方法,根据不同的情况,Spring会调用不同的构造方法,我将其分为2类:

  1. 有无参构造方法:如果没写构造方法或只有一个无参构造方法,或者是很多构造方法但其中包含无参构造方法,那么Spring就会默认使用无参构造方法去生成普通对象,这是显而易见的(有无参当然默认用无参)
  2. 没有无参构造方法:如果显示写了某个或某几个构造方法,那么Spring会调用哪个呢?
    首先,如果有某个构造方法上添加了@Autowired注解,那么Spring就会使用这个构造方法
    如果没有,那就会报错。

在调用构造方法时,Spring传的参数也是有讲究的:

public class UserService{
        public OrderService orderService;
        UserService(OrderService orderService){}
}

Spring会自动检测到构造方法需要一个OrderService类型的名为orderService的对象,则Spring会去单例池中找(不理解单例池可以去看我的另一篇文章),注意,此处是先按类型找,再按名称找。

问题出现了,了解单例池的会知道,单例池中是以BeanName为Key存储的,即Map<BeanName,Object>,为什么这里又先按类型去找呢?

想要解决这个问题,我们需要先明确单例是什么意思:这里的“单例”和设计模式中的“单例”不是一个东西,不是说Spring单例池中只有一个OrderService类型的Bean,而是说,Spring每次getBean("orderService")得到的结果都是一个Bean,也就是说,单例池中可以有多个OrderService类型的Bean,我们这里命名为orderService,orderService1,orderService2,orderService3

所以上面的构造方法的参数,先按类型找找到了四个OrderService类型的Bean,又按名称找到了名为orderService的唯一的Bean,所以到底为什么会设计成先类型后名字呢?

我认为,这是Spring团队对程序员代码容错性的一种让步,因为在90%的情况下,可能单例池中某中类型的Bean只有一个,如果现在单例池中只有一个OrderService类型的Bean,而某个构造方法是这样的:

public class UserService{
        public OrderService orderService;
        UserService(OrderService os){}
}

可以看到,参数名称是os,但按照Spring的规则,他还是可以找到对应的那个Bean,但如果是先按名称去查找,这样由程序员造成的漏洞可能会更多,因为你不能保证"os"这个名称的Bean在单例池中是OrderService类型,它可能是其他类型,这会导致奇怪的错误。

依赖注入

其实在上边构造器中已经介绍了使用构造器注入的过程,即先ByType后ByName,这里不多介绍,另外,@Autowired注解也是去单例池中找,先ByType后ByName

初始化前

初始化前,这有什么好说的?
我们可以使用@PostConstruct注解加在某个方法上,这样该方法会在依赖注入后执行,一般可用于某些属性的初始化赋值,在底层实际上Spring通过反射去查哪个方法上加了该注解,即去执行

初始化

初始化阶段,可以使该类实现接口InitializingBean,并重写方法afterPropertiesSet,便可进行属性初始化赋值,在底层并不是通过反射去执行,而是通过instance of来判断

初始化后

重点来了,为什么初始化后这个阶段是重点呢?因为它包含了Spring非常重要的一个东西:AOP,我们知道,AOP底层实际上是创建了一个代理对象,但具体细节是什么?下面来揭示:

假设有一个UserService类,其中进行了AOP,那么Spring在底层创建出代理对象:

class UserServiceProxy extends UserService{
    UserService target;
    public void test(){
        //执行@Before切面逻辑
        target.test();
    }
}

代理对象是通过给target赋值为普通对象,在利用target来调用原本对象的方法,但有人说,为什么不直接调用super.test()而是要用一个target呢?我们来设想以下,如果代码变成这样的:

class UserServiceProxy extends UserService{
    public void test(){
        //执行@Before切面逻辑
        super.test();
    }
}

class UserService{
    @Autowired
    public OrderService orderService;
    
    @Before
    public void test(){
        sout(orderService);
    }
}

在UserService中的test方法,我们调用了orderService去做一些逻辑(这里本来应该是合理的,因为在Bean的创建生命周期中,已经经过了依赖注入,所以此时的orderService应该是有值的),但是我们在代理类中调用super.test()时,使用的是哪个orderService?根据多态的知识,我们知道,此处使用的是代理对象的orderService,而代理对象是临时被创建出来的,并没有经过依赖注入等环节,所以其orderService是没有值的,这样就出现了问题。而如果使用上面的target解决方案,就不会出现这种问题。

另外,存在AOP的类,最后进入单例池的是其代理对象。


Echo
2 声望1 粉丝