spring源码之模拟扫描器

之前写Junit测试类的时候,我们都会给spring容器手动注入一个配置类,里面会用@ComponentScan来告诉spring需要扫描的路径。如下图:
在这里插入图片描述
那么spring就是通过这个配置类的注解,拿到我们定义的路径,然后从电脑中的绝对路径读取到.class文件进行解析。
大概流程如下:

  1. ConfigrarionClassPostProcessor
  2. 获取包名
  3. 得到路径下的所有文件
  4. 通过ASM的方式读取字节码信息,生成类
  5. 验证是否符合规则,生成beanDefinition
  6. 放入map中

我们可以通过代码模拟一下这个流程

public class MyScaner {

	File f = new File(this.getClass().getResource("/").getPath());
	public List<String> listName = new ArrayList<>();
	public Map<String, AbstractBeanDefinition> map = new HashMap<>();


	//这个方法一旦完成,就是完成了spring的扫描
	//listName中存了左右符合规则的bean名字
	//map中存了所有beanName以及对应的beanDefinition
	public void scan(String packageName) throws ClassNotFoundException {

		System.out.println("packageName = " + packageName);
		String scanPath = "";
		//获取当前类的存放绝对路径
		//  /Users/xxxx/Downloads/idea/spring-framework-5.2.x/spring-example/src/main/java/com/spring/scan
		//  /Users/shang/Downloads/idea/spring-framework-5.2.x/spring-example/out/test/classes/com/spring/scan/bean/A.class
		//  /Users/shang/Downloads/idea/spring-framework-5.2.x/spring-example/out/test/classes/com/spring/scan/bean
		String rootPath = f.getPath();
		System.out.println("rootPath = " + rootPath);

		//传入的包路径将. 替换为\
		//com.spring.scan --- com\spring\scan
		scanPath = packageName.replaceAll("\\.", "/");
		System.out.println("scanPath = " + scanPath);

		//拼接绝对路径
		rootPath = rootPath+ "/"+ scanPath;
		System.out.println("rootPath = " + rootPath);

		//取出文件夹下的文件,这里只是简单取出了,没有处理文件夹中的文件夹
		File rootDir = new File(rootPath);
		String[] list = rootDir.list();
		if (list == null || list.length == 0) {
			System.out.println("bean null");
			return;
		}
		for (String s : list) {
			s = s.replaceAll(".class", "");
			System.out.println("s = " + s);

			String beanName = s.toLowerCase();
			System.out.println("beanName = " + beanName);

			s = packageName + "." + s;
			System.out.println("s = " + s);


			//通过反射的方式获取bean
			//spring中是通过ASM字节码的方式获取,优点是不会提前执行构造方法
			Class<?> clazz = Class.forName(s);

			//如果有Component注解的话才处理
			if (clazz.isAnnotationPresent(Component.class)) {
				GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
				beanDefinition.setBeanClass(clazz);
				
				//如果自定义了bean的作用域的话就放入这个组件中
				if(clazz.isAnnotationPresent(Scope.class)){
					beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());
				}

				map.put(beanName, beanDefinition);
				listName.add(beanName);
			}
		}

	}

}



public class ScanBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {


	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		try{
			MyScaner myScaner = new MyScaner();
			myScaner.scan("com.spring.scan.bean");
			//

			List<String> listName = myScaner.listName;
			Map<String, AbstractBeanDefinition> map = myScaner.map;
			for (String s : listName) {
				registry.registerBeanDefinition(s, map.get(s));

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

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

	}
}

//注意这三个类,因为是用Junit Test运行的,所以这三个类也要跟测试类在一块
@Component
public class A {

	@PostConstruct
	public void init() {
		System.out.println("A init");
	}
}

public class B {

	@PostConstruct
	public void init() {
		System.out.println("B init");
	}
}

@Component
public class C {

	@PostConstruct
	public void init() {
		System.out.println("C init");
	}
}

public class ScanTest {

	@Test
	public void defaultScanTest() {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.addBeanFactoryPostProcessor(new ScanBeanDefinitionRegistryPostProcessor());
		context.refresh();
	}
}

这个测试的方法与spring不一样的是第4步。

测试方法是通过反射获取类对象;而spring是通过ASM方式获取获取的,这个我也不懂,是一个跟高深的学问。
只是知道他的优势在于,不会提前执行static代码块。
因为如果用反射的方式获取的话,就直接把类加载到JVM了,如果类中有static代码块并且还关联了其他类,那不就要报错了吗。而通过ASM获取的话就不会,只是获取这个文件而已。
这就是spring优秀的地方

PS:
spring的扫描注解什么情况不会生效。
在正常的扫描过程中,如果扫描到的类中,有ComponentScan注解的话,也会读取,然后按照配置继续扫描
如下:

@ComponentScan("com.spring.other")
public class A {

	@PostConstruct
	public void init() {
		System.out.println("A init");
	}
}

但是,这个类如果是通过BeanDefinitionPostProcessor类注册进来的话,ComponentScan就不会生效了,因为扫描的工作在执行BeanDefinitionPostProcessor回调方法之前就已经完成了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木小同

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值