之前写Junit测试类的时候,我们都会给spring容器手动注入一个配置类,里面会用@ComponentScan来告诉spring需要扫描的路径。如下图: 那么spring就是通过这个配置类的注解,拿到我们定义的路径,然后从电脑中的绝对路径读取到.class文件进行解析。 大概流程如下:
- ConfigrarionClassPostProcessor
- 获取包名
- 得到路径下的所有文件
- 通过ASM的方式读取字节码信息,生成类
- 验证是否符合规则,生成beanDefinition
- 放入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<>();
public void scan(String packageName) throws ClassNotFoundException {
System.out.println("packageName = " + packageName);
String scanPath = "";
String rootPath = f.getPath();
System.out.println("rootPath = " + rootPath);
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);
Class<?> clazz = Class.forName(s);
if (clazz.isAnnotationPresent(Component.class)) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(clazz);
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 {
}
}
@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回调方法之前就已经完成了。
|