diff --git a/README.md b/README.md index af8032b..cf7f7b2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # mini-spring [![Build Status](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/DerekYRC/mini-spring) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) +[![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) ## About * [中文版](./README_CN.md) @@ -38,9 +40,9 @@ If this project can help you, please give a **STAR, thank you!!!** #### Expanding * [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) -* [Type conversion](#类型转换) * [Package scan](#包扫描) -* [Autowired annotation](#基于注解的依赖注入Autowired) +* [@Autowired and @Value annotation](#基于注解@Autowired和@Value的依赖注入) +* [Type conversion](#类型转换) #### Advanced * [Solve the problem of circular dependencies](#解决循环依赖问题) @@ -48,6 +50,12 @@ If this project can help you, please give a **STAR, thank you!!!** ## Usage Each function point corresponds to a branch. Switch to the branch corresponding to the function point to see the new function. The incremental change point is described in the [changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) file. +## Contributing +Any contributions you make are greatly appreciated. + +## Contact +Please feel free to ask me any questions related to mini-spring and other technologies. My email is **15521077528@163.com**. + ## Reference - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) - [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) diff --git a/README_CN.md b/README_CN.md index 42c344b..0ef4cc5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,6 +1,8 @@ # mini-spring [![Build Status](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/DerekYRC/mini-spring) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) +[![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) ## 关于 * [English version](./README.md) @@ -38,9 +40,9 @@ #### 扩展篇 * [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) -* [类型转换](#类型转换) * [包扫描](#包扫描) -* [基于注解的依赖注入Autowired](#基于注解的依赖注入Autowired) +* [基于注解@Autowired和@Value的依赖注入](#基于注解@Autowired和@Value的依赖注入) +* [类型转换](#类型转换) #### 高级篇 * [解决循环依赖问题](#解决循环依赖问题) @@ -48,6 +50,13 @@ ## 使用方法 每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。 +## 贡献 +欢迎Pull Request + +## 联系我 +欢迎探讨跟mini-spring和其他技术相关的问题,个人邮箱:**15521077528@163.com** + + ## 参考 - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) - [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) diff --git a/changelog.md b/changelog.md index 3b42239..c8d815c 100644 --- a/changelog.md +++ b/changelog.md @@ -976,7 +976,47 @@ public class PropertyPlaceholderConfigurerTest { } ``` +## 包扫描 +> 分支:package-scan +结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。 + +在XmlBeanDefinitionReader中解析**``````**标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。 + +测试: +``` +@Component +public class Car { + +} +``` +package-scan.xml +``` + + + + + + +``` +``` +public class PackageScanTest { + + @Test + public void testScanPackage() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:package-scan.xml"); + + Car car = applicationContext.getBean("car", Car.class); + assertThat(car).isNotNull(); + } +} +``` diff --git a/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java b/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java index 66a08c8..6719772 100644 --- a/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java +++ b/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java @@ -2,6 +2,8 @@ import org.springframework.beans.PropertyValues; +import java.util.Objects; + /** * BeanDefinition实例保存bean的信息,包括class类型、方法构造参数、bean属性、bean的scope等,此处简化只包含class类型和bean属性 * @@ -82,4 +84,17 @@ public String getDestroyMethodName() { public void setDestroyMethodName(String destroyMethodName) { this.destroyMethodName = destroyMethodName; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BeanDefinition that = (BeanDefinition) o; + return beanClass.equals(that.beanClass); + } + + @Override + public int hashCode() { + return Objects.hash(beanClass); + } } diff --git a/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java b/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java index 91c3a00..8882ddf 100644 --- a/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java +++ b/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -37,6 +38,8 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { public static final String INIT_METHOD_ATTRIBUTE = "init-method"; public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method"; public static final String SCOPE_ATTRIBUTE = "scope"; + public static final String BASE_PACKAGE_ATTRIBUTE = "base-package"; + public static final String COMPONENT_SCAN_ELEMENT = "component-scan"; public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { super(registry); @@ -71,8 +74,19 @@ protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentExc SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); - Element beans = document.getRootElement(); - List beanList = beans.elements(BEAN_ELEMENT); + Element root = document.getRootElement(); + + //解析context:component-scan标签并扫描指定包中的类,提取类信息,组装成BeanDefinition + Element componentScan = root.element(COMPONENT_SCAN_ELEMENT); + if (componentScan != null) { + String scanPath = componentScan.attributeValue(BASE_PACKAGE_ATTRIBUTE); + if (StrUtil.isEmpty(scanPath)) { + throw new BeansException("The value of base-package attribute can not be empty or null"); + } + scanPackage(scanPath); + } + + List beanList = root.elements(BEAN_ELEMENT); for (Element bean : beanList) { String beanId = bean.attributeValue(ID_ATTRIBUTE); String beanName = bean.attributeValue(NAME_ATTRIBUTE); @@ -126,4 +140,15 @@ protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentExc getRegistry().registerBeanDefinition(beanName, beanDefinition); } } + + /** + * 扫描注解Component的类,提取信息,组装成BeanDefinition + * + * @param scanPath + */ + private void scanPackage(String scanPath) { + String[] basePackages = StrUtil.splitToArray(scanPath, ','); + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry()); + scanner.doScan(basePackages); + } } diff --git a/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java new file mode 100644 index 0000000..4c7d03a --- /dev/null +++ b/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -0,0 +1,71 @@ +package org.springframework.context.annotation; + +import cn.hutool.core.util.StrUtil; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.stereotype.Component; + +import java.util.Set; + +/** + * @author derekyi + * @date 2020/12/26 + */ +public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { + + private BeanDefinitionRegistry registry; + + public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) { + this.registry = registry; + } + + public void doScan(String... basePackages) { + for (String basePackage : basePackages) { + Set candidates = findCandidateComponents(basePackage); + for (BeanDefinition candidate : candidates) { + // 解析bean的作用域 + String beanScope = resolveBeanScope(candidate); + if (StrUtil.isNotEmpty(beanScope)) { + candidate.setScope(beanScope); + } + //生成bean的名称 + String beanName = determineBeanName(candidate); + //注册BeanDefinition + registry.registerBeanDefinition(beanName, candidate); + } + } + } + + /** + * 获取bean的作用域 + * + * @param beanDefinition + * @return + */ + private String resolveBeanScope(BeanDefinition beanDefinition) { + Class beanClass = beanDefinition.getBeanClass(); + Scope scope = beanClass.getAnnotation(Scope.class); + if (scope != null) { + return scope.value(); + } + + return StrUtil.EMPTY; + } + + + /** + * 生成bean的名称 + * + * @param beanDefinition + * @return + */ + private String determineBeanName(BeanDefinition beanDefinition) { + Class beanClass = beanDefinition.getBeanClass(); + Component component = beanClass.getAnnotation(Component.class); + String value = component.value(); + if (StrUtil.isEmpty(value)) { + value = StrUtil.lowerFirst(beanClass.getSimpleName()); + } + return value; + } +} diff --git a/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java new file mode 100644 index 0000000..e66567c --- /dev/null +++ b/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -0,0 +1,26 @@ +package org.springframework.context.annotation; + +import cn.hutool.core.util.ClassUtil; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author derekyi + * @date 2020/12/26 + */ +public class ClassPathScanningCandidateComponentProvider { + + public Set findCandidateComponents(String basePackage) { + Set candidates = new LinkedHashSet(); + // 扫描有org.springframework.stereotype.Component注解的类 + Set> classes = ClassUtil.scanPackageByAnnotation(basePackage, Component.class); + for (Class clazz : classes) { + BeanDefinition beanDefinition = new BeanDefinition(clazz); + candidates.add(beanDefinition); + } + return candidates; + } +} diff --git a/src/main/java/org/springframework/context/annotation/Scope.java b/src/main/java/org/springframework/context/annotation/Scope.java new file mode 100644 index 0000000..20bc7f7 --- /dev/null +++ b/src/main/java/org/springframework/context/annotation/Scope.java @@ -0,0 +1,15 @@ +package org.springframework.context.annotation; + +import java.lang.annotation.*; + +/** + * @author derekyi + * @date 2020/12/26 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Scope { + + String value() default "singleton"; +} diff --git a/src/main/java/org/springframework/stereotype/Component.java b/src/main/java/org/springframework/stereotype/Component.java new file mode 100644 index 0000000..005c9b4 --- /dev/null +++ b/src/main/java/org/springframework/stereotype/Component.java @@ -0,0 +1,15 @@ +package org.springframework.stereotype; + +import java.lang.annotation.*; + +/** + * @author derekyi + * @date 2020/12/26 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Component { + + String value() default ""; +} \ No newline at end of file diff --git a/src/test/java/org/springframework/test/bean/Car.java b/src/test/java/org/springframework/test/bean/Car.java index 9406211..6e68b15 100644 --- a/src/test/java/org/springframework/test/bean/Car.java +++ b/src/test/java/org/springframework/test/bean/Car.java @@ -1,9 +1,12 @@ package org.springframework.test.bean; +import org.springframework.stereotype.Component; + /** * @author derekyi * @date 2020/11/24 */ +@Component public class Car { private String brand; diff --git a/src/test/java/org/springframework/test/ioc/PackageScanTest.java b/src/test/java/org/springframework/test/ioc/PackageScanTest.java new file mode 100644 index 0000000..40d8244 --- /dev/null +++ b/src/test/java/org/springframework/test/ioc/PackageScanTest.java @@ -0,0 +1,22 @@ +package org.springframework.test.ioc; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.bean.Car; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +/** + * @author derekyi + * @date 2020/12/26 + */ +public class PackageScanTest { + + @Test + public void testScanPackage() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:package-scan.xml"); + + Car car = applicationContext.getBean("car", Car.class); + assertThat(car).isNotNull(); + } +} diff --git a/src/test/resources/package-scan.xml b/src/test/resources/package-scan.xml new file mode 100644 index 0000000..98cf664 --- /dev/null +++ b/src/test/resources/package-scan.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file