如何实现 Spring 的 @Component 注解
前言
实现步骤拆分
本文实现的注解虽然说不用再配置 XML 文件,但是有点需要明确的是指定扫描 Bean 的包还使用 XML 文件的方式配置的,只是指定 Bean 不再使用配置文件的方式。有前面两篇文章的基础后实现 @Component 注解主要分成以下几个步骤:
读取 XML 配置文件,解析出需要扫描的包路径
对解析后的包路径进行扫描然后读取标有 @Component 注解的类,创建出对应的 BeanDefinition
根据创建出来的 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
第二步是整个实现步骤中最为复杂和比较麻烦的一步,当面对一个任务比较复杂而且比较大时,可以对其进行适当的拆分为几个小步骤分别去实现,这里可以其再次拆分为如下几个小步骤:
扫描包路径下的字节码(.class )文件并转换为一个个 Resource 对象(其对于 Spring 框架来说是一种资源,在 Spring 中资源统一抽象为 Resource ,这里的字节码文件具体为 FileSystemResource)
读取转换好的 Resource 中的 @Component 注解
根据读取到的 @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);
}
}
}
...
}
上面的第一小步至此已经完成了,
CSDN-Ada助手: 恭喜你这篇博客进入【CSDN每天值得看】榜单,全部的排名请看 https://bbs.csdn.net/topics/616568476。
zuijianren: 这个示例还挺全的
码农BookSea: 写的很好,加油!
大家一起学编程(python): 大佬就是大佬,666
mghio: 感谢认可!