[关闭]
@joshsulin 2018-08-23T02:35:14.000000Z 字数 3979 阅读 1023

Spring 手动装配实现原理

Spring手动装配


1、前言

最近在学习SpringBoot自动装配的实现原理, 通过一段时间的探索, 发现SpringBoot的自动装配是源于SpringFramework的手动装配。所以先对SpringFramework的手动装配做一个总结。

2、回顾SpringFramework手动装配如何使用?

在我们刚开始接触Spring的时候, 要定义bean的话需要在xml中编写,比如:
java
<bean id="myBean" class="your.pkg.YourClass"/>

后来发现如果bean比较多,需要写很多的bean标签,太麻烦了。于是出现了一个component-scan注解。这个注解直接指定包名就可以,它会去扫描这个包下所有的class,然后判断是否解析:
java
<context:component-scan base-package="your.pkg"/>

再后来,由于注解Annotation的流行, 出现了@ComponentScan注解,作用跟component-scan标签一样:
java
@ComponentScan(basePackages = {"your.pkg", "other.pkg"})
public class Application { ... }

功能其实很简单, context:component-scan 或者 @ComponentScan 告诉Spring哪个packages下的用注解(@Component、@Service、@Controller、@Repository等等)标识的类会被spring自动扫描并且装入IOC容器。

3、假如需要我们自己实现该功能需要如何实现? 功能关键点有哪些?每个功能关键点如何实现?

  • a、根据basePackages, 找到包含包名的绝对目录。
  • b、根据绝对目录, 罗列出该目录下所有的Class文件。
  • c、解析该Class文件, 判断是否包含特定的注解(@Component、@Service、@Controller、@Repository等等)的类。
  • d、将满足条件的类, 装入IOC容器。

3.1 如何根据basePackages, 找到包含包名的绝对目录。

需要完成: basePackages的值为 -> com.josh.demo.service 找到 /Users/project/java/xxx_project/target/classes/com/josh/demo/service ?

  1. String basePackage = "com.josh.toefl.service";
  2. // 转化成 com/josh/toefl/service/
  3. String packageSearchPath = StringUtils.replace(basePackage, ".", "/") + "/";
  4. Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(packageSearchPath);
  5. while (resources.hasMoreElements()) {
  6. URL url = resources.nextElement();
  7. System.out.println(url.getPath());
  8. }

3.2 根据找到包含包名的绝对目录, 如何罗列出该目录下所有的Class文件.

  1. String path = "/Users/project/java/xxx_project/target/classes/com/josh/toefl/service";
  2. File dir = new File(path);
  3. File[] files = dir.listFiles();
  4. for (File file : files) {
  5. System.out.println(file.getAbsoluteFile());
  6. }

3.3 解析该Class文件, 判断是否包含特定的注解(@Component、@Service、@Controller、@Repository等等)的类。

  1. String fileName = "/Users/project/java/xxx_project/target/classes/com/josh/toefl/service/TfUserService.class";
  2. Resource resource = new FileSystemResource(fileName);
  3. SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();
  4. MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader(resource);
  5. AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
  6. String annotationTypeName = "org.springframework.stereotype.Component";
  7. // 判断是否直接包含Component注解或者包含Component元注解
  8. if (annotationMetadata.hasAnnotation(annotationTypeName) || annotationMetadata.hasMetaAnnotation(annotationTypeName)) {
  9. System.out.println("满足装配进IOC容器条件");
  10. } else {
  11. System.out.println("不满足装配进IOC容器条件");
  12. }

3.4 将满足条件的类, 装入IOC容器。

  1. String fileName = "/Users/project/java/xxx_project/target/classes/com/josh/toefl/service/TfUserService.class";
  2. Resource resource = new FileSystemResource(fileName);
  3. SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();
  4. MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader(resource);
  5. ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
  6. sbd.setResource(resource);
  7. sbd.setSource(resource);
  8. // sbd 是根据class的metadata构建的 BeanDefinition, 中间还需要补充一些BeanDefinition, 然后通过 registerBeanDefinition 注入到IOC容器

4、Spring 中该功能是如何实现的?

以下源码以 context:component-scan xml 元素为例, 看看Spring中是如何实现的。

4.1 主要方法入口(标签解析)
/org/springframework/spring-context/4.3.8.RELEASE/spring-context-4.3.8.RELEASE-sources.jar!/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java

  1. @Override
  2. public BeanDefinition parse(Element element, ParserContext parserContext) {
  3. // 获取到 base-package 的包名
  4. String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
  5. // 对包名进行处理
  6. basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
  7. // 对多个包名进行分隔, 转化成数组
  8. String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
  9. ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  10. // Actually scan for bean definitions and register them.
  11. ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
  12. // 扫描包对应目录下的所有文件, 将符合条件的类转化成BeanDefinition
  13. Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
  14. registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
  15. return null;
  16. }

大家可以在这个方法里打一个断点, 逐步跟进。相信大家可以一探究竟。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注