纯手写SpringFramework-第一代(原创)

2年前 (2022) 程序员胖胖胖虎阿
254 0 0

个人简介

作者是一个来自河源的大三在校生,以下笔记都是作者自学之路的一些浅薄经验,如有错误请指正,将来会不断的完善笔记,帮助更多的Java爱好者入门。

文章目录

    • 个人简介
    • 纯手写SpringFramework-第一代
      • Spring是什么?
      • 我们手写的第一代Spring实现了什么?
      • 手写Spring项目的结构
      • 手写Spring-核心注解
        • @MyAutowired
        • @MyComponent
        • @MyComponentScan
        • @MyScope
      • 手写Spring-入口配置类
      • 手写Spring-Bean的定义类
      • 手写Spring-用户业务类
      • 手写Spring-容器类(核心)
        • 容器顶层接口
        • 容器实现类(核心)
          • 核心步骤深度解析
      • 手写Spring-测试类

纯手写SpringFramework-第一代

Spring是什么?

Spring是一个开源框架,它由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson)创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。

Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。

轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。

控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。

面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。

(以上摘选自:《百度百科》)

我们手写的第一代Spring实现了什么?

事实上,写一个Spring的难度是极大的,我作为一个在校学生是不可能写出完整版的,故第一代我花了一天去构思,主要对Spring的架构进行梳理,实现了其扫描包组件、及控制反转(IOC)容器、以及Component、ComponentScan、Scope等注解功能,(DI和AOP)等功能将在后面进行逐步完善。如有不足,请谅解。

手写Spring项目的结构

我们手写的Spring起名叫:MySpringFramework

纯手写SpringFramework-第一代(原创)

项目包结构介绍

com.springframework.core:这个包是我们的核心包,所有核心文件都放在这

com.springframework.core.annotation:注解包

com.springframework.core.config:启动配置包,例如MyComponentScan注解配置类

com.springframework.core.constant:常量包

com.springframework.core.context:Spring的核心内容包,例如MyAnnotationConfigApplicationContext上下文。

com.springframework.core.exception:自定义异常类的包

com.springframework.core.service:用户写的类都放在这里等待被扫描。

com.springframework.test:测试Spring功能包

手写Spring-核心注解

@MyAutowired

package com.springframework.core.annotation;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface MyAutowired {

}

  • 作用是自动注入,该注解只能放在字段上

@MyComponent

package com.springframework.core.annotation;

import java.lang.annotation.*;

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

    String value() default ""; //组件beanName

}
  • MySpring的组件注解,作用是把一个类添加到IOC容器中,生成bean对象。

@MyComponentScan

package com.springframework.core.annotation;

import java.lang.annotation.*;


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

    String value();

}
  • 扫描组件注解,作用是指定一个value,value为指定扫描的包的全名称,例如com.springframework.core.service

@MyScope

package com.springframework.core.annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyScope {
    String value();
}
  • 作用是指定@MyComponent作用的类上是单例还是多例。

  • 如果不指定,默认是单例singleton。

package com.springframework.core.constant;

import java.io.Serializable;

/**
 * scope的类型常量
 * 例如:单例和多例
 */
public final class ScopeType implements Serializable {

    private ScopeType(){}
    //单例
    public static final String SINGLETON="singleton";

    //多例
    public static final String PROTOTYPE="prototype";

}

手写Spring-入口配置类

  • 这个类是必须要写的,不然MySpring不知道我们要扫描那个包。
package com.springframework.core.config;

import com.springframework.core.annotation.MyComponentScan;

/**
 * Spring的配置类
 */
@MyComponentScan("com.springframework.core.service")//扫描包名
public class SpringApplicationConfig {

}
  • 这需要这样指定就可以啦。

手写Spring-Bean的定义类

package com.springframework.core.context;

import java.io.Serializable;

/**
 * bean的定义
 * @author 游政杰
 * TODO: 2022/3/20
 */
public class BeanDefinition implements Serializable {

    private Class<?> type; //bean所属类
    private String scope; //单例还是多例

    public Class<?> getType() {
        return type;
    }

    public void setType(Class<?> type) {
        this.type = type;
    }

