Spring框架核心功能手写实现

Posted 十八岁讨厌编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架核心功能手写实现相关的知识,希望对你有一定的参考价值。

文章目录

概要

  • 手写Spring启动以及扫描流程
  • 手写getBean流程
  • 手写Bean生命周期流程
  • 手写依赖注入流程
  • 手写BeanPostProcessor机制
  • 手写Aop机制

Spring启动以及扫描流程实现

我们平时都是使用这两种方法获得spring容器,上面的是通过加载类路径上的配置文件来获得容器。下面的方式和上面的原理相同只不过是通过注解的形式去实现,我们传入的也是一个配置类的class文件,我们可以把这个文件类比成第一种方法中的xml文件,然后这个xml文件里的一个个标签都变成了注解。

基础环境搭建

首先搭建好基础环境:

我们的测试类:

public class MySpringTest 
    public static void main(String[] args) 
        MyApplicationContext applicationContext = new MyApplicationContext(AppConfig.class);

        Object bean = applicationContext.getBean("");

        System.out.println(bean);
    

我们的容器类MyApplicationContext :

public class MyApplicationContext 
    private Class configClass;

    public MyApplicationContext(Class configClass) 
        this.configClass = configClass;
    

    public Object getBean(String beanName)
        return null;
    

我们原来在编写Spring的配置文件的时候会使用一个注解@ComponentScan,来定义一个扫描路径。所以我们这里也定义了一个。

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

    String value();

  • Retention指定该注解的生命周期,设置成RUNTIME便于反射获取

  • Target指定注解的使用位置,设置为TYPE表示能在类上使用

我们的AppConfig;

@ComponentScan("com.zyb.service")
public class AppConfig 

根据扫描包我们再创建一个业务层类UseService,这个UseService我们一般会使用@Component注解进行标记,这里我们也是如此:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component 
    String value();

@Component("userService")
public class UserService 


扫描逻辑实现

我们的思路就是;

  • 解析配置类
  • 拿到ComponentScan注解
  • 得到扫描路径
  • 进行扫描

首先我们可以通过如下代码拿到AppConfig中ComponentScan的内容:

public MyApplicationContext(Class configClass) 
        this.configClass = configClass;
        //解析配置类
        //ComponentScan注解 --》 扫描路径 --》 扫描

        ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();
        System.out.println(path);


我们测试一下:

拿到路径之后我们就可以开始扫描了。而扫描的目的并不是包下的所有类,而是那些带有@Component注解的类,而Spring会将他们的对象当作Spring中的一个bean。

这里我们扫描的思路如下:

  • 通过类加载器,得到类路径上的class文件
  • 对文件进行筛选
    • 是否以class结尾(判断是否为class文件)
    • 对class文件名进行处理
      • 替换\\.
      • 截取全限定名
  • 然后将类加载进jvm虚拟机
  • 判断运行时类是否有@Component注解,如果有则进行相关的创建bean对象操作

代码如下:

    public MyApplicationContext(Class configClass) 
        this.configClass = configClass;
        //解析配置类
        //ComponentScan注解 --》 扫描路径 --》 扫描
        ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();
        String searchPath = path.replace(".", "/");
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(searchPath);
        File file = new File(resource.getFile());
        if (file.isDirectory()) 
            //首先筛选末尾是.class文件的
            File[] files = file.listFiles();
            for (File classFile: files) 
                String absolutePath = classFile.getAbsolutePath();
                if (absolutePath.endsWith(".class")) 
                    //拼接全限定名
                    String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    fullyQualifiedClassName = fullyQualifiedClassName.replace("\\\\", ".");
                    //使用app类加载器加载这个类
                    Class<?> aClass = null;
                    try 
                        aClass = classLoader.loadClass(fullyQualifiedClassName);
                        if (aClass.isAnnotationPresent(Component.class)) 
                            //然后加载bean

                        
                     catch (ClassNotFoundException e) 
                        e.printStackTrace();
                    
                    
                
            
        
    

注意;

  • classLoader.loadClass接收的是类的全限定名

当我们确认当前类有@Component注解的时候并不是急着去给其创建bean,我们在使用spring的时候是可以决定该bean是否为单例的。我们在这里还是创建一个同名注解@Scope:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope 
    String value() default "single";

默认是single单例的,如果是原型bean则传入prototype。

而我们实现单例bean是通过一个Map单例池,不考虑懒加载。

现在我们的大致思路就是在通过@Scope注解确定bean是单例模式还是原型模式之后,在进行相应的bean的创建,但是这里我们考虑到一个问题,我们在getBean的时候如果每次都去解析这个类获得@Scope是很麻烦的。所以我们在这里引入一个新的概念BeanDefinition。

BeanDefinition中包含了当前bean的所有定义,例如bean的类型、作用域、是否懒加载等等。注意bean的name不在BeanDefinition中:

public class BeanDefinition 
    private Class beanClass;
    private String scope;

    public Class getBeanClass() 
        return beanClass;
    

    public void setBeanClass(Class beanClass) 
        this.beanClass = beanClass;
    

    public String getScope() 
        return scope;
    

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

在Spring中每扫描到一个bean都会对其进行解析然后生成各自的BeanDefinition实例,随后将这个BeanDefinition对象放在map中,key为bean的name,BeanDefinition对象作为value。以后当我们要获取一个bean时先会去map中查看,如果查找不到bean的BeanDefinition才会去解析类,以此来减少解析类的次数提高效率。

接下来我们完善一下代码,思路如下:

  • 判断运行时类是否有Component注解
  • 如果有,再获取该类的@Scope
    • 如果没有注解则为默认的单例模式
    • 如果有此注解则为原型模式
  • 然后再把对应的模式添加到BeanDefinition
  • 将此BeanDefinition和对应的bean name添加到beanDefinitionMap中

