Spring底层之bean底层原理,spring容器底层原理

语重心长的话

因为是刚刚开始,容我多说两句,那么久只说这两句,首先第一句不是特别重要,重要的是第二句,第二句的重点和第一句一样.....巴拉巴拉,不鬼扯了!

一开始我也想着自己看源码,但是当我打开一个自己写好的spring项目的时候却无从下手,总而言之,言而总之,就是不知道从哪里下手。所以我选择去和老师学。嗯,那我们开始吧!

从这里开始

了解java的同学学起来很快啊,主要还是老师讲的好

在使用spring bean是我们需要去  ClassPathXmlApplicationContext()  中获取到 ApplicationContext  对象 这个对象就是我们所谓的 大容器,你可以这么理解,它里面主要包含了两个容器和 一个 Class 属性的对象(初学,只涉及到这几个属性,源码里面应该还有很多配置,这里我们后续应该会了解到)。那我们就详细讲解一下其中的厉害关系。

1、创建一个普通的对象(UserService)和一个测试类(Test)(下面我用  PanXiaoheiApplicationContext 代替   ApplicationContext  

1.1、UserService

UserService 对象中我需要将他放到spring容器中,因此需要加一个 @Component("userService") 注解。因为我们还要判断这个类似单例还是多例,因此需要添加一个@Scope("prototype")注解。

import com.panxiaohei.spring.Component;

@Component("userService")
@Scope("prototype")
public class UserService {//测试注入ben
}

 Component  注解类中,我们只需要实现几个简单的约束。

@Retention(RetentionPolicy.RUNTIME)//生效时间
@Target(ElementType.TYPE)//只能写在类上面
public @interface Component {

    String value() default "";//给定当前ben取一个名字
}

 Scope 注解类

@Retention(RetentionPolicy.RUNTIME)//生效时间
@Target(ElementType.TYPE)//只能写在类上面
public @interface Scope{

    String value() default "";//单例或多例
}

​​​​​​​

我的理解:注解就像一个笔记本,用于约束和附带信息。

 1.2、Test 

Test  类中使用main方法来创建我们的 PanXiaoheiApplicationContext  

import com.panxiaohei.spring.PanXiaoheiApplicationContext;
public class Test {
    public static void main(String[] args) {

        PanXiaoheiApplicationContext applicationContext = new PanXiaoheiApplicationContext(AppConfig.class);

        UserService userService = (UserService) applicationContext.getBen("userService");

    }
}

 AppConfig 为我们的配置注解类:

@ComponentScan("com.panxiaohei.service")
public class AppConfig {
}

 由此可见我们的配置类上有一个指定扫描路径,即我们需要将那个包下面的类放入spring容器中。

接下来我们来实现这个注解:

ConponentScan 注解类

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)//生效时间
@Target(ElementType.TYPE)//只能写在类上面
public @interface ComponentScan {

    String value() default "";//指定扫描路径

}

这里我们什么配置都没写,就加了两个注解,分别表示生效时间和使用这个注解的位置。

2 、我们自己创建一个 ApplicationContext  对象:

解:由上面Tset类可得,我们的 PanXiaoheiApplicationContext 对象中需要一个有参构造器和一个 getBean 的方法,下面的两个容器,我们后续会用到。


public class PanXiaoheiApplicationContext {

    private Class configClass;
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, Object> singletonObject =new ConcurrentHashMap<>();

    public PanXiaoheiApplicationContext(Class configClass) {
        this.configClass = configClass;
    }

    /**
     * 根据名字找到类,判断是单例还是多例
     * @param  beanName
     */
    public Object getBen(String beanName) {
        
    }
}

我们需要在这个类中大展身手了!

3、bean的实现(存储)

3.1 扫描需要放入容器的类

