0


手写实现简易spring框架第一天

哈喽大家好,最近闲来无事,又不想去研究业务逻辑,所以就开始尝试写一下简易spring框架的实现。由于刚工作一年且因为是在学习,所以如果有写的不对的地方,还希望各位大神给一点指导。本文的主要意图呢,就是为了我学习不会间断

先放出spring的官网供大家查阅

Spring | Home

一、spring简介

这里我相信大家应该对spring都很了解了,就简单的一笔带过,不写那么多啰嗦的话题了。

核心就是一个控制反转(ioc)和面向切面编程(AOP)

主要功能呢就是:

1:方便解耦,简化开发;

2:方便的对程序进行拦截、运行、监控等功能;

3:可以对事务声明;

4:属于一个万能的框架,跟很多框架都是百搭;

由于本文只描述如何实现简易spring框架,所以不再阐述spring框架的搭建以及配置。

二、如何实现

一、准备工作

我们先创建一个基本上什么都没有的maven工程,大致如下

如果不会创建的小伙伴可以移步这位大佬的文档

(2条消息) Idea中创建maven项目(超详细)_Yan Yang的博客-CSDN博客_idea创建maven项目

建好我们的文件夹以及我们的容器类,还有测试类,这里先不用去管我的别的文件夹,只要去看我圈出来的类就可以了。这里是建了一个ZfApplicationContext去作为我们框架的容器类,以及Test测试类。

在spring框架中我们知道在创建容器类的时候要传递一个配置类作为参数,就是我们的AppConfig,

已经在调用getBean方法时传递的beanName作为参数,这里创建了一个空的业务层去模拟我们平时的业务层,这里的AppConfig类目前也是空的状态,里面并没有加内容。到这里我们的准备工作大致是完成了。

容器类中的代码,在提供一个构造方法

public class ZfApplicationContext {

    //配置类
    private Class configClass;

    //创建容器时传入配置类
    public ZfApplicationContext(Class configClass) {
        this.configClass = configClass;
    }
}

二、spring的启动和扫描逻辑的实现

我们知道在spring中实例化容器类的时候,传入的配置类可以通过**@ComponentScan**注解将我们要扫描的目录传递进去,这个是怎么实现的呢?

我们也来定义一个简单的**@ComponentScan**注解,这里我就不给它设置默认值了,我们后续可以手动去给它添加路径

@Retention(RetentionPolicy.RUNTIME) 、//意为运行时注解
@Target(ElementType.TYPE) //用于描述类、接口(包括注解类型) 或enum声明
public @interface ComponentScan {

    String value();
}

当我们可以通过**@ComponentScan获得要扫描的路径,在spring中还有一个@Component**注解去标记那些类是需要被创建为bean对象的,所以我们还需要一个@Component注解,同样我们只是实现简单的spring框架,所以就不在去实现该注解是如何去实现给类设置默认值,我们手动去添加(主要是懒-.-!)。

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

    String value();
}

这时候我们可以将**@ComponentScan**注解写在我们的配置类上,并设置值,即为我们要扫描的目录

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

在spring中还需要一个注解去设置我们容器中的作用域,就是我们的@Scope注解,

** singleton单例模式 -- 全局有且仅有一个实例
 prototype原型模式 -- 每次获取Bean的时候会有一个新的实例
 request -- request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
 session -- session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
 globalsession -- global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义**

所以我们应该也要在容器类****ZfApplicationContext中添加俩个属性分别为

    //单例池 ,主要存单例对象
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
    //存储我们一开始扫描到的所有的bean的定义
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

分别去存储单例对象,和我们其他的bean,这里spring中用到了ConcurrentHashMap,其实我们自己去实现简易的容器的话也可以用HashMap,这里我们key存储beanName,value就是存储我们定义好的BeanDefiniyion对象,方便我们接下来去实现getBean方法。

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

    String value();
}

我们在容器解析到bean对象时需要一个类去“存储“这个bean,这里的存储并不是真正意义上的存储,而是存储这个bean对象的类型和它的作用域。即为bean的定义,BeanDefinition,@Component,@Scope等注解就是在定义一个bean

public class BeanDefinition {
    //当前某一个bean的类型
    private Class clazz;
    //当前bean的作用域
    private String scope;

    public BeanDefinition(Class clazz, String scope) {
        this.clazz = clazz;
        this.scope = scope;
    }

    public BeanDefinition() {
    }

    public Class getClazz() {
        return clazz;
    }

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

    public String getScope() {
        return scope;
    }

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

接下来就是我们如何去在ZfApplicationContext中的构造方法里实现里去解析我们的配置类

我们需要去实现一个方法为scan(),这里的一段代码我是都有做好注解的。

    private void scan(Class configClass) {
        //解析配置类
        //通过componentScan注解---->扫描路径 ---->扫描 ---->开始解析类
        //获取配置类上的自定义ComponentScan注解
        ComponentScan componentScanAnnotation = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);

        //获取扫描路径
        String path = componentScanAnnotation.value();
        //测试能否拿到路径
//        System.out.println(path); 这里打印的com.zf.service为包名,我们需要将“.”替换为“/”,意为将其替换为路径
        path = path.replace(".","/");

