Skip to content

Commit

Permalink
包扫描
Browse files Browse the repository at this point in the history
  • Loading branch information
DerekYRC committed Dec 26, 2020
1 parent 861fba5 commit aa3ec5a
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 6 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# <img src="assets/spring-framework.png" width="80" height="80"> 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)
Expand Down Expand Up @@ -38,16 +40,22 @@ 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](#解决循环依赖问题)

## 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 **[email protected]**.

## Reference
- [《Spring源码深度解析》](https://book.douban.com/subject/25866350/)
- [《精通Spring 4.x》](https://book.douban.com/subject/26952826/)
Expand Down
13 changes: 11 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# <img src="assets/spring-framework.png" width="80" height="80"> 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)
Expand Down Expand Up @@ -38,16 +40,23 @@

#### 扩展篇
* [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer)
* [类型转换](#类型转换)
* [包扫描](#包扫描)
* [基于注解的依赖注入Autowired](#基于注解的依赖注入Autowired)
* [基于注解@Autowired@Value的依赖注入](#基于注解@Autowired和@Value的依赖注入)
* [类型转换](#类型转换)

#### 高级篇
* [解决循环依赖问题](#解决循环依赖问题)

## 使用方法
每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。

## 贡献
欢迎Pull Request

## 联系我
欢迎探讨跟mini-spring和其他技术相关的问题,个人邮箱:**[email protected]**


## 参考
- [《Spring源码深度解析》](https://book.douban.com/subject/25866350/)
- [《精通Spring 4.x》](https://book.douban.com/subject/26952826/)
Expand Down
40 changes: 40 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,47 @@ public class PropertyPlaceholderConfigurerTest {
}
```

## 包扫描
> 分支:package-scan
结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。

在XmlBeanDefinitionReader中解析**```<context:component-scan />```**标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。

测试:
```
@Component
public class Car {
}
```
package-scan.xml
```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.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/context/spring-context-4.0.xsd">
<context:component-scan base-package="org.springframework.test.bean"/>
</beans>
```
```
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();
}
}
```



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.springframework.beans.PropertyValues;

import java.util.Objects;

/**
* BeanDefinition实例保存bean的信息,包括class类型、方法构造参数、bean属性、bean的scope等,此处简化只包含class类型和bean属性
*
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand Down Expand Up @@ -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<Element> 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<Element> beanList = root.elements(BEAN_ELEMENT);
for (Element bean : beanList) {
String beanId = bean.attributeValue(ID_ATTRIBUTE);
String beanName = bean.attributeValue(NAME_ATTRIBUTE);
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<BeanDefinition> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
// 扫描有org.springframework.stereotype.Component注解的类
Set<Class<?>> classes = ClassUtil.scanPackageByAnnotation(basePackage, Component.class);
for (Class<?> clazz : classes) {
BeanDefinition beanDefinition = new BeanDefinition(clazz);
candidates.add(beanDefinition);
}
return candidates;
}
}
15 changes: 15 additions & 0 deletions src/main/java/org/springframework/context/annotation/Scope.java
Original file line number Diff line number Diff line change
@@ -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";
}
15 changes: 15 additions & 0 deletions src/main/java/org/springframework/stereotype/Component.java
Original file line number Diff line number Diff line change
@@ -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 "";
}
3 changes: 3 additions & 0 deletions src/test/java/org/springframework/test/bean/Car.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
22 changes: 22 additions & 0 deletions src/test/java/org/springframework/test/ioc/PackageScanTest.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
12 changes: 12 additions & 0 deletions src/test/resources/package-scan.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.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/context/spring-context-4.0.xsd">

<context:component-scan base-package="org.springframework.test.bean"/>

</beans>

0 comments on commit aa3ec5a

Please sign in to comment.