手写Mini版的Spring

本文记录了作者手写简化版Spring的过程,包括启动时扫描组件到容器,依赖注入的实现,以及特殊功能如自动赋值beanName、InitializingBean接口支持和BeanPostProcessor。文章详细介绍了每个步骤的代码实现和逻辑,提供了手动测试验证。
摘要由CSDN通过智能技术生成

这段时间有想法去学习手写Spring源码,在此记录一下自己手写的Spring的迭代版本趴。

1. Spring的启动扫描到容器

@Component
@ComponentScan
@Scope // 判断Bean对象是不是单例,值为singleton是单例,

上述注解需要手写出来来实现简单实现Spring中的功能

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
   

    /**
     * 容器中类的名称
     * @return
     */
    String value() default "";
}
/**
 * 自定义的注解,默认扫描包的路径
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
   

    String value() ; // 扫描的路径
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
   


    String value();
}

上述三个注解里面没有什么价值性的东西,因为注解在我理解就是一个标识嘛。

接下来就要实现ApplicationContext,这玩意和我们使用Spring的ApplicationContext一样,主要的核心都在里面
直接上代码。

/**
 * 容器类
 */
public class ZJHApplicationContext {
   

    /**
     * 配置类
     */
    private Class configClass;

    /**
     * 单例池,里面存储的是单例对象
     */
    private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();

    /**
     * 存放BeanDefinition对象
     */
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();



