终于不是单学API啦, 基于 [Java基础 反射 注解 动态代理] 自己实现简单的Spring容器(上) 功能 : (IOC , 依赖注入)

✨✨hello,愿意点进来的小伙伴们,你们好呐!
🐻🐻系列专栏:【JavaEE】
🐲🐲本篇内容:自己实现简单的Spring容器 ,实现功能 : IOC , BeanPostProcessor , AOP
🐯🐯作者简介:一名现大二的三非编程小白,日复一日,仍需努力。
😘😘gitee仓库地址 : gitee代码地址

在我们日常的学习中会发现有时候学习很多都是在学习API,停留于API的层面,这个是很不好的,因为我们学习要是都停留在表面的API上的话,那么只会使用,并不知道为何能这样子中,在使用的时候看到的永远是API,看不到底层的东西

因此在学习Spring的过程中,最好还是自己实现一个简易版本的Spring,不然看到的都是一堆配置和注解,这样子并不会进步

一图胜千言 : Spring剖析思路 :

在这里插入图片描述

这个实现思路的示意图先摆在这里,感兴趣的小伙伴可以看着我一步一步地实现Spring

扫描指定的包将Bean信息存入容器中

在这里插入图片描述

现在我们来实现这条线,读取bean.xml中配置的包路径,并扫描包,得到bean的class对象,然后将bean的信息封装到BeanDefinition中,并初始化bean池

  1. 我们先创建一个annotation 包,用来存放注解的.
    在这里插入图片描述

  2. 在该包下面创建一个 ComponentScan 注解,用来使用注解指定要扫描的包, 创建Componet注解来表示要注入的对象,Scope注解表示是否为单例,Autowired 注解来依赖注入

@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    //value指定要扫描的包
    String value() default "";
}
@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Scope {
    //这里要来填入是否单例
    String value() default "singleton";
}

  1. 然后创建一个ioc包,用来存放与IOC容器有关的类
    在这里插入图片描述

  2. 在ioc包下面创建一个LhjSpringConfig类,用来指定要扫描的包的注解,我们这里就不用xml文件了,要是想使用xml来配置要扫描的包路径的小伙伴可以使用dom4j来解析xml文件

@ComponentScan(value = "com_.lhjedu.spring.component")
public class LhjSpringConfig {
}

com_.lhjedu.spring.component 就是用来存放要存入Spring容器的bean的包

  1. ioc包中创建BeanDefinition 类,用来存放bean的信息,有scope和 class对象
public class BeanDefinition {
    private String scope;
    private Class clazz;

    public String getSingleton() {
        return scope;
    }

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

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    @Override
    public String toString() {
        return "BeanDefinition{" +
                "scope='" + scope + '\'' +
                ", clazz=" + clazz +
                '}';
    }
}
  1. 在ioc包下面创建LhjSpringApplicationContext类,是Spring容器的主要类,用来封装bean信息,实例化bean,获取bean等功能 现在我们先来实现封装BeanDefinition放入map中,初始化单例池的功能
package com_.lhjedu.spring.ioc;

import com.lhjedu.spring.annotation.Autowired;
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import com_.lhjedu.spring.annotation.Component;
import com_.lhjedu.spring.annotation.ComponentScan;
import com_.lhjedu.spring.annotation.Scope;
import com_.lhjedu.spring.anomaly.NotFoundBeanException;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 罗鸿基
 * @version 1.0
 */
public class LhjSpringApplicationContext {
    private Class configClass;

    //用来存储bean对象的信息
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap =
            new ConcurrentHashMap();

    private ConcurrentHashMap<String,Object> singletonObjects =
            new ConcurrentHashMap();

