如何实现 Spring 的 @Component 注解

本文详细介绍了如何实现Spring框架中的@Component注解,包括读取XML配置文件获取扫描包路径,扫描包路径下的类并读取注解,以及根据注解创建BeanDefinition。文章通过ASM框架读取字节码,解析注解信息,并提供了ClassPathBeanDefinitionScanner类用于扫描和注册BeanDefinition到BeanFactory。下篇将介绍@Autowried注解的实现。
摘要由CSDN通过智能技术生成

前言

前面两篇文章(如何实现一个简易版的 Spring - 如何实现 Setter 注入如何实现一个简易版的 Spring - 如何实现 Constructor 注入)介绍的都是基于 XML 配置文件方式的实现,从 JDK 5 版本开始 Java 引入了注解支持,带来了极大的便利,Sprinng 也从 2.5 版本开始支持注解方式,使用注解方式我们只需加上相应的注解即可,不再需要去编写繁琐的 XML 配置文件,深受广大 Java 编程人员的喜爱。接下来一起看看如何实现 Spring 框架中最常用的两个注解(@Component、@Autowired),由于涉及到的内容比较多,会分为两篇文章进行介绍,本文先来介绍上半部分 — 如何实现 @Component 注解。

   

实现步骤拆分

本文实现的注解虽然说不用再配置 XML 文件,但是有点需要明确的是指定扫描 Bean 的包还使用 XML 文件的方式配置的,只是指定 Bean 不再使用配置文件的方式。有前面两篇文章的基础后实现 @Component 注解主要分成以下几个步骤:

  1. 读取 XML 配置文件,解析出需要扫描的包路径

  2. 对解析后的包路径进行扫描然后读取标有 @Component 注解的类,创建出对应的 BeanDefinition

  3. 根据创建出来的 BeanDefinition 创建对应的 Bean 实例

下面我们一步步来实现这几个步骤,最后去实现 @Component 注解:

   

读取 XML 配置文件,解析出需要扫描的包路径

假设有如下的 XML 配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.e3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/beans/spring-context.xsd">


    <context:scann-package base-package="cn.mghio.service.version4,cn.mghio.dao.version4" />


</beans>

我们期望的结果是解析出来的扫描包路径为:cn.mghio.service.version4、cn.mghio.dao.version4 。如果有仔细有了前面的文章后,这个其实就比较简单了,只需要修改读取 XML 配置文件的类 XmlBeanDefinitionReader 中的 loadBeanDefinition(Resource resource) 方法,判断当前的 namespace 是否为 context 即可,修改该方法如下:

public void loadBeanDefinition(Resource resource) {
  try (InputStream is = resource.getInputStream()) {
    SAXReader saxReader = new SAXReader();
    Document document = saxReader.read(is);
    Element root = document.getRootElement();  // <beans>
    Iterator<Element> iterator = root.elementIterator();
    while (iterator.hasNext()) {
      Element element = iterator.next();
      String namespaceUri = element.getNamespaceURI();
      if (this.isDefaultNamespace(namespaceUri)) {  // beans
        parseDefaultElement(element);
      } else if (this.isContextNamespace(namespaceUri)) {  // context
        parseComponentElement(element);
      }
    }
  } catch (DocumentException | IOException e) {
    throw new BeanDefinitionException("IOException parsing XML document:" + resource, e);
  }
}


private void parseComponentElement(Element element) {
  // 1. 从 XML 配置文件中获取需要的扫描的包路径
  String basePackages = element.attributeValue(BASE_PACKAGE_ATTRIBUTE);
  // TODO 2. 对包路径进行扫描然后读取标有 @Component 注解的类,创建出对应的 BeanDefinition
  ...
    
}


private boolean isContextNamespace(String namespaceUri) {
  // CONTEXT_NAMESPACE_URI = http://www.springframework.org/schema/context
  return (StringUtils.hasLength(namespaceUri) && CONTEXT_NAMESPACE_URI.equals(namespaceUri));
}


private boolean isDefaultNamespace(String namespaceUri) {
  // BEAN_NAMESPACE_URI = http://www.springframework.org/schema/beans
  return (StringUtils.hasLength(namespaceUri) && BEAN_NAMESPACE_URI.equals(namespaceUri));
}

第一个步骤就已经完成了,其实相对来说还是比较简单的,接下来看看第二步要如何实现。

   

对解析后的包路径进行扫描然后读取标有 @Component 注解的类,创建出对应的 BeanDefinition

第二步是整个实现步骤中最为复杂和比较麻烦的一步,当面对一个任务比较复杂而且比较大时,可以对其进行适当的拆分为几个小步骤分别去实现,这里可以其再次拆分为如下几个小步骤:

  1. 扫描包路径下的字节码(.class )文件并转换为一个个 Resource 对象(其对于 Spring 框架来说是一种资源,在 Spring 中资源统一抽象为 Resource ,这里的字节码文件具体为 FileSystemResource)

  2. 读取转换好的 Resource 中的 @Component 注解

  3. 根据读取到的 @Component 注解信息创建出对应的 BeanDefintion

   

① 扫描包路径下的字节码(.class )文件并转换为一个个 Resource 对象(其对于 Spring 框架来说是一种资源,在 Spring 中资源统一抽象为 Resource ,这里的字节码文件具体为 FileSystemResource)

第一小步主要是实现从一个指定的包路径下获取该包路径下对应的字节码文件并将其转化为 Resource 对象,将该类命名为 PackageResourceLoader,其提供一个主要方法是 Resource[] getResources(String basePackage) 用来将一个给定的包路径下的字节码文件转换为 Resource 数组,实现如下:

public class PackageResourceLoader {
  
    ...
  
    public Resource[] getResources(String basePackage) {
        Assert.notNull(basePackage, "basePackage must not be null");
        String location = ClassUtils.convertClassNameToResourcePath(basePackage);
        ClassLoader classLoader = getClassLoader();
        URL url = classLoader.getResource(location);
        Assert.notNull(url, "URL must not be null");
        File rootDir = new File(url.getFile());


        Set<File> matchingFile = retrieveMatchingFiles(rootDir);
        Resource[] result = new Resource[matchingFile.size()];
        int i = 0;
        for (File file : matchingFile) {
            result[i++] = new FileSystemResource(file);
        }
        return result;
    }


    private Set<File> retrieveMatchingFiles(File rootDir) {
        if (!rootDir.exists() || !rootDir.isDirectory() || !rootDir.canRead()) {
            return Collections.emptySet();
        }
        Set<File> result = new LinkedHashSet<>(8);
        doRetrieveMatchingFiles(rootDir, result);
        return result;
    }


    private void doRetrieveMatchingFiles(File dir, Set<File> result) {
        File[] dirContents = dir.listFiles();
        if (dirContents == null) {
            return;
        }


        for (File content : dirContents) {
            if (!content.isDirectory()) {
                result.add(content);
                continue;
            }
            if (content.canRead()) {
                doRetrieveMatchingFiles(content, result);
            }
        }
    }
  
  ...


}

上面的第一小步至此已经完成了,

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值