    public String getScope() {
        return scope;
    }

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

    @Override
    public String toString() {
        return "BeanDefinition{" +
                "type=" + type +
                ", scope='" + scope + '\'' +
                '}';
    }
}

  • 这个类的作用是:当MySpring在初始化容器时,首先会把指定包下的所有加了@MyComponent注解的类都扫描,并且不管是单例还是多例都要封装成一个BeanDefinition对象放到beanDefinitionMap中(key为beanName)
  • 后面我们创建bean对象必须依靠这个beanDefinitionMap

手写Spring-用户业务类

  • 这个不是重点,看看就好,没啥的!!!!
package com.springframework.core.service;

public interface UserService {
}
package com.springframework.core.service;

import com.springframework.core.annotation.MyComponent;
import com.springframework.core.annotation.MyScope;
import com.springframework.core.constant.ScopeType;
import com.springframework.core.service.UserService;

@MyComponent //标记组件
@MyScope(ScopeType.SINGLETON) //标记单例
public class UserServiceImpl implements UserService {

}

手写Spring-容器类(核心)

容器顶层接口

  • 这个也没啥,目前就定义了一个getBean方法待实现。
package com.springframework.core.context;

/**
 * 手写Spring框架
 * @author 游政杰
 */
public interface ApplicationContext {

    Object getBean(String beanName);

}

容器实现类(核心)

  • 这里才是MySpring的核心之处,较为深度的模拟了SpringFramework的一些步骤,不过还是有很多不完善,等待第二代的更新。
package com.springframework.core.context;

import com.springframework.core.annotation.MyComponent;
import com.springframework.core.annotation.MyComponentScan;
import com.springframework.core.annotation.MyScope;
import com.springframework.core.constant.ScopeType;
import com.springframework.core.exception.NotSuchBeanException;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 纯手写一个Spring框架
 * @author 游政杰
 * TODO: 2022/3/19
 */
public class MyAnnotationConfigApplicationContext implements ApplicationContext {

    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap;


    private ConcurrentHashMap<String,Object> singletonMap;


    private MyAnnotationConfigApplicationContext(){}

