• springboot初始化


    一、 SpringBean

    1. Spring Bean

    1) Bean定义

    Bean是什么,Bean是特殊的对象,交由Spring管理的Java对象,这类对象在创建的时候会根据spring的一些注解,和IOC,属性如果使用@Autowired的话,会自动赋值。Bean和对象的区别是:bean是spring拖管的,里面的属性也是有值的,对象的话直接new 里面的属性是空的,bean是特殊的对象,spring会对单例Bean进行缓存。

    

    2) Bean生命周期

    Spring在创建Bean的时候,首先根据ComponetScan注解来扫描具体哪个包下的所有类,如果类上带有Controller,Service,Response,Componet等注解就会创建对应的Bean对象,并且如果是单例的就会缓存到Bean对象池中,如果是多例的话,不缓存,使用的时候直接创建。BeanDefinition信息包括:

    

    

    Spring创建Bean的过程:获取类的class对象,通过BeanFactory获取类的元信息(有哪些注解,属于哪个class,依赖哪个Bean等),为每个Bean的元信息都创建BeanDefinition一个对象,并且缓存到Map中(BeanFactory就是用来创建BeanDefinition对象的),在此过程中,如果需要对创建的BeanDefinition加工处理,则可以实现Spring提供的接口BeanFactoryPostProcessor,实现类交给Spring拖管,然后可以在这里面添加一些加工处理的代码,用于创建BeanDefinition对象前后执行的方法。

    创建好BeanDefinition,遍历Map实例化Bean,注意,如果是多例的对象不需要实例化,填充属性,此时需要为其他Bean创建对象,创建对象使用class方式,利用反射实例化对象,实例化过程中,如果类实现Aware接口(一共9个,每个Aware接口对应不同的功能,如:获取Bean名称,获取BeanFactory,初始化执行的回调函数等等),在不同的时期执行该接口相关的回调函数,此时使用了模板模式,然后初始化对象,在这个过程,如果创建了BeanPostProcessor(post过滤器),则执行这里面的方法,这里面的方法是创建Bean的前置执行的方法,类似于切面,对所有Bean都可以在这里拦截进行加工处理。

    如果该类使用了AOP,需要使用代理创建该类的动态代理的子类,如果没有使用AOP,则直接把创建好的对象放到单例池中(Map集合,key是bean的名称,value是创建的Bean)。

    先实例化,再初始化,如果不开启AOP就是普通的对象,如果是开启了,那就是生成的代理对象。

    3) 特殊接口

    1. 通过注解方式扫描Bean

    启动的时候,可以通过XML方式和注解方式扫描Bean,如果是注解的方式,扫描Bean的时候需要通过@Componet(“路径”)指定一下扫描路径。

    public class StartSpring { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); } }
    @Configuration @ComponentScan("com.test") public class AppConfig { }

    2. 准备BeanDefinition集合

    Bean在创建的时候,spring会根据类上是否有Componet等注解,然后生成beandefinition对象,生成好后放到map中,此时还没有生成正真的对象呢,当把BeanDefinition都准备好后放到Map中,遍历Map获取每个BeanDefinition,调用getBean(“bean名称”)才创建Bean,如果是单例就缓存起来,如果是多例就直接创建不缓存。

    BeanDefinition的作用就是获取Class信息,获取类模板信息就可以修改类的信息,所以修改类可以在这个过程做操作。

    

    3. Spring创建代理对象

    场景:Spring整合Mybatis的时候,需要为Mapper生成代理对象,在BeanFactory创建BeanDefinition的时候,有一些属性依赖Mapper接口,这时候就需要为Mapper创建代理对象,并且缓存到单例池中,这时候使用这种方式创建代理对象即可。

    import org.springframework.beans.factory.FactoryBean; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyFactoryBean implements FactoryBean { @Override public Object getObject() throws Exception { // 创建Object代理对象 需要创建什么代理对象就创建什么代理对象 Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{Object.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return proxy; } }); return null; } @Override public Class getObjectType() { return null; } }

    4. 通过各种Aware接口设置想要的值

    Aware一共有9个,每种都有不同的用途。以下是两种例子:

    import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService implements BeanFactoryAware, BeanNameAware { @Autowired private User user; private BeanFactory beanFactory; private String beanName; /** * 获取当前Bean创建的BeanFactory对象 * @param beanFactory * @throws BeansException */ @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } /** * 获取当前Bean在单例池中的名称 * @param beanName */ @Override public void setBeanName(String beanName) { this.beanName = beanName; } }

    5. 初始化完需要执行的方法可使用接口

    通过InitializingBean可以对Bean初始化完成后需要执行的方法,继承该方法,可以在初始化完成后执行该操作。

    import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService2 implements InitializingBean { @Autowired private User user; @Override public void afterPropertiesSet() throws Exception { // TODO 初始化完成后执行的操作 } }

    2. 循环依赖

    1) 循环依赖过程

    构造方法的循环依赖是无法解决的,因为对象在创建的时候要调用构造函数,调用的时候还要去创建依赖的对象,依赖的对象反过来依赖自己,还没有实例化对象,无法提前暴露对象。

    

    Spring创建循环依赖的对象,并为其赋属性值的过程是:

    如A依赖B,B依赖A,A在实例化后,在为属性B赋值的时候,B也会像A一样实例化,然后进行属性赋值,当为A赋值的时候回去缓存中查找,如果A没有进行AOP操作的话,A在实例化后就会把它的原始对象(原始对象是属性还没有填充值的对象)放到单例池中,二级缓存中,提前暴露给别的Bean引用,只是此时的A还不完整,就放到二级缓存中。B从一级缓存中获取没有找到A,就从二级缓存中查找,发现找到了提前暴露的A,然后赋值引用,完成属性的赋值,然后返回A,A继续操作后续过程。

    但是如果A发生了AOP的话,就不能把原始对象直接放到二级缓存中了,因为AOP最终要生成代理对象,代理对象和原始对象不是一个的话,为B赋值的是原始对象的引用,为A赋值的是代理对象(放在一级缓存单例池中的对象),这样就违背了单例模式了。

    此时A在实例化后把它自己放到三级缓存中,把实例对象传进去,生成一个lambad表达式(() -> getEarlyBeanReference(beanName,mbd,bean原始对象)),不直接生成代理对象,后面谁用到谁再生成,然后赋值B属性的时候,创建B的Bean对象,B引用A的时候,会从一级缓存获取发现没有,从二级缓存获取也发现没有,从三级缓存获取,拿到了lambad表达式,然后执行获取A的代理对象,此时A的代理对象其实是提前生成了,B帮忙生成的,生成后会把A的代理对象放到二级缓存中,然后引用指向了B中的Bean的A属性,然后返回A。

    

    创建代理对象具体代码:

    

    A填充B的属性,执行Aware,init操作后(此时还是针对的是A的原始对象,不是代理对象),执行完后当要开始生成代理对象的时候,还是会从一级缓存获取一下,发现没有,然后从二级缓存获取,发现二级缓存中存在A的代理对象(spring中代理对象里面有一个引用,指向了原始对象),然后获取后直接把该对象放到一级缓存中,完成了初始化过程。

    这里有一个细节,为了保证前后对象的唯一性,通过名称找到的对象是不是自己的对象,在B中提前生成的A的代理对象后,会把对象放到earlyProxyReferences map中,当A在执行完第五步属性赋值完后开始生成代理对象前,要到这个map中通过beanName名称获取到对应的对象,然后判断是自己的代理对象,就不生成代理对象了,然后再从一级缓存,二级缓存中查找,如果不是自己的对象,则调用wrapIfNecessary方法进行生成代理对象。

    

    2) 三级缓存

    一级缓存是最终存放Bean对象的Map集合,二三级缓存是为解决循环依赖而创建的,在循环依赖过程中,一个对象提前暴露的话会存在二级缓存中,如果这个对象在初始化的时候,进行了AOP操作,就需要生成代理对象,会先在三级缓存中存放一个要生成代理对象的lambad表达式,后面谁依赖的了就执行lambad表达式,生成代理对象,但是由于不是自己生成的,生成完后的代理对象需要放到二级缓存中。

    二级缓存是为了提前暴露对象而产生的,三级缓存是为了AOP对象需要生成代理对象产生的。如果A没有循环依赖,但是进行了AOP操作,也是会生成一个lambad表达式放到三级缓存中,但是没有其他Bean依赖A,所以三级缓存中的lambad表达式始终都不会执行到,执行不到就不会放到二级缓存中,当A执行到最后的时候,从二级缓存中获取代理对象是获取不到的,所以自己生成。

    三级缓存包括:

    Ø singletonObjects:一级缓存,用于存放最终的Bean对象,一级缓存对于循环依赖没有什么帮助;

    Ø earlySingletonObjects:二级缓存,存放不完整的对象,创建提前暴露的对象放在里面,提前暴露的对象可能还没有进行完全的初始化,属性还没有赋值完成或者是完全;

    Ø singletonFactories:三级缓存,用于存放对原始对象的包装的一个lambad表达式,里面存储的是() -> getEarlyBeanReference(beanName,mbd,bean原始对象),还没有进行创建代理对象,只是存一个表达式,value是ObjectFactory;

    Ø earlyProxyreferences:其实还包括一个缓存,他用来记录某个原始队形是否进行了AOP了。

    3) 为什么需要三级缓存

    在构建原始对象的时候,不知道是否会发生循环依赖,如A依赖B,B没有依赖A,二级缓存存的是对象,三级缓存存的是ObjectFactory,如果只利用二级缓存省去三级缓存,那么二级缓存应该存什么,一个Bean在初始化的时候,在执行到BeanPostProcessor的时候才知道自己要不要生成代理对象,存原始对象还是存代理对象,如果没有发生AOP,则需要存原始对象,如果发生了AOP则需要存储代理对象,这样就矛盾了,所以放到三级缓存中一个表达式,在使用的时候再创建是最好的选择,如果没有AOP则可以省去二级缓存和三级缓存其实也是可以的。

    二、 SpringBoot

    1. Refresh方法

    Spring容器创建之后,会调用它的refresh方法,refresh的时候会做很多事情:比如完成配置类的解析、各种BeanFactoryPostProcessor和BeanPostProcessor的注册、国际化配置的初始化、web内置容器的构造等等。

    以web为例,对应的Spring容器为AnnotationConfigEmbeddedWebApplicationContext。它的refresh方法调用了父类AbstractApplicationContext的refresh方法:

    

    1) prepareRefresh方法

    表示在真正做refresh操作之前需要准备做的事情:

    l 设置Spring容器的启动时间,撤销关闭状态,开启活跃状态;

    l 初始化属性源信息(Property);

    l 验证环境信息里一些必须存在的属性。

    2) prepareBeanFactory方法

    从Spring容器获取BeanFactory(Spring Bean容器)并进行相关的设置为后续的使用做准备:

    l 设置classloader(用于加载bean),设置表达式解析器(解析bean定义中的一些表达式),添加属性编辑注册器(注册属性编辑器);

    l 添加ApplicationContextAwareProcessor这个BeanPostProcessor。取消ResourceLoaderAware、ApplicationEventPublisherAware、MessageSourceAware、ApplicationContextAware、EnvironmentAware这5个接口的自动注入。因为ApplicationContextAwareProcessor把这5个接口的实现工作做了;

    l 设置特殊的类型对应的bean。BeanFactory对应刚刚获取的BeanFactory;ResourceLoader、ApplicationEventPublisher、ApplicationContext这3个接口对应的bean都设置为当前的Spring容器;

    l 注入一些其它信息的bean,比如environment、systemProperties等。

    注意:https://blog.csdn.net/z69183787/article/details/104364846

    2. Springboot如何加载一个starter依赖组件并完成初始化

    引入maven starter依赖,基本就满足了日常的web接口开发,而不使用springboot时,需要引入spring-web、spring-webmvc、spring-aop等等来支持项目开发。

    实际上那些必要的依赖在spring-boot-starter-web中已经被引入了,说白了,starter可以当成是一个maven依赖组,引入这个组名就引入了所有的依赖。

    对于其他一些starter,比如要使用redis、jpa等等,就不仅仅是引入依赖了,还需要实现一些初始的配置,比如常使用@Configuration,这个注解就会在springboot启动时去实例化被其修饰的类,前提是springboot配置了@EnableAutoConfiguration,而springboot启动类默认的@SpringBootApplication中默认包含了该注解,所以不用再显示引入,最后需要在starter项目中META-INF/spring.factories中添加:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.xxx.Xxx

    这样在springboot启动时,才能正确加载自定义starter的配置。所以说,starter中简单来讲就是引入了一些相关依赖和一些初始化的配置。

    为什么加了@Configuration注解还是要配置META-INF/spring.factories呢?因为springboot项目默认只会扫描本项目下的带@Configuration注解的类,如果自定义starter,不在本工程中,是无法加载的,所以要配置META-INF/spring.factories配置文件。

    为什么配置了META-INF/spring.factories配置文件就可以加载?这里才是springboot实现starter的关键点,springboot的这种配置加载方式是一种类SPI(Service Provider Interface)的方式,SPI可以在META-INF/services配置接口扩展的实现类,springboot中原理类似,只是名称换成了spring.factories而已。

  • 相关阅读:
    如何在不依靠工资收入的情况下赚到一万元?
    centos docker jenkins 日志
    【Java快速入门】Java语言的抽象类和接口(十)
    FFmpeg转码参数说明及视频转码示例
    所有的注意力不专注都是目标不明确、不坚定的表现
    hadoop安装的过程中的报错​/libhadoop.so.1.0.0​
    GPS北斗校时器(NTP网络校时服务器)医院应用方案
    农村人口房屋管理系统(C#+Mysql)
    网络安全(黑客)自学
    经典同步问题
  • 原文地址:https://blog.csdn.net/fuqianming/article/details/134251703