手写min-Spring框架——理解bean的生命周期

Spring的核心实际上就是Spring的IOC容器

创建这个IOC容器最重要的就是扫描路径,这里通过一个配置类作为创建容器的参数,将要扫描的路径传入进去

首先先是min-Spring中的几个注解:

//ComponentScan:指定这个注解只能加在类上,以及注解的作用范围
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {

    //这个属性是来指定扫描路径的
    String value() default "";
}

//Component:这个注解标识当前这个类要注册一个bean
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {

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

//Scope:这个注解标识当前这个bean对象的作用范围
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {

    //给bean的作用域
    String value() default "singleton";
}

//AutoWired:就是起到一个标识的作用
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AutoWired {
}

Spring容器概览:

public class FrogApplicationContext {

    private Class configClass;

    
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    //单例池(只有单例的bean对象放到这个map中,多例的话不放到这个map中)
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
    private ArrayList<BeanPostProcessor> beanPostProcessorsList = new ArrayList<>();

    /*
    spring启动的时候的构造函数
        1.扫描包路径,得到文件下所有要注册成bean的类分别创建beanDefinition(包含bean的名称和作用域)
        2.实例化单例的bean
     */
    public FrogApplicationContext(Class configClass)

    /*
    创建bean对象
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition)


    /*
    spring容器获得bean的方法,传入bean的名称
        1.通过beanName去找beanDefinition
        2.通过beanDefinition中的范围参数分别创建bean对象
            单例:单例池中有则直接get,没有就创建一个bean对象放到单例池中
            多例:创建一个bean对象
     */
    public Object getBean(String beanName)
}

 容器构造方法:

//容器的构造方法,通过一个配置类来进行构造
public FrogApplicationContext(Class configClass){
        this.configClass = configClass;

        //扫描-->得到很多BeanDefinition对象存到map中

        //首先判断这个配置类上有没有扫描包的这个conponentScan这个注解,然后获得这个注解
        if(configClass.isAnnotationPresent(ComponentScan.class)){
            ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            //从这个注解中拿到扫描路径
            //但是拿到的扫描路径实际上是一个包名,扫描的其实不是这个包名下的内容,而是这个包下的类对应的编译后的class文件
            //这里的path是com.frog.service
            String path = componentScan.value();
            //怎么获得对应包下的类编译后的class文件呢?
            //首先将报名转换成路径(是个相对路径)
            path = path.replace(".","/");//com/frog/ervice

            //classpath+path实际上就是我们想要的路径,其中classpath是类路径,path是上面获得的相对路径
            //通过类加载器获得calsspath
            //classLoader.getResource(path)尝试从类路径(classpath)中找到指定路径的资源,并返回一个URL对象。
            ClassLoader classLoader = FrogApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);

            //file既可以表示一个文件,也可以表示一个目录
            File file = new File(resource.getFile());

            //System.out.println(file);//打印看一下文件路径先

            //是不是一个文件夹,因为去扫描这个路径下的所有类看看加没加component注解
            if(file.isDirectory()){
                File[] files = file.listFiles();//拿到这个文件路径下的所有文件

                for(File f : files){
                    String fileName = f.getAbsolutePath();//这里获得文件的绝对路径,绝对路径中就包含文件名
                    //System.out.println(fileName);
                    //我们只需要class文件
                    if(fileName.endsWith(".class")){
                        //判断这个类有没有加component注解首先要获得这个类,通过反射获得这个类有没有这个注解
                        //通过类加载器加载类
                        //类加载器传入的是全限定包名
                        String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                        className = className.replace("\\",".");

                        //System.out.println(className);

                        try {
                            Class<?> clazz = classLoader.loadClass(className);
                            //判断这个类有没有这个注解
                            if(clazz.isAnnotationPresent(Component.class)){

                                //用来判断这个类是不是实现了这个接口,因为这里是通过class来判断是不是实现了某个接口,不能用instanceof方法,这个方法是判断这个对象的有没有实现某个接口,class对象的话不行
                                if(BeanPostProcessor.class.isAssignableFrom(clazz)){
                                    BeanPostProcessor instance = (BeanPostProcessor)clazz.newInstance();
                                    beanPostProcessorsList.add(instance);
                                }

                                //这里不是直接判断是否为bean然后生成bean对象
                                //因为bean有单例和多例bean,如果是多例bean的话会在获取的时候新建一个bean
                                //获取是通过容器的getBean方法进行获取的,而且传入的参数只有bean的名字,不可能在getBean方法中再去通过bean的名字再去获得bean的类再去判断单例多例再去创建bean就过于麻烦
                                //所以这里判断是否为bean之后生成一个BeanDefinition
                                BeanDefinition beanDefinition = new BeanDefinition();
                                beanDefinition.setType(clazz);
                                if(clazz.isAnnotationPresent(Scope.class)){
                                    Scope scope = clazz.getAnnotation(Scope.class);
                                    beanDefinition.setScope(scope.value());
                                }
                                else{
                                    beanDefinition.setScope("singleton");
                                }
                                //通过component获得bean的名字
                                Component component = clazz.getAnnotation(Component.class);
                                String beanName = component.value();
                                if(beanName.equals("")){
                                    //通过类的名自动生成bean的名字
                                    beanName = Introspector.decapitalize(clazz.getSimpleName());
                                }
                                beanDefinitionMap.put(beanName, beanDefinition);
                            }
                        } catch (ClassNotFoundException e) {
                            throw new RuntimeException(e);
                        } catch (InstantiationException e) {
                            throw new RuntimeException(e);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }

                    }
                }


            }
        }


        //实例化单例bean
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

            if(beanDefinition.getScope().equals("singleton")){
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
    }

注意:这里在扫描包的时候,不是扫描的创建类的那个包,而是扫描的类对应的字节码文件的那个包

可以通过当前这个容器的类加载器来获得类路径,然后将类路径拼接上相对路径(通过包名转换成相对路径得到的)得到要扫描的文件目录

所以容器的构造函数大概可以分为以下几步:

1.得到扫描目录的路径

2.得到这个目录下的所有文件,但是只取.class结尾的文件

3.根据这个.class结尾文件获得对应的类(使用反射根据全限定类名获得类的class对象)

4.对每个类都生成一个beanDefiniton放到beanDefinitonMap中(为什么不是直接生成bean对象,因为bean有单例有多例,多例会在每次getBean的时候获得,只有单例可以在创建容器的时候创建,这个beanDefiniton中起码包含这个类的class对象以及作用域,方便后面创建)

5.再扫描一遍beanDefinitonMap,将单例的通过createBean方法创建出来

getBean方法:

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

        if(beanDefinition == null)
            throw new NullPointerException();
        else{
            String scope = beanDefinition.getScope();
            if(scope.equals("singleton")){
                //单例(TODO:这里应该还需要改进一下,根据单例的那个方法,两次判断,否则可能会有线程安全问题)
                Object bean = singletonObjects.get(beanName);
                if(bean == null){
                    Object bean1 = createBean(beanName, beanDefinition);
                    //放入到单例池的时候才认为这个对象已经是一个bean对象
                    singletonObjects.put(beanName, bean1);
                }
                return bean;
            }
            else{
                //多例
                return createBean(beanName, beanDefinition);
            }
        }

    }