        //扫描 更据path包名,得到包下面的路径
        //三种类加载器
        //Bootstrap ----> jre/lib
        //Ext ------> jre/ext/lib
        //App -------> classpath ---->指的是当前应用对应的那个路径
        //例如: ClassLoader classLoader = ZfApplicationContext.class.getClassLoader();
        //classLoader.getResource("com/zf/service"); 意思为拿到ZfApplicationContext这个app下的路径为"com/zf/service"的资源,这个相对路径就是当前这个classLoader所对应的路径
        //这里的Resource即可以是某个文件也可以是某个目录
        ClassLoader classLoader = ZfApplicationContext.class.getClassLoader();
        //URL统一资源标识符(Uniform Resource Identifier ,URL)是采用一种特定语法标识一个资源的字符串。所标识的资源可能是服务器上的一个文件。
        //这里就是拿到当前指定扫描路径下的文件目录
        URL resource = classLoader.getResource(path);
        //这里的resource是"com/zf/service"下目录的file对象,
        File file = new File(resource.getFile());
        //判断是否是一个目录
        if (file.isDirectory()) {
            //拿出这个目录下的所有文件
            File[] files = file.listFiles();
            //循环扫描到的类
            for (File f : files) {
                //获取该文件的绝对路径并截取成全限定名
                //这里是将C:\Users\Administrator\IdeaProjects\spring2\target\classes\com\zf\service\UserService.class 截取成并替换成com.zf.service.UserService
                String fileName = f.getAbsolutePath();
                //当然要先判断这个类是否是类文件
                if (fileName.endsWith(".class")) {
                    String className = fileName.substring(fileName.indexOf("com"),fileName.indexOf(".class"));
                    //拿到这个类的全限定名
                    className = className.replace("\\", ".");
                    //ClassLoader的加载机制,ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了
                    //找到当前包下某个类
                    try {

                        Class<?> clazz = classLoader.loadClass(className);
                        //判断该类是否有Component注解
                        if (clazz.isAnnotationPresent(Component.class)) {
                            //有的话就表示当前这个类是一个Bean对象
                            // 解析类 ,判断当前bean是单例bean,还是原型(prototype)的bean
                            //TODO BeanDefinition :bean的定义 (@Component,@Scope等注解就是在定义一个bean)
                            //解析某一个类,生成某一个BeanDefinition对象

                            //先通过Component注解取出bean的名字
                            Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();

                            //创建一个BeanDefinition对象
                            BeanDefinition beanDefinition = new BeanDefinition();
                            //将bean对象的类型设置进去
                            beanDefinition.setClazz(clazz);

                            //查看是否设置一些作用域
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                //如果存在就拿出作用域名称设置进BeanDefinition对象
                                Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                                beanDefinition.setScope(scopeAnnotation.value());
                            } else {
                                //如果没有就表明这个bean是默认单例的
                                beanDefinition.setScope("singleton");
                            }
                            //将扫描到的bean的定义存进去
                            beanDefinitionMap.put(beanName,beanDefinition);

                        }

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

                }
            }
        }
    }

启动时扫描完得到所有的beanDefinition对象,将beanDefinitionMap里的单例bean全部创建好存入我们的单例池,我们的构造方法就会变成如下

    public ZfApplicationContext(Class configClass) {
        this.configClass = configClass;
        //通过componentScan注解---->扫描路径 ---->扫描 ---->开始解析成BeanDefinition------>放入beanDefinitionMap中
        scan(configClass);

        //启动时扫描完得到所有的beanDefinition,将beanDefinitionMap里的单例bean全部创建好存入单例池
        for (Map.Entry<String,BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            //遍历所有的bean,拿到单例的bean并创建
            if (beanDefinition.getScope().equals("singleton")) {
                //创建单例bean
                Object bean = createBean(beanDefinition);
                //放入单例池
                singletonObjects.put(beanName,bean);
            }
        }
    }

    //创建bean的方法,根据bean的定义BeanDefinition,去创建一个bean
    public Object createBean(BeanDefinition beanDefinition) {
        //通过传入的BeanDefinition对象获取bean的类型,通过反射直接构造一个实例对象
        try {
            Class clazz = beanDefinition.getClazz();
            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;
    }

然后我们的getBeanName方法就好实现啦。直接上代码吧

    public Object getBean(String beanName) {
        //判断传入的beanName在spring容器中是否存在,就是是否存在beanDefinitionMap中
        if (beanDefinitionMap.containsKey(beanName)) {
            //如果存在判断这个bean对象是否是一个单例的如果是,那就从单例池里面返回一个对象,然后返回这个bean对象
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if (beanDefinition.getScope().equals("singleton")) {
                Object o = singletonObjects.get(beanName);
                return o;
            } else {
                //如果是原型作用域prototype,调用createBean方法直接创建返回bean对象
                Object bean = createBean(beanDefinition);
                return bean;
            }

        }else {
            //抛出异常,不存在这个bean
        }
        return null;
    }

然后我们来看一下测试类;

🆗,今天就到此为止啦!欢迎大家讨论,在此要感谢一位叫周瑜的架构老师!有不对的地方欢迎指出,好让我去及时修改,谢谢!

标签: spring java

本文转载自: https://blog.csdn.net/weixin_51516004/article/details/125350497
版权归原作者 幕幕幕幕幕幕景~ 所有, 如有侵权,请联系我们删除。

“手写实现简易spring框架第一天”的评论:

还没有评论