1.spring 启动和扫描逻辑实现
文章目录
1.spring 启动和扫描逻辑实现
在spring 容器启动的过程中,会扫描指定包路径下的class 文件,判断当前类是否是一个bean 对象,如果是一个bean对象,将其注入到spring 容器中。
基础配置类
AppConfig 配置类的信息
@ComponentScan("com.zhouyu.service")
public class AppConfig {
}
ComponentScan
ComponentScan——主要定义包扫描路径——spring在容器启动的过程中,会通过读取配置类AppConfig类的class 对象获取 @ComponentScant定义的扫描路径
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value();
}
Component
表示这个类是一个bean
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
scope
定义Bean的作用域,是单例singleton还是原型 prototype
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value();
}
包扫描逻辑
新建 ZhouyuApplicationContext.java,作为spring容器的模拟实现
启动类
public class Test {
public static void main(String[] args) throws Exception {
ZhouyuApplicationContext applicationContext = new ZhouyuApplicationContext(AppConfig.class);
Object userUservice = applicationContext.getBean("userService");
Object userUservice1 = applicationContext.getBean("userService");
System.out.println(userUservice);
System.out.println(userUservice1);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T2UibU9Q-1658677523596)(…/学相伴笔记/04-SpringBoot/assets/微信截图_20220724103655.png)]
ZhouyuApplicationContext
- Class configClass 属性
- 带Class 参数的构造方法
在spring 启动类上通过传入一个Config配置类,作为Spring启动类的配置信息
scan——方法完成了包扫描逻辑,通过传入的AppConfig类的class对象,我们可以读取到AppConfig类上的@ComponentScan注解,其中定义了包扫描路径
// 解析配置类
// ComponentScan注解 --> 扫描路径 --> 扫描
ComponentScan annotations = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
String path = annotations.value();// 扫描路径 com.zhouyu.service
在Java中,需要加class加载到JVM中,不同的类是由不同的类加载器去完成加载的,Bootstrap类加载器加载jre/lib目录下的类,Ext类加载器加载jre/ext/lib目录下的类,App类加载器加载classpath目录下的类。其中classpath就是我们的项目路径。
private void scan(Class configClass) throws ClassNotFoundException {
// 解析配置类
// ComponentScan注解 --> 扫描路径 --> 扫描
ComponentScan annotations = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
String path = annotations.value();// 扫描路径 com.zhouyu.service
//扫描
// Bootstrap --> jre/lib
// Ext ------> jre/ext/lib
// App ------> classpath
ClassLoader classLoader = ZhouyuApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource("com/zhouyu/service");
File file = new File(resource.getFile());
if(file.isDirectory()){
File[] files = file.listFiles();
for(File f: files){
//D:\2022-04-22-program\zhouYu\spring01\target\classes\com\zhouyu\service\UserService.class -> com.zhouyu.service.UserService
String fileName = f.getAbsolutePath();
if(fileName.endsWith(".class")){
String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
className = className.replace("\\",".");
Class<?> clazz = classLoader.loadClass(className);
if(clazz.isAnnotationPresent(Component.class)){
// 表示当前这个类是一个Bean
// 解析类,判断当前bean是单例bean 还是prototype原型bean
// BeanDefinition
Component componentAnnotaion = clazz.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotaion.value();
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setClazz(clazz);
// 判断是不是单例模式
if(clazz.isAnnotationPresent(Scope.class)){
Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
}else {
beanDefinition.setScope("singleton");
}
beanDefinitionMap.put(beanName,beanDefinition);
}
}
}
}
}
BeanDefinition
对Bean 信息的定义
描述一个bean 的全部信息,比如 class 类型,bean 的作用域,是否懒加载
常用属性
- beanClass:表示Bean类型,未加载类的时候存放Bean的名字,加载类后存放Bean的class信息。
- scope:表示Bean的作用域,一般值为单例或者原型。
- lazyInit:表示Bean是否是懒加载。
- initMethodName:Bean初始化需要执行的方法。
- destroyMethodName:Bean销毁时要执行的方法。
- factoryBeanName:创建当前Bean的工厂。
BeanDefinition是如何存放的?
-
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
当Bean注册完成后,会在spring容器中把扫描到的beanDefinition存放到beanDefinitionMap中,方便后续的使用。
单例池 singletonObjects
spring中定义了单例池存放单例bean,在spring扫描逻辑scan方法执行完之后,spring会将非懒加载的单例bean注入到spring容器中
// 单例池
private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
getBean
单例直接从单例池获取
多列通过createBean方法创建bean
public Object getBean(String beanName) throws Exception{
if(beanDefinitionMap.containsKey(beanName)){
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if(beanDefinition.getScope().equals("singleton")){
Object o = singletonObjects.get(beanName);
return o;
} else {
// 创建 bean
Object bean = createBean(beanDefinition);
return bean;
}
}else {
throw new NullPointerException();
}
}