2.Spring源码的起点:BeanDefinition

★为什么从BeanDefinition讲起?

spring源码太庞大了,精通spring源码确实不容易,讲懂更难。学习spring源码是件浩大的工程。很多读者从spring容器启动开始逐行debug源码进行分析,刚开始很容易理解,但是当你逐层debug源码深处时,就会感慨“身在此山中,不识真面目”。

笔者在刚开始的时候也经历过这种痛苦,精读几遍之后笔者觉着spring源码不能从头读起,我们需要先搞懂spring中的基础组件及组件之间的关系,这就好比组装电脑,你得先了解CPU作用、内存的作用、主板的作用、硬盘的作用,然后你才知道如何讲他们组装到一起,从头阅读源码就跟你让一个学金融专业的学生组装电脑一样,效果可想而知。

image.png

如果我问你spring的作用?想必大部分程序员都会回答“管理对象,方便开发”。

不错,java一切皆对象,spring封装管理你所创建的对象,封装后的对象叫bean,bean扩展了你的对象,你所创建的对象的所有属性和方法是bean中的一个子集。

spring所有的功能都是围绕这个bean展开的。这个bean在spring中具体的名字就是BeanDefinition。

★理解BeanDefinition的作用

假如你编写一个Person.java文件,编译成Persion.class文件后,jvm会将这个class文件加载到虚拟机内存(方法区),程序在当遇到new关键字的时候根据class在堆内生成对象。

class文件已经生成了,spring无法更改你的class,但还要扩展你的class怎么办?很简单,spring定义一个BeanDefinition类进一步封装Person类,Person只是BeanDefinition中的一个属性,最后注册到spring容器中。

可以把BeanDefinition理解为Java中的.java文件,在.java文件没有编译成class文件时,假如我们可以对.java的文件做任意的修改,那么就可以达到扩展的目的。

等我们扩展完成,编译成.class文件运行即可。

BeanDefinition也是如此,我们可以通过后置处理器修改BeanDefinition影响类的方法和属性。最后spring针对修改后或者说扩展后的BeanDefinition实例化,从而达到扩展的目的。

image.png

BeanDefinition中还会封装跟Person相关的其他属性,比如bean的作用域,bean的注入模型,bean是否是懒加载等等信息。

如果把Person.java和PersonBeaDefiniton对比来看:

image.png

同理,如果你不了解BeanDefinition你就读不懂Spring源码。beanDefintion的比较枯燥和晦涩难懂,但是非常非常重要。如果你想精读spring源码,请你一定细读三遍beanDefintion的知识,他是spring framework当中的基石,你避不开!

★加载BeanDefinition的流程

配置类

package tyrant;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
//包扫描
@ComponentScan("tyrant")
//使用xml文件启动
@ImportResource("applicationContext.xml")
public class Config {
}

xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
             >
          <!--注意上面的default-autowire="byType"  根据type进行自动注入-->
          <bean id="ab" abstract="true" ></bean>
          <bean id="xxx" class="tyrant.InterService" parent="ab"></bean>
      </beans>

xml方式启动属实用的不多了已经,xml最大的作用就是依赖注入(以后再讲,可忽略),笔者在后续讲解中基本以注解方式进行讲解。