    public LhjSpringApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描存入bean信息
        beanDefinitionScan(configClass);
        //初始化对象池
        initializationBean();
    }

    private void initializationBean() {
        //使用枚举来遍历
        Enumeration<String> keys = beanDefinitionMap.keys();
        //先将对象存入单例池
        while (keys.hasMoreElements()) {
            String beanId = keys.nextElement();
            BeanDefinition beanDefinition =  beanDefinitionMap.get(beanId);
            if (beanDefinition == null) {
                //如果没有该bean的信息
                //那么报null异常
                throw new NotFoundBeanException("没有该bean");
            }

            //判断是否单例
            if ("singleton".equals(beanDefinition.getSingleton())) {
                try {
                    //创建实例
                    Object bean = createBean(beanDefinition,beanId);
                    //存入单例池
                    singletonObjects.put(beanId,bean);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //创建对象
    private Object createBean(BeanDefinition beanDefinition, String beanId) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class clazz = beanDefinition.getClazz();

        //创建对象
        Object bean = clazz.getDeclaredConstructor().newInstance();
        //判断是否依赖注入
        //遍历要创建的对象的所有字段
        //判断字段上是否有依赖注入
        for (Field declaredField : clazz.getDeclaredFields()) {
            //得到字段的名字
            if (declaredField.isAnnotationPresent(com.lhjedu.spring.annotation.Autowired.class)) {
                //获取注解的字段
                com.lhjedu.spring.annotation.Autowired autowired = declaredField.getDeclaredAnnotation(Autowired.class);
                boolean required = autowired.required();
                if (required) {
                    //拿到依赖注入的字段名字
                    String name = declaredField.getName();
                    //再通过getBean来获取对象
                    Object obj = getBean(name);
                    declaredField.setAccessible(true);//因为属性是私有的,所以需要爆破
                    //进行组装, 创建 bean , bean中有个obj属性要来组装
                    declaredField.set(bean, obj);
                }
            }
        }
        
        return bean;
    }

   

    //这个方法来扫描包
    private void beanDefinitionScan(Class configClass) {
        //拿到注解
        ComponentScan componentScan
                = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
        //拿到注解的路径
        String scanPath = componentScan.value();
        scanPath = scanPath.replace(".", "/");

        //拿到类加载器
        ClassLoader classLoader = LhjSpringApplicationContext.class.getClassLoader();
        //类加载器得到该扫描路径的url (全路径)
        URL url = classLoader.getResource(scanPath);
        /*file:/G:/HSPVIP/hsp-Java/java-web/spring_poject/lhj-myspring/target/classes/com_/lhjedu/spring/component*/
        //通过类的路径获取该路径下的类
        File file = new File(url.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f:
                    files) {
                //拿到类的路径
                String classAbsolutePath = f.getAbsolutePath();
                //判断是不是.class后缀
                if (classAbsolutePath.endsWith(".class")) {
                    //拿到类名
                    String className =
                            classAbsolutePath.substring(classAbsolutePath.lastIndexOf("\\") + 1,
                                    classAbsolutePath.indexOf(".class"));
                    //拿到类路径
                    scanPath = scanPath.replace("/", ".");
                    String classScanPath = scanPath + "." + className;

                    //通过反来创建类的class对象
                    try {
                        Class<?> clazz = classLoader.loadClass(classScanPath);
                        //判断是否有要注入的注解
                        if (clazz.isAnnotationPresent(Component.class)) {
                            //然后要封装到singleton中
                            //先判断是否有指定id
                            Component component = clazz.getDeclaredAnnotation(Component.class);
                            String beanId = component.value();
                            if ("".equals(beanId)) {
                                //没有指定id
                                //id就是类名首字母小写
                                beanId = StringUtils.uncapitalize(className);
                            }
                            //到了这里id好了
                            //然后要来封装到BeanDefinition
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(clazz);
                            //判断是否为单例,有单例多例的注解
                            String scope = "singleton";
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                if ("prototype".equals(clazz.getDeclaredAnnotation(Scope.class).value())) {
                                    //多例
                                    scope = "prototype";
                                }
                            }
                            beanDefinition.setSingleton(scope);

                            beanDefinitionMap.put(beanId,beanDefinition);
                        }

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

  1. 我们来编写getBean方法,getBean返回对象思路如下 :
    在这里插入图片描述
 public Object getBean(String beanId) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {

        Object bean = null;
        //判断是否有该bean
        if (beanDefinitionMap.containsKey(beanId)) {
            //如果有bean就判断是否为单例,看要创建还是获取
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanId);
            String scope = beanDefinition.getSingleton();
            if ("singleton".equals(scope)) {
                 bean = singletonObjects.get(beanId);
            }else if ("prototype".equals(scope)){
                bean = createBean(beanDefinition, beanId);
            }
        }else {
            throw new NotFoundBeanException("该bean不存在");
        }

        return bean;
    }

😘😘😘

总代码 :

gitee代码地址

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无满*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值