    public MyAnnotationConfigApplicationContext(final Class<?> configClass) {

        try {

            if (configClass.isAnnotationPresent(MyComponentScan.class)) {

                this.beanDefinitionMap=new ConcurrentHashMap<>();
                this.singletonMap=new ConcurrentHashMap<>();


                MyComponentScan componentScan = configClass.getAnnotation(MyComponentScan.class);

                String scc = componentScan.value();

                String sc = scc.replace(".", "/");
                ClassLoader classLoader = this.getClass().getClassLoader();

                String uri = Objects.requireNonNull(classLoader.getResource(sc)).toURI().getPath();
                String scp = uri.substring(1, uri.length());
                File dir = new File(scp);

                if (dir.isDirectory()) {
                    File[] files = dir.listFiles();
                    if(files!=null&&files.length>0){

                        for (File file : files) {
                            //暂时不做这个处理。这个可以递归实现
//                            if(file.isDirectory()){
//
//                            }

                            if(file.getName().endsWith(".class")){
                                //取类名
                                String cn = file.getName().substring(0,file.getName().length()-6);
                                //全类名
                                String className=scc+"."+cn;

                                Class<?> aClass = classLoader.loadClass(className);

                                if(!aClass.isInterface()&&aClass.isAnnotationPresent(MyComponent.class)) {
                                    BeanDefinition beanDefinition = new BeanDefinition();
                                    //获取类名
                                    String simpleName = aClass.getSimpleName();

                                    String beanName = Introspector.decapitalize(simpleName);

                                    beanDefinition.setType(aClass);

                                    if(aClass.isAnnotationPresent(MyScope.class)){

                                        MyScope myScope = aClass.getAnnotation(MyScope.class);

                                        String sv = myScope.value();
                                        //为null则报错
                                        sv = Objects.requireNonNull(sv);

                                        if(sv.equals(ScopeType.PROTOTYPE)){

                                            beanDefinition.setScope(ScopeType.PROTOTYPE);
                                        }else {

                                            beanDefinition.setScope(ScopeType.SINGLETON);
                                        }
                                    }

                                    beanDefinitionMap.put(beanName,beanDefinition);
                                }
                            }
                        }

                    }

                }

                ConcurrentHashMap.KeySetView<String, BeanDefinition>
                        beanDefinitionKeySetView = beanDefinitionMap.keySet();


                for (String beanName : beanDefinitionKeySetView) {
                    BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
                    String scope = beanDefinition.getScope();
                    if(scope!=null&&scope.equals(ScopeType.PROTOTYPE)){

                    }else {

                        if(!singletonMap.containsKey(beanName)){

                            Object obj = this.createBean(beanName);

                            singletonMap.put(beanName,obj);
                        }
                    }

                }





            }else {
                throw new RuntimeException("指定配置类上没有MyComponentScan注解存在");
            }
        }catch (URISyntaxException e1){
            e1.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }


    private Object createBean(String beanName) {


        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        if(beanDefinition==null){
            throw new NotSuchBeanException("beanDefinitionMap没有该bean的定义");
        }else {
            Class<?> aClass = beanDefinition.getType();
            try {
                Object obj = aClass.getConstructor().newInstance();
                return obj;
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            return null;
        }
    }


    @Override
    public Object getBean(String beanName) {

        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if(beanDefinition==null){
//            throw new NotSuchBeanException("beanDefinitionMap没有该bean的定义");
            return null;
        }else {

            String scope = beanDefinition.getScope();

            if(scope!=null&&scope.equals(ScopeType.PROTOTYPE)){

                Object obj = this.createBean(beanName);
                return obj;
            }else {

                if(singletonMap.containsKey(beanName)){

                    return singletonMap.get(beanName);
                }else {

                    Object obj = this.createBean(beanName);

                    singletonMap.put(beanName,obj);
                    return obj;
                }
            }
        }
    }
}
核心步骤深度解析

1:容器变量定义

    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap;

    private ConcurrentHashMap<String,Object> singletonMap;
  • beanDefinitionMap在初始化阶段就会把单例和多例的类封装成BeanDefinition对象放入,Map的格式为<beanName,该类的BeanDefinition对象>

  • singletonMap单例池,singletonMap和beanDefinitionMap的区别就是singletonMap存放的全都是单例,并且value全都是经过beanDefinitionMap的值通过反射生成的。该Map的格式为<beanName,bean对象>

2:

if (configClass.isAnnotationPresent(MyComponentScan.class)) {
    xxx
}
  • 判断扫描类上是否有MyComponentScan注解存在

3:

MyComponentScan componentScan = configClass.getAnnotation(MyComponentScan.class);
String scc = componentScan.value();
  • 1:获取MyComponentScan注解对象
  • 2:获取MyComponentScan注解的值—>com.springframework.core.service

4:

String sc = scc.replace(".", "/");
  • 转换成转换成 com/springframework/core/service格式

5:

ClassLoader classLoader = this.getClass().getClassLoader();
                //这种写法可以防止Java把空格路径转换成%20而导致找不到文件夹
                String uri = Objects.requireNonNull(classLoader.getResource(sc)).toURI().getPath();
                String scp = uri.substring(1, uri.length());
                File dir = new File(scp);
  • 找到编译后的存放字节码文件的文件夹。因为我们的框架通过操作字节码文件来达到想要的目的的。
  • 然后生成File对象,这个file对象必须是文件夹。

6:

if(file.getName().endsWith(".class")){
}
  • 指定我们只找后缀名为.class的文件,也就是编译后的字节码文件。

7:

								//取类名
                                String cn = file.getName().substring(0,file.getName().length()-6);
                                //全类名
                                String className=scc+"."+cn;
                                
                                Class<?> aClass = classLoader.loadClass(className);
  • 通过classloader类加载器去加载类,获取类实例

8:

 if(!aClass.isInterface()&&aClass.isAnnotationPresent(MyComponent.class)) {
 }
  • 如果不是接口并且还有MyComponent注解就把这个bean放到beanDefinitionMap中。

9:

BeanDefinition beanDefinition = new BeanDefinition();
                                    //获取类名
                                    String simpleName = aClass.getSimpleName();
                                    
                                    String beanName = Introspector.decapitalize(simpleName);
                                    //beanDefinition的class类型
                                    beanDefinition.setType(aClass);
  • Introspector.decapitalize可以把类名转换成bean名,也就是首字母小写的name

10:

if(aClass.isAnnotationPresent(MyScope.class)){
}
  • 判断是否有MyScope注解,判断是单例还是多例
beanDefinitionMap.put(beanName,beanDefinition);
  • 不管是单例还是多例都放到beanDefinitionMap中,等待下面的处理,到这里扫描就结束了

11:

ConcurrentHashMap.KeySetView<String, BeanDefinition>
                        beanDefinitionKeySetView = beanDefinitionMap.keySet();

                //遍历出beanName
                for (String beanName : beanDefinitionKeySetView) {
                    BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
                    String scope = beanDefinition.getScope();
                    if(scope!=null&&scope.equals(ScopeType.PROTOTYPE)){ //多例则不管它
                        //不做任何事
                    }else {
                        //如果是单例的,我们就要去单例池找一下,看看有没有
                        if(!singletonMap.containsKey(beanName)){//如果单例池没有这个对象就要创建了
                            //创建对象
                            Object obj = this.createBean(beanName);
                            //放入单例池
                            singletonMap.put(beanName,obj);
                        }
                    }

                }
  • 找出单例并把它生成bean对象放入单例池中

11:createBean

	BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);	
	if(beanDefinition==null){
            throw new NotSuchBeanException("beanDefinitionMap没有该bean的定义");
        }else {
            Class<?> aClass = beanDefinition.getType();
            try {
                Object obj = aClass.getConstructor().newInstance();
                return obj;
            }
  • 由于对象的BeanDefinition全都在加载阶段放到beanDefinitionMap中了,如果通过beanName拿不到说明根本就没有这个bean

12:getBean

			//判断是单例还是多例
            String scope = beanDefinition.getScope();
            //如果是单例的话
            if(scope!=null&&scope.equals(ScopeType.PROTOTYPE)){
                //多例则每次都创建对象
                Object obj = this.createBean(beanName);
                return obj;
            }else {
                //如果是单例的,我们就要去单例池找一下,看看有没有
                //如果有的话就返回这个单例池对象即可
                if(singletonMap.containsKey(beanName)){
                    //返回bean对象
                    return singletonMap.get(beanName);
                }else { //如果单例池没有这个对象就要创建了
                    //创建对象
                    Object obj = this.createBean(beanName);
                    //放入单例池
                    singletonMap.put(beanName,obj);
                    return obj;
                }
            }

手写Spring-测试类

package com.springframework.test;

import com.springframework.core.config.SpringApplicationConfig;
import com.springframework.core.context.ApplicationContext;
import com.springframework.core.context.MyAnnotationConfigApplicationContext;

import java.beans.Introspector;

public class SpringTest {

    public static void main(String[] args) {

        ApplicationContext applicationContext=new MyAnnotationConfigApplicationContext(SpringApplicationConfig.class);

        System.out.println(applicationContext.getBean("userServiceImpl"));
        System.out.println(applicationContext.getBean("userServiceImpl"));
        System.out.println(applicationContext.getBean("userServiceImpl"));
        System.out.println(applicationContext.getBean("userServiceImpl"));
        System.out.println(applicationContext.getBean("userServiceImpl"));

    }

}
  • 当前userServiceImpl的状态为:单例模式的bean
com.springframework.core.service.UserServiceImpl@51565ec2
com.springframework.core.service.UserServiceImpl@51565ec2
com.springframework.core.service.UserServiceImpl@51565ec2
com.springframework.core.service.UserServiceImpl@51565ec2
com.springframework.core.service.UserServiceImpl@51565ec2
  • OK!第一代的手写Spring就大功告成啦。轻点喷。目前有很多都是不完善的,第二代将更加完整的实现IOC、DI、AOP、Aware、InitializingBean、PostProcessor机制等功能。敬请期待!
版权声明:程序员胖胖胖虎阿 发表于 2022年11月7日 上午4:40。
转载请注明:纯手写SpringFramework-第一代(原创) | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...