业务类

    package tyrant;
    @Component
    @Description("业务类")
    @Scope("singleton")
    public class InterService {
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

测试类

    package tyrant;
    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;

    public class SpringTest {
        public static void main(String[] args) throws InterruptedException {
            start1();
        }
        
         public static void start1() {
            //其中Config.class可以指定为
            //包扫描启动:@ComponentScan("tyrant")
            //使用xml文件启动:@ImportResource("applicationContext.xml")
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
            //注册配置类
            context.register(Config.class);
            //加载或者刷新当前的配置信息
            context.refresh();
        }
        public static void start2() {
            //其中Config.class可以指定为
            //包扫描启动:@ComponentScan("tyrant")
            //使用xml文件启动:@ImportResource("applicationContext.xml") 
            AnnotationConfigApplicationContext context = new            
                                                    AnnotationConfigApplicationContext(Config.class);
            //加载或者刷新当前的配置信息
            context.refresh();
        }
    }

spring启动会完成很多工作,不可能在专题第一篇就把整个启动流程讲清楚,为了引出BeanDefinition。

笔者讲解三个过程:

  • 扫描 spring首先根据Config 的注解@ComponentScan("com")扫描com包下符合规则的类。这里只有InterService类;
  • 解析 解析InterService类,主要是拿到所有的注解:@Component、@Description("业务类")、@Scope("singleton")
  • 封装 解析完的信息保存到哪里里去呢? 就存在BeanDefinition中。BeanDefinition就是对你的InterService进行建模。

BeanDefinition的源码

其实,BeanDefinition只是一个接口:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
     //InterService的scope值,单例
    String SCOPE_SINGLETON = "singleton";
    //InterService的scope值,非单例
    String SCOPE_PROTOTYPE = "prototype";
    //Bean角色:
    //用户
    int ROLE_APPLICATION = 0;
    //某些复杂的配置
    int ROLE_SUPPORT = 1;
    //完全内部使用
    int ROLE_INFRASTRUCTURE = 2;
    //返回此InterService的的父bean定义的名称
    //如果有的话 <bean parent="">(xml配置文件中的bean配置)
    String getParentName();    
    void setParentName(String var1);
    //设置或获取业务类全路径名
    void setBeanClassName(String var1);
    String getBeanClassName();
    //InterService的scope值
    void setScope(String var1);
    String getScope();
   //InterService的懒加载设置及获取
    void setLazyInit(boolean var1);
    boolean isLazyInit();
    //InterService的依赖对象 ,表示InterService实例创建前应该先创建哪个对象
    void setDependsOn(String... var1);
    String[] getDependsOn();
    //是否为被自动装配
    void setAutowireCandidate(boolean var1);
    boolean isAutowireCandidate();
   //如果是被自动装配,是否为主候选bean
    void setPrimary(boolean var1);
    boolean isPrimary();
   //定义创建该Bean对象的工厂类 
    void setFactoryBeanName(String var1);
    String getFactoryBeanName();
   //定义创建该Bean对象的工厂方法 
    void setFactoryMethodName(String var1);
    String getFactoryMethodName();
   //返回此InterService的构造函数参数值。
    ConstructorArgumentValues getConstructorArgumentValues();
   //获取普通属性集合
    MutablePropertyValues getPropertyValues();
   //是否为单例
    boolean isSingleton();
    //是否为原型
    boolean isPrototype();
    //是否为抽象类
    boolean isAbstract();
    //获取这个bean的应用
    int getRole();
   //返回对bean定义的可读描述。
    String getDescription();
   //返回该bean定义来自的资源的描述(用于在出现错误时显示上下文)
    String getResourceDescription();
    BeanDefinition getOriginatingBeanDefinition();
}

通过BeanDefinition接口可以对InterService的附加属性进行设置和获取,虽然目前不能完全搞懂所有属性及方法,建议把这个接口代码都看一遍。

在这里建议大家读源码一定要“慢下来”,一点一点的啃,切忌浮躁,这个痛苦的过程过去了你会有种高屋建瓴的感觉! 光读不行,还得写代码调试从而增加印象,比如上面BeanDefinition接口中的方法我想验证一下,很简单,上代码:


