手写spring(2)-启动和扫描逻辑实现

在spring容器启动的过程中,会去扫描指定包路径下的class文件,判断当前类是否是一个bean对象,如果是一个bean对象,将其注入到spring容器中。本次,我们来实现spring启动过程中的扫描逻辑。

1.基础配置信息

AppConfig配置类的信息如下:

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

}

其中,@ComponentScan是由我们自己定义的一个注解类,主要用来定义包扫描的路径,spring在容器启动的过程中,会通过读取配置类AppConfig类的class对象,获取到@ComponentScan定义的扫描路径。

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

@Component是spring中常见的注解,在一个类上加上@Component注解,表示这个类是一个Bean

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    // 定义的bean名字,如果没有定义bean的名字,spring会将类名首字母小写作为bean的名字
    String value() default "";
}

UserServiceImpl类上加了@Component注解,在spring启动过程中,将会扫描到这个类(扫描的逻辑在后面)。

@Component("userService")
public class UserServiceImpl implements UserService {

@Scope定义bean的作用域,是单例singleton,还是原型prototype

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    // 定义bean的scope
    String value();
}

2.包扫描逻辑

spring启动类的信息如下:

public class Test {
    public static void main(String[] args) {
        ZhouyuApplicationContext applicationContext = new ZhouyuApplicationContext(AppConfig.class);

      	...
    }
}

首先,在spring包下新建ZhouyuApplicationContext.java,作为spring容器的模拟实现。

// 配置类的class
private Class configClass;

public ZhouyuApplicationContext(Class configClass) {
  this.configClass = configClass;

  // 解析配置类
  // 解析@ComponentScan-->扫描路径-->扫描--->beanDefinition--->BeanDefinitionMap
  scan(configClass);

  ...
}

ZhouyuApplicationContext类提供了一个Class configClass的属性、一个带有Class参数的构造方法,在spring启动类上通过传入一个Config配置类,作为spring启动类的配置信息。

scan方法完成了包扫描逻辑。通过传入的AppConfig类的class对象,我们可以读取到在AppConfig类上的@ComponentScan注解,其中定义了包扫描的路径。

ComponentScan componentScanAnnotation =(ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();//com.zhouyu.service

在Java中,需要加class加载到JVM中,不同的类是由不同的类加载器去完成加载的,Bootstrap类加载器加载jre/lib目录下的类,Ext类加载器加载jre/ext/lib目录下的类,App类加载器加载classpath目录下的类。其中classpath就是我们的项目路径。

笔者的电脑是Mac操作系统,Mac操作系统和Windows操作系统的文件路径的分隔符不同,在实现的时候需要注意。

private void scan(Class configClass) {
  ComponentScan componentScanAnnotation =(ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
  String path = componentScanAnnotation.value();//com.zhouyu.service

  // 扫描 通过类加载器去加载class到JVM中
  // Bootstrap--->jre/lib
  // Ext--------->jre/ext/lib
  // App--------->classpath
  ClassLoader classLoader = ZhouyuApplicationContext.class.getClassLoader();
  // 类加载器去加载相对路径的资源
  URL resource = classLoader.getResource(path.replaceAll("\\.", "/"));
  //        System.out.println(resource.getFile());
  // 转换成File,好操作
  File file = new File(resource.getFile());

  if (file.isDirectory()) {
    File[] files = file.listFiles();
    for (File file1 : files) {
      ///Users/liuxin/Desktop/Demo/lx-spring/target/classes/com/zhouyu/service/UserService.class
      String absolutePath = file1.getAbsolutePath();
      if (absolutePath.endsWith(".class")) {
        String substring = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));

        // 获取类的全限定名
        String className = substring.replaceAll("/", "\\.");//com.zhouyu.service.UserService

        try {
          Class<?> clazz = classLoader.loadClass(className);

           // 如果类上面加了@Component,则生成bean到spring容器
           if (clazz.isAnnotationPresent(Component.class)) {
             ...
           }

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

    }
  }
}

在spring扫描的过程中,不是存放的bean对象,而是存放的BeanDefinition。其中BeanDefinition是Bean对象的定义,里面存放了Bean的Class、bean的scope(singleton or prototype)等信息,用于我们来创建bean对象使用。

public class BeanDefinition {

    private Class clazz;

    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;
    }
}

在ZhouyuApplicationContext容器中,也定义了一个属性用来存放BeanDefinition

// beanDefinitionMap 存放的是spring启动的时候 扫描 到的beanDefintion
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

生成beanDefinition,设置beanClass和scope属性,添加到beanDefinitionMap

// 根据bean的作用域判断是否需要生成bean注入到spring容器
// 解析类 -> BeanDefinition
// BeanDefinition 有一个属性表示bean是单例还是原型
Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();

BeanDefinition beanDefinition = new BeanDefinition();
// 设置clazz
beanDefinition.setClazz(clazz);
// 设置scope
if (clazz.isAnnotationPresent(Scope.class)) {
    Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
    beanDefinition.setScope(scopeAnnotation.value());
} else {
    // 默认设置bean为单例
    beanDefinition.setScope("singleton");
}
// 添加beanDefinition到beanDefinitionMap中
beanDefinitionMap.put(beanName,beanDefinition);

3.单例池singletonObjects

spring中定义了单例池存放单例bean,在spring扫描逻辑scan方法执行完之后,spring会将非懒加载的单例bean注入到spring容器中

// 单例池,存放单例bean Map<beanName,bean对象>
private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();

// 在spring容器启动的时候,将单例bean加载到spring容器中
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
    String beanName = entry.getKey();
    BeanDefinition beanDefinition = entry.getValue();
    if ("singleton".equals(beanDefinition.getScope())) {
        Object bean = createBean(beanName,beanDefinition);
        singletonObjects.put(beanName,bean);
    }
}

spring中获取bean对象通过getBean方法,支持通过Bean的class、name来获取bean对象,在获取bean时也会去判断bean的scope,如果是单例直接从单例池获取,如果是多例则通过createBean方法创建bean。在实现中通过beanName获取bean对象。

public Object getBean(String beanName) {
  if (beanDefinitionMap.containsKey(beanName)) {
    BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
    if ("singleton".equals(beanDefinition.getScope())) {
      Object o = singletonObjects.get(beanName);
      return o;
    } else {
      // 原型,创建bean返回
      return createBean(beanName,beanDefinition);
    }
  } else {
    throw new RuntimeException("不存在的beanName");
  }
}

spring通过createBean方法创建bean对象

/**
 * 单例、原型bean都是通过这个方法创建的
 * @param beanDefinition
 * @return
 */
public Object createBean(String beanName,BeanDefinition beanDefinition) {
  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;
}

参考B站视频:链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值