getBean方法思路:

  • 判断要获取的bean的BeanDefinition是否存在于beanDefinitionMap
    • 如果不存在,说明容器中不存在该bean则直接报错
    • 如果存在则在BeanDefinition中拿到该bean的scope,根据scope再去具体的创建bean

代码如下:

public class MyApplicationContext 
    private Class configClass;
    private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
    private HashMap<String,BeanDefinition> beanDefinitionHashMap  = new HashMap<>();

    public MyApplicationContext(Class configClass) 
        this.configClass = configClass;
        //解析配置类
        //ComponentScan注解 --》 扫描路径 --》 扫描
        ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();
        String searchPath = path.replace(".", "/");
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(searchPath);
        File file = new File(resource.getFile());
        if (file.isDirectory()) 
            //首先筛选末尾是.class文件的
            File[] files = file.listFiles();
            for (File classFile: files) 
                String absolutePath = classFile.getAbsolutePath();
                if (absolutePath.endsWith(".class")) 
                    //拼接全限定名
                    System.out.println(classFile);
                    String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    fullyQualifiedClassName = fullyQualifiedClassName.replace("\\\\", ".");
                    //使用app类加载器加载这个类
                    Class<?> aClass = null;
                    try 
                        aClass = classLoader.loadClass(fullyQualifiedClassName);
                        if (aClass.isAnnotationPresent(Component.class)) 
                            BeanDefinition beanDefinition = new BeanDefinition();
                            if (aClass.isAnnotationPresent(Scope.class)) 
                                Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
                                String scope = scopeAnnotation.value();
                                beanDefinition.setScope(scope);
                            else
                                beanDefinition.setScope("single");
                            

                            Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            beanDefinitionHashMap.put(beanName,beanDefinition);

                        
                     catch (ClassNotFoundException e) 
                        e.printStackTrace();
                    

                
            
        
    

    public Object getBean(String beanName)
        //首先判断是否存在该bean的BeanDefinition
        if (beanDefinitionHashMap.containsKey(beanName)) 
            BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
            if (beanDefinition.getScope().equals("single")) 
                //单例模式创建bean
            else 
                //原型模式创建bean
            
        else 
            throw new NullPointerException("不存在该bean");
        
    


最后我们可以把扫描这部分逻辑提取出来重新建立一个scan方法:

public class MyApplicationContext 
    private Class configClass;
    private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
    private HashMap<String,BeanDefinition> beanDefinitionHashMap  = new HashMap<>();

    public MyApplicationContext(Class configClass) 
        this.configClass = configClass;
        scan(configClass);
    

    private void scan(Class configClass) 
        //解析配置类
        //ComponentScan注解 --》 扫描路径 --》 扫描
        ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();
        String searchPath = path.replace(".", "/");
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(searchPath);
        File file = new File(resource.getFile());
        if (file.isDirectory()) 
            //首先筛选末尾是.class文件的
            File[] files = file.listFiles();
            for (File classFile: files) 
                String absolutePath = classFile.getAbsolutePath();
                if (absolutePath.endsWith(".class")) 
                    //拼接全限定名
                    System.out.println(classFile);
                    String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    fullyQualifiedClassName = fullyQualifiedClassName.replace("\\\\", ".");
                    //使用app类加载器加载这个类
                    Class<?> aClass = null;
                    try 
                        aClass = classLoader.loadClass(fullyQualifiedClassName);
                        if (aClass.isAnnotationPresent(Component.class)) 
                            BeanDefinition beanDefinition = new BeanDefinition();
                            if (aClass.isAnnotationPresent(Scope.class)) 
                                Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
                                String scope = scopeAnnotation.value();
                                beanDefinition.setScope(scope);
                            else
                                beanDefinition.setScope("single");
                            

                            Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            beanDefinitionHashMap.put(beanName,beanDefinition);

                        
                     catch (ClassNotFoundException e) 
                        e.printStackTrace();
                    

                
            
        
    

    public Object getBean(String beanName)
        //首先判断是否存在该bean的BeanDefinition
        if (beanDefinitionHashMap.containsKey(beanName)) 
            BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
            if (beanDefinition.getScope().equals("single")) 
                //单例模式创建bean
            else 
                //原型模式创建bean
            
        else 
            throw new NullPointerException("不存在该bean");
        
    


bean创建的简单实现

spring容器启动之后,先进行扫描步骤,而这个扫描的主要作用就是得到BeanDefinition,这样我们就有了BeanDefinitionMap,然后我们就可以根据BeanDefinitionMap去创建单例池singleBeanHashMap为我们的getBean方法提供支持。

在getBean方法中:

  • 如果是单例模式的bean就直接在singleBeanHashMap中去拿
  • 如果是原型模式的bean就直接使用creatBean方法创建bean

我们这里的creatBean方法就是用来创建bean的方法,我们这里暂时只对此方法进行一个简单的实现。

整体代码如下:

public class MyApplicationContext 
    private Class configClass;
    private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
    private HashMap<String,BeanDefinition> beanDefinitionHashMap  = new HashMap<>();

    public MyApplicationContext(Class configClass) 
        this.configClass = configClass;
        scan(configClass);
        //scan之后就得到了beanDefinitionHashMap,然后我们根据此来构建singleBeanHashMap
        for (String beanName:beanDefinitionHashMap.keySet()) 
            从零开始手写 spring ioc 框架,深入学习 spring 源码

05. 手写Spring核心框架

:开篇介绍

Spring我抄袭了Spring,手写一套MySpring框架。。。

#yyds干货盘点# 30个类手写Spring核心原理之动态数据源切换

手写Spring MVC框架 实现简易版mvc框架