    /**
     * 构造器
     *
     * @param configClass
     * @throws ClassNotFoundException
     */
    public ZJHApplicationContext(Class configClass) {
   
        this.configClass = configClass;
        // 初始化Bean容器,其实Scan方法并没有将单例Bean对象加入到单例池中,Scan方法的主要作用就是将BeanDefinition对象加入到BeanDefinitionMap中,为了下面的将单例Bean加入到单例池中做准备
        scan(configClass);

        // 初始化BeanDefinition容器后,通Beandefinition容器里的元素来一一将扫描路径下的对象的Beandefinition对象取出
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
   
            // 获取Beandefinition对象的key,也就是Bean的name
            String beanName = beanDefinitionEntry.getKey();
            // 获取BeandefinitionEntry的value,也就是BeanDefinition对象
            BeanDefinition beanDefinition = beanDefinitionEntry.getValue();
            // 接下来就是获取BeanDefinition对象的scope属性来判断对象是单例还是多例
            if ("singleton".equals(beanDefinition.getScope())){
   
                // 说明是单例Bean,将单例Bean加入到单例池中
                Object bean = createBean(beanDefinition);
                singletonObjects.put(beanName,bean);
            }
            // 如果对象是多例,则不需要加入到单例池中
        }

    }

    /**
     * 创建Bean对象的方法,
     * @param beanDefinition
     * @return
     */
    public Object createBean(BeanDefinition beanDefinition){
   
        // 通过BeanDefinition的class属性来通过反射创建出对象
        Class clazz = beanDefinition.getClazz();
        try {
   
            Object instance = clazz.getDeclaredConstructor().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容器的方法
     * @param configClass
     */
    private void scan(Class configClass){
   
        // 拿到配置类后会去解析这个配置类

        // 1. 解析@ComponentScan注解的信息 -> 获取扫描路径 -> 扫描

        // 查看配置类里有没有ComponentScan注解
        ComponentScan componentScanAnnotation = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
        // 获取扫描路径
        String path = componentScanAnnotation.value();
        System.out.println(path); // test.service

        // 扫描路径的类是否加入了Component注解

        // 1. 根据扫描路径来得到路径下的所有类,通过类加载器来获取,这里需要应用类加载器,因为Bootstrap,ext加载器不加载我们自定义的类,通过自己的类来获取对应的类加载器
        ClassLoader classLoader = ZJHApplicationContext.class.getClassLoader();

        path = path.replace(".", "//");
        // 这个resource是一个目录
        URL resource = classLoader.getResource(path);
        // 通过File类来获取目录下的文件
        File file = new File(resource.getFile());
        // 如果文件是一个文件夹,则获取文件夹下的所有的文件
        if (file.isDirectory()) {
   
            File[] files = file.listFiles();
            for (File f : files) {
   
                System.out.println(f);

                // 将D:\Tools\Spring_ZJH\target\classes\test\service\UserService.class转为 test.service.UserService
                String fileName = f.getAbsolutePath();

                // 判断文件是不是class文件,如果是的话在开始截取
                if (fileName.endsWith(".class")) {
   
                    // 从test开始截取,到.class结束
                    String className = fileName.substring(fileName.indexOf("test"), fileName.indexOf(".class"));
                    // 将\转换为.
                    className = className.replace("\\", ".");
                    // 得到了类的路径
                    System.out.println(className);

                    try {
   
                        // 通过类加载器来加载类
                        Class<?> clazz = classLoader.loadClass(className);
                        // 判断类上是否有Component注解
                        if (clazz.isAnnotationPresent(Component.class)) {
   
                            // 到这一步就说明扫描类中已经有了Component注解,即它是一个Bean对象

                            // 到了这一步得判断这个Bean是单例Bean还是Prototype的Bean(即多例Bean)
                            // 如果是单例Bean则将Bean对象加入到singletonObjects单例池中
                            // 在加入容器时,我们需要判断Bean是单例还是多例,在getBean方法时,我们也需要判断,但其实没有必要判断两次,Spring中有BeanDefinition的概念,即Bean的定义
                            // 每解析一个类都会生成一个BeanDefinition对象

                            // 获取Bean的名称
                            Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();

                            // 生成BeanDefinitation对象,注意这个不是Bean对象,而是Bean的定义
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(clazz);

                            if (clazz.isAnnotationPresent(Scope.class)){
   
                                // 设置BeanDefinitation对象的scope属性
                                Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                beanDefinition.setScope(value);
                            }else {
   
                                // Bean中没有scope注解,则将BeanDefinition的scope属性设置为singleton,即单例
                                beanDefinition.setScope("singleton");

                            }

                            // 将BeanDefinition对象加入到BeanDefinitionMap中
                            beanDefinitionMap.put(beanName,beanDefinition);


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

                }
            }
        }
    }

    /**
     * getBean方法
     *
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) {
   
        if (beanDefinitionMap.containsKey(beanName)){
   
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if ("singleton".equals(beanDefinition.getScope())){
   
                // 说明这是单例Bean,从单例池子中获取Bean对象返回
                Object o = singletonObjects.get(beanName);
                return o;
            }else {
   
                // 说明这是多例Bean 手动创建Bean对象并返回
                return createBean
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring框架是一个非常庞大的框架,涵盖了很多的功能和模块。如果要手写一个简单Spring框架,我们可以从以下几个方面入手: 1. IOC容器 Spring框架的核心就是IOC容器。我们可以实现一个简单的IOC容器,通过注解或配置文件的方式来管理Bean。首先,我们需要定义一个Bean类,它包含了类名、类路径、是否单例等信息。然后,我们需要解析注解或配置文件,将所有的Bean信息存储到一个Map中。最后,在需要使用Bean的地方,我们可以通过Bean的名称从Map中获取Bean实例。 2. AOP Spring框架的另一个核心是AOP。AOP可以帮助我们实现各种各样的切面功能,例如事务管理、日志记录等。我们可以通过定义切点和切面来实现AOP。切点定义了哪些方法需要被代理,切面定义了具体的代理逻辑。我们可以使用JDK动态代理或者CGLIB动态代理来实现代理逻辑。 3. MVC Spring框架还提供了一个MVC模块来帮助我们实现Web应用程序。我们可以实现一个简单的DispatcherServlet来接收HTTP请求,并且根据请求路径和请求方法来调用相应的Controller方法。Controller方法可以返回一个ModelAndView对象,其中包含了响应页面的路径和数据模型。最后,我们可以使用模板引擎来渲染响应页面。 以上是实现一个简单Spring框架的基本思路。当然,这只是一个简单的示例,实际上Spring框架还包括了很多其他的功能和模块,例如JDBC、ORM等。如果想要更深入地了解Spring框架,可以参考Spring官方文档或者相关书籍。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值