在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站视频:链接