package tyrant;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTest {
    public static void main(String[] args) throws InterruptedException {
    
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
    
        //加载或者刷新当前的配置信息
        context.refresh();;
        
        //获取InterService对应的BeanDefinition 
        //默认名称为interService,关于名字的更改以后讲。
        BeanDefinition interServiceBeanDefinition = context.getBeanDefinition("interService");

        System.out.println("——————InterService的附加属性如下:");
        System.out.println("父类"+interServiceBeanDefinition.getParentName());
        System.out.println("描述"+interServiceBeanDefinition.getDescription());
        System.out.println("InterService在spring的名       
                           称"+interServiceBeanDefinition.getBeanClassName());
        System.out.println("实例范围"+interServiceBeanDefinition.getScope());
        System.out.println("是否是懒加载"+interServiceBeanDefinition.isLazyInit());
        System.out.println("是否是抽象类"+interServiceBeanDefinition.isAbstract());
        System.out.println("——————等等等等,读者自行编写");
    }
}

image.png

注意,这些属性在xml配置文件中都能找到对应匹配的标签,读者可用xml配置文件自行进行配置,只需要将Config配置类上的@ImportResource("applicationContext.xml")注解打开即可。

既然BeanDefinition是个接口,那spring中肯定有他的实现类对不对,好,是时候看一下BeanDefinition的类继承图了。

在这里笔者跟大家说一个问题,笔者发现很多人读源码的时候拿来一个类就读或者debug源码跟读,也不管这个类跟其他类的关系,读完后感觉很混乱,甚至吐槽spring源码写的毫无章法,拜托,spring源码是典型的面向接口编程,严格遵循开闭原则、依赖倒置原则和迪米特法则,是spring源码写的差还是你的水平差?

spring每一个模块都有一个完整的类继承关系图,不然spring被业界称赞的高扩展性谈何而来?所以我们必须将每个模块的类继承关系了然于胸。

初学,我们也不可能将继承体系中的每个类都搞懂,把这个继承图下载下来存到桌面,在以后的源码阅读中这个继承关系会被你一一攻破,学完你也就掌握了,而且不会忘,更能提高你的编程水平,读完spring你会发现的编程风格潜移默化的被spring影响了!对吧。

BeanDefinition的继承关系

image.png

硬编码注入BeanDefinition

以前利用spring开发大都通过xml方式进行bean配置,其实spring框架开发之初并不想让程序员通过xml方式进行配置,而是通过代码让我们手动将InterService封装成对应的BeanDefinition,看下面的代码:

    //首先,删掉业务类InterService所有的注解,不让他被扫描到,spring根据注解判断是否将类进行包装。
    package tyrant;
    public class InterService {
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

 

    public class SpringTest {
        public static void main(String[] args) throws InterruptedException {
            AnnotationConfigApplicationContext context = new 
                                    AnnotationConfigApplicationContext();
            //注册配置类
            context.register(Config.class);
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClassName("com.InterService");
            beanDefinition.setScope("singleton");
            beanDefinition.setDescription("手动注入");
            beanDefinition.setAbstract(false);
            //将beanDefinition注册到spring容器中
            context.registerBeanDefinition("interService",beanDefinition);
            //加载或者刷新当前的配置信息
            context.refresh();
            
            BeanDefinition interServiceBeanDefinition = context.getBeanDefinition("interService");
            System.out.println("——————InterService的附加属性如下:");
            System.out.println("父类"+interServiceBeanDefinition.getParentName());
            System.out.println("描述"+interServiceBeanDefinition.getDescription());
            System.out.println("InterService在ioc的名称"+interServiceBeanDefinition.getBeanClassName());
            System.out.println("实例范围"+interServiceBeanDefinition.getScope());
            System.out.println("是否是懒加载"+interServiceBeanDefinition.isLazyInit());
            System.out.println("是否是抽象类"+interServiceBeanDefinition.isAbstract());
            System.out.println("——————等等等等,读者自行编写");
            
        }
    }

输出:

——————InterService的附加属性如下:
父类null
描述手动注入
InterService在ioc的名称tyrant.InterService
实例范围singleton
是否是懒加载false
是否是抽象类false
——————等等等等,读者自行编写

其实笔者很喜欢这种代码方式完成spring配置工作,这样能让我们更深入的了解和应用spring,不过这种方式的缺点也很明显-繁琐易出错,spring为了简化我们的工作提供了xml配置方式,直到spring5.x注解方式的稳定成熟,spring全家桶得到了飞速的发展。但通过这个例子读者可以加深对BeanDefinition的理解。

★IOC的引出

看上面这么一行代码:

 context.registerBeanDefinition("interService",beanDefinition);

这行代码的意思是将我们手动封装的beanDefinition注册到容器中,同时给这个beanDefinition起了个名字"interService",spring内部生成beanDefinitino时会默认起一个名字,改名字的规则就是业务类名字首字母小写。 那生成的BeanDefinition保存在哪里呢?既然我们是通过上面的方法将BeanDefinition注册到容器中,肯定是在这个方法底层实现了保存,我们点进去看。

     @Override
        public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
                throws BeanDefinitionStoreException {
            // 将beanDefinition保存到spring容器中
            this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
        }

继续跟进registerBeanDefinition方法,找到下面这行代码:

  this.beanDefinitionMap.put(beanName, beanDefinition);

顾名思义,beanDefinitionMap就是一个Map呀!

具体是啥map,我们ctrl+鼠标左键单击找到beanDefinitionMap定义处:

/* Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

总之,这就是beanDefinition存储的容器,这行代码所在的类名叫DefaultListableBeanFactory。

它是bean的工厂,spring中所有的对象或者说bean都存在这个bean工厂中,业界叫它IOC,很多书或者视频都会讲IOC,相信读者也知道IOC是容器,但它就是一堆Map集合而已,beanDefinitionMap只是众多Map中的一个而已,以后我会将其他的map容器,今天你只需要只到这么一个存放BeanDefinition的容器即可。

IOC是什么?

IOC的全称是Inversion of Control,翻译过来就是控制反转。控制反转的意思就是我们将对象的创建和管理权交给IoC Service Provider(IoC思想的具体实现)

控制反转IOC是一种设计思想,DI依赖注入是实现IoC的一种方法。

IOC是Spring框架的核心内容,它使用多种方式完美的实现了Ioc,可以使用XML配置,也可以使用注解,新版本的Spring也可以使用完全的Java类加注解实现零配置的IoC,但这种实现方式再SpringBoot中比较常见。

为什么要使用IOC

为了降低**耦合度。

个人理解:接口HumanService有多个实现类UserServiceImpl、PeopleServiceImpl、HumanServiceImpl。 一开始我们用的是UserServiceImpl,可以这么写

HumanService humanService = new UserServiceImpl();

当我们业务发生变化,需要使用PeopleServiceImpl,此时需要手动修改代码为:

HumanService humanService = new PeopleServiceImpl();

假如我们有100个地方都需要这个用到HumanService,那么我们需要硬编码修改100处代码。

假如有一天我们要改为HumanServiceImpl,又要硬编码修改100处代码。

痛,真的太痛了!

但是如果使用ioc,那我们可以这么写

    @Autowired
    private HumanService humanservice;

当我们需要UserServiceImpl,我们只需要注入UserServiceImpl。

    @Service
    public class  UserServiceImpl implements HumanService{
    }

此时ioc容器会帮我们自动注入HumanService对应的实现为UserServiceImpl。 当我们业务发生变化,需要使用PeopleServiceImpl。只需要把UserserviceImpl对应的实现类上面的@Service去掉加到PeopleServiceImpl上即可实现替换。相比较而言使用ioc依赖注入比硬编码new出来对象要优雅很多!

★DefaultListableBeanFactory创建时机

有读者问,那DefaultListableBeanFactory这个bean工厂啥时候创建的?看代码:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

这是我们创建spring上下文对象,AnnotationConfigApplicationContext 类有一个父类,AnnotationConfigApplicationContext的无参构造函数执行时会默认调用父类无参构造函数,AnnotationConfigApplicationContext的父类如下:

    public class AnnotationConfigApplicationContext 
                        extends GenericApplicationContext 
                                        implements AnnotationConfigRegistry 

也就是GenericApplicationContext ,我们看一下他的构造函数:

    public GenericApplicationContext() {
        //初始化一个BeanFactory
        this.beanFactory = new DefaultListableBeanFactory();
    }

也就是说,spring启动的时候就创建好了这个bean工厂!

咦?不是要讲BeanDefinitino的继承关系吗?怎么跑偏了?

这就是spring的特点,太庞大了,没有孤立的知识点!这也是很多读者阅读spring源码时读着读着就蒙圈的原因。

★后置处理器的引出

上文我们通过手动将InterService封装成了一个BeanDefinition然后注册(put到了beanDefinitionMap中)到了容器中,我说了现在我们没有这么用的了,都是spring自动帮我们完成扫描注册,在哪完成的扫描注册?回到下面这几行代码:

     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //注册配置类
    context.register(Config.class);
    //加载或者刷新当前的配置信息
    context.refresh();

context.refresh()方法,完成了spring的启动、扫描、解析、实例化等一系列过程,这个方法完成的功能太多了,我们的扫描注册也是在这里完成的,进入到这个方法,找到这么一行代码:

invokeBeanFactoryPostProcessors(beanFactory);

翻译一下名字,执行bean工厂的后置处理器,这行代码完成了扫描与注册,我不带大家分析里面的代码,你只需要知道他的作用就行,这行代码执行完成后,我们只是把业务类InterService封装成了BeanDefinition而已,业务类InterService并没有实例化,在业务类InterService实例化之前我们能不能从beanDefinition中将InterService偷梁换柱呢?

或者说,我们能通过BeanDefinition来构建bean,那我们能不能修改bean呢?那必须的! 通过后置处理器完成,什么是后置处理器?可以把它理解成回调,我扫描注册成功后回调后置处理器!BeanDefinition讲完后紧接着就讲后置处理器。我们添加一个后置处理器:

    /**
     * 扫描注册成功完成后
     * spring自动调用后置处理器MyBeanFactoryPostProcessor的postProcessBeanFactory方法
     */
    @Component
    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
                throws BeansException {
            //通过bean工厂拿到业务类InterService的beanDefinition
            GenericBeanDefinition beanDefinition =
                    (GenericBeanDefinition) beanFactory.getBeanDefinition("interService");
            System.out.println("扫描注册成功完成后,spring自动调用次方法");
            System.out.println(beanDefinition.getDescription());
        }
    }

spring扫描注册完成后,会自动调用MyBeanFactoryPostProcessor的postProcessBeanFactory方法,这个方法给你传递了一个ConfigurableListableBeanFactory类型的bean工厂。

ConfigurableListableBeanFactory是一个接口,上文spring启动实例化的DefaultListableBeanFactory工厂是它的实现类。天啊,竟然把bean工厂给你了,相当于敌人把军火库暴露在你面前,你岂不是想干嘛就干嘛!

上述代码我们通过bean工厂拿到了业务类InterService的beanDefinition,我都拿到你的beanDefinition了,那么我不但可以get到你的信息,我也可以set你的信息从而改变你的行为来影响你后续的实例化。我们来编写另一个业务类:

    package tyrant;
   
    public class User {
        private int age =31;
        private String name="user";
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

spring启动时也会把这个业务类扫描,接下来,看好了。

我在bean工厂中偷梁换柱,在beanDefinition中将InterService业务类替换掉:

        package tyrant;
        
        import org.springframework.beans.BeansException;
        import org.springframework.beans.factory.config.BeanDefinition;
        import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
        import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
        import org.springframework.beans.factory.support.GenericBeanDefinition;
        import org.springframework.stereotype.Component;
        
        /**
         * 扫描注册成功后,spring自动调用MyBeanFactoryPostProcessor的postProcessBeanFactory方法
         */
        @Component
        public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
            @Override
            public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
                    throws BeansException {
                BeanDefinition interService = beanFactory.getBeanDefinition("interService");
                
                
                /***
                getBeanDefinition方法返回的BeanDefinition类型为什么强转成GenericBeanDefinition?
                因为BeanDefinition接口中并没有setBeanClass这个方法
                GenericBeanDefinition是他的实现,提供更丰富的功能。
                不同的BeanDefinition实现具有不同的作用。
                ***/ 
                if(interService instanceof GenericBeanDefinition){
                    GenericBeanDefinition beanDefinition =
                            (GenericBeanDefinition) interService;
                    System.out.println("修改后的名字:" + beanDefinition.getBeanClassName());
                    //注意这里修改了class类型
                    beanDefinition.setBeanClass(User.class);
                }
            }
        }
     

        public class Test {
            public static void main(String[] args) {
                AnnotationConfigApplicationContext context 
                                    = new AnnotationConfigApplicationContext();
                //注册配置类
                context.register(Config.class);
                context.refresh();
                System.out.println("更改后的业务类:"+
                                 context.getBeanDefinition("interService").getBeanClassName());
            }
        }

打印结果:

修改的名字:tyrant.InterService
更改后的业务类:tyrant.User

既然我们已经偷梁换柱把InterService替换掉了,那么spring在后续实例化过程中就不会实例化InterService。

看下面代码:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new 
                                            AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.refresh();
        //更改后的业务类:tyrant.User
        System.out.println("更改后的业务类:"+
                            context.getBeanDefinition("interService").
                                                            getBeanClassName());
        //尝试获取名为interService的BeanDefinition会报错
        //因为在MyBeanFactoryPostProcessor中执行了以下代码     
        //beanDefinition.setBeanClass(User.class);
        context.getBean(InterService.class);
    }
}

打印结果报错,因为spring当中不存在名为interService的BeanDefinition。

总结

本篇文章主要讲述了BeanDefinition的作用以及加载的流程,引出了IOC容器,以及IOC容器的作用。最后引出后置处理器,通过后置处理器我们可以对BeanDefinition进行进一步的改造。

猜你喜欢

转载自juejin.im/post/7234887157000175674