diff --git a/README.md b/README.md
index af8032b..cf7f7b2 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
#
mini-spring
[](https://github.com/DerekYRC/mini-spring)
[](https://www.apache.org/licenses/LICENSE-2.0.html)
+[](https://img.shields.io/github/stars/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
[](https://github.com/DerekYRC/mini-spring)
[](https://www.apache.org/licenses/LICENSE-2.0.html)
+[](https://img.shields.io/github/stars/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