思路:

  1. 传入的 configClass 类中有我们需要加入spring容器包的地址
  2. 通过这个地址,获取到该地址下的所有文件
  3. 遍历这些文件,并筛选出.class文件
  4. 通过这些.class文件的地址获取到他的类(控制反转
  5. 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
  6. 创建一个bean的定义类(beanDefinition),用于包容放入容器的对象和它的一些配置(如:是否是单例,是否为懒加载),将对象和它的配置放入该类中。
  7. 创建一个总的bean容器,以对象名(或者Component注解中的value属性)作为key,存储beanDefinition。

实现:

// 扫描
        if (configClass.isAnnotationPresent(ComponentScan.class)) {  //判断configClass类有没有ComponentScan注解

            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);

            String path = componentScanAnnotation.value();//扫描路径 com.panxiaohei.service

            path = path.replace(".","/"); //转化为目录格式 com/panxiaohei/service

            ClassLoader classLoader = PanXiaoheiApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);//获取绝对路径

            File file = new File(resource.getFile());

            System.out.println("扫描的绝对路径:"+file);
            if (file.isDirectory()) { //是否为文件夹
                File[] files = file.listFiles();
                for (File f:files) {
                    String fileName = f.getAbsolutePath();//文件的绝对路径
//                    System.out.println("目录下的文件路径:"+fileName);

                    if (fileName.endsWith(".class")) { //判断字符串末尾

                        //获取到 com\panxiaohei\service\UserService
                        String calssName = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                        //转化成 com.panxiaohei.service.UserService
                        calssName = calssName.replace("\\", ".");

//                        System.out.println("===>         "+calssName);

                        /**
                         * 判断类上是否有Conpoent注解
                         */
                        try {
                            Class<?> aClass = classLoader.loadClass(calssName);

                            if (aClass.isAnnotationPresent(Component.class)) {

                                System.out.println("===>         "+calssName);

                                //获取该类上的 Compoenet 对象
                                Component component = aClass.getAnnotation(Component.class);
                                String beanName=component.value();

                                if (beanName.equals("")){
                                    //生成名字 默认首字母小写
                                    beanName = Introspector.decapitalize(aClass.getSimpleName());
                                }


                                //生成 BeanDefinition 对象
                                BeanDefinition beanDefinition = new BeanDefinition();
                                beanDefinition.setType(aClass);//bean的类型
                                //判断是单例还是多例
                                if (aClass.isAnnotationPresent(Scope.class)) {
                                    Scope scopeAnnotaicon = aClass.getAnnotation(Scope.class);
                                    beanDefinition.setScope(scopeAnnotaicon.value());
                                } else {
                                    beanDefinition.setScope("singleton");
                                }


                                //存储
                                beanDefinitionMap.put(beanName,beanDefinition);


                            }
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

以上涉及到的 BeanDefinition 对象:

package com.panxiaohei.spring;

/**
 * bean的定义
 */
public class BeanDefinition {

    private Class type; //类型
    private String scope; //单例,多例
    //懒加载,非懒加载

    public Class getType() {
        return type;
    }

    public void setType(Class type) {
        this.type = type;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }
}

createBean 方法

    /**
     * 创建一个 bean (反转控制法)
     *
     * @param beanName
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();

        //通过类的构造器 生成一个类的对象
        try {
            Object instance = clazz.getConstructor().newInstance();

            return instance;

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

至此,我们完成了对象提交到bean容器中管理,但是涉及到单例bean时,我们应该在创建容器的时候就应该去new出来,因为单例bean只会创建一次,因此在完成bean的存储之后我们接着需要单独再存储一次单例bean

3.2 存储单例bean

继:实现扫描之后.

思路:

  1. 遍历bean容器中所有的 BeanDefinition 筛选出Scope为 “singleton ”的对象
  2. 将筛选出来的对象,以对象名(或者Component注解中的value属性)作为key,放入 bean单例容器中

实现:

        //实例化单例bean
        for (String beanName : beanDefinitionMap.keySet()) {

            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

            if (beanDefinition.getScope().equals("singleton")) {// 判断是否为单例

                Object bean = createBean(beanName,beanDefinition);
                singletonObject.put(beanName,bean);
            }
        }

至此,我们完成了将对象交给spring容器托管。

4、获取spring容器中的bean对象

还记得上面我们写Test对象中,PanXiaoheiApplicationContext 对象的getbean方法吗?

那么它里面是这么实现的呢?

思路:

  1. 通过对象名(或者Component注解中的value属性值)获取到 PanXiaoheiApplicationContext 对象中的总的bean容器中的beanDefinition
  2. 判断该definition中的scope是否为单例
  3. 单例从bean单例容器中获取bean,并返回bean中存储的对象。
  4. 多例,返回一个new的bean对象。

实现:

    public Object getBen(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        if (beanDefinition == null) {
            throw new NullPointerException();
        }else {
            String scope = beanDefinition.getScope();
            if (scope.equals("singleton")) {//单例
                Object bean = singletonObject.get(beanName);
                if (bean == null){
                    Object o = createBean(beanName, beanDefinition);
                    singletonObject.put(beanName,o);
                }
                return bean;
            }else {//多例
                return createBean(beanName,beanDefinition);
            }
        }
    }

总结

学到这里我才发现,原理bean中并没有我想象的那么复杂,也可能是看源码摸不着头,因为它太深沉,父类子类继承太多,太繁琐。因此跟着前辈的步伐走真的可以少走很多弯路,也给我们这些穷苦家庭的孩子有了一个很好的学习机会。可能获取学习会越来越深入,涉及到锁的时候就会难一点,但是也要充分做好准备。也许是为了找到一份好工作去学,也去是因为想要开创思维,了解前人的强大,从而壮大自己我去学。不管是哪一种,都是好的。学无止境都是好的。

最后,祝和我一样刚开始接触spring底层的码子们都能顺顺利利,找到自己的一条路!后续我也会持续更新自己的总结,也希望能帮到大家!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值