createBean方法

private Object createBean(String beanName, BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        try {
            //bean的生命周期第一步:实例化——>实际上就是创建了一个普通的对象
            //如果只有一个构造方法的话,就执行这唯一的一个构造方法
            //如果有多个构造方法的话,Spring会去多个构造方法中找无参的构造方法,如果没有这个无参构造方法的话,就会报错
            //除非程序员显式地告诉去执行哪个构造方法,也就是在构造方法上加Autowired注解
            //如果构造函数有传参的话,spring会先去单例池中找有没有这个bean(单例bean才去单例池找,多例bean的话直接创建),有的话传进去,没有的话就会创建(前提是这个参数得是个bean,不然会报错)
            //入参:先byType再byName
            Object instance = clazz.getConstructor().newInstance();

            //bean的生命周期第2步:依赖注入(给这个类中所有的属性上加了Autowired注解的属性进行赋值)
            //先byType再byName
            for(Field f : clazz.getDeclaredFields() ){
                if(f.isAnnotationPresent(AutoWired.class)){
                    f.setAccessible(true);//修改反射时的访问权限,可以访问私有属性
                    f.set(instance, getBean(f.getName()));//把这个属性名字当作bean的名字去容器中找(实际上就是byName方式进行依赖注入)
                }
            }

            //bean生命周期第三步:一些初始化步骤
            //3.1一些回调方法,目的是为了让程序员可以在获得bean的时候知道一些关于容器的信息
            if(instance instanceof BeanNameAware){
                ((BeanNameAware)instance).setBeanName(beanName);
            }
            //实际上是对bean提供了一些自定义扩展
            //初始化前执行
            for(BeanPostProcessor beanPostProcessor : beanPostProcessorsList){
                instance = beanPostProcessor.postProcessBeforeInitialization(beanName, instance);
            }
            //3.2初始化
            if(instance instanceof InitializingBean){
                ((InitializingBean)instance).afterPropertiesSet();
            }
            //回调和初始化的区别:回调是容器告诉这个bean一些信息;初始化是调用bean的一些方法,不关心这些方法内的一些内容
            //初始化后执行(AOP)对象——>代理对象(代理对象和对象的类型实际上是不一样的)
            for(BeanPostProcessor beanPostProcessor : beanPostProcessorsList){
                instance = beanPostProcessor.postProcessAfterInitialization(beanName, instance);
            }

            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
bean的创建生命周期:

1.实例化(创建一个普通对象):将beanDefinition中的class对象取出来,通过反射执行构造方法

(Spring:如果只有一个构造方法就执行唯一的那个构造方法;如果有多个的话,Spring会在这多个构造方法中找无参构造方法,如果没找到就报错;除非在构造方法上加Autowired注解,spring就会去执行这个加了注解的构造方法)

2.依赖注入:通过反射得到这个类所有的属性,遍历每个属性,加了Autowired注解的,通过set方法进行注入(也是去容器中找)

3.回调方法:依次判断有没有实现一些Aware接口,这个回调实际上是让程序员知道一些关于这个容器的信息

自定义一个BeanPostProcessor实现类实现BeanPostProcessor接口,重写接口里的两个方法,实际上也是bean对象,在扫描容器的时候会将判断这个bean有没有实现BeanPostProcessor接口,实现的话会加入到一个List中,然后在初始化前后遍历这个list中所有的实现类的这两个方法

可以看作是对创建bean提供了一些自定义扩展的部分

4.初始化前:BeanPostProcessor接口的postProcessBeforeInitialization

5.初始化:如果这个类实现了InitializingBean接口,就调用这个接口中的方法

6.初始化后:BeanPostProcessor接口的postProcessAfterInitialization

在初始化后这个阶段实现AOP,也就是在这个阶段,判断符合类型的bean,为其创建一个代理对象,这个代理对象中持有这个前面创建的普通对象,然后将这个代理对象返回覆盖掉原来的普通对象(原来的普通对象没有没,就是在代理对象中持有了)

7.将代理对象放到Map(单例池)中,这个时候一个bean对象就生成好了!

Note:代理对象一般是一个接口的实现类或者原有对象的子类,代理对象没有实现依赖注入!所以虽然加了Autowired但是从map中获得的代理对象的属性还是空的,但是代理对象会持有一个普通对象,这个普通对象经过了依赖注入,所以这个普通对象的属性是赋值过的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值