Skip to content

Commit 0c2e5e4

Browse files
committed
[Expanding][Add @component @scope][feat] 新增兩的Annotation
1 parent d9221b0 commit 0c2e5e4

File tree

10 files changed

+264
-70
lines changed

10 files changed

+264
-70
lines changed

pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@
5252
<version>3.24.2</version>
5353
<scope>test</scope>
5454
</dependency>
55+
<dependency>
56+
<groupId>org.dom4j</groupId>
57+
<artifactId>dom4j</artifactId>
58+
<version>2.1.3</version>
59+
</dependency>
5560

5661
</dependencies>
5762
</project>

src/main/java/org/m1a2st/beans/factory/config/BeanDefinition.java

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import org.m1a2st.beans.PropertyValues;
44

5+
import java.util.Objects;
6+
57
/**
68
* BeanDefinition實例保存bean的信息,包括class類型、
79
* 方法構造參數、是否為單例等,
@@ -80,4 +82,17 @@ public boolean isSingleton() {
8082
public boolean isPrototype() {
8183
return prototype;
8284
}
85+
86+
@Override
87+
public boolean equals(Object o) {
88+
if (this == o) return true;
89+
if (o == null || getClass() != o.getClass()) return false;
90+
BeanDefinition that = (BeanDefinition) o;
91+
return beanClass.equals(that.beanClass);
92+
}
93+
94+
@Override
95+
public int hashCode() {
96+
return Objects.hash(beanClass);
97+
}
8398
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
package org.m1a2st.beans.factory.xml;
22

33
import cn.hutool.core.util.StrUtil;
4-
import cn.hutool.core.util.XmlUtil;
4+
import org.dom4j.Document;
5+
import org.dom4j.DocumentException;
6+
import org.dom4j.Element;
7+
import org.dom4j.io.SAXReader;
58
import org.m1a2st.beans.BeansException;
69
import org.m1a2st.beans.PropertyValue;
710
import org.m1a2st.beans.factory.config.BeanDefinition;
811
import org.m1a2st.beans.factory.config.BeanReference;
912
import org.m1a2st.beans.factory.support.AbstractBeanDefinitionReader;
1013
import org.m1a2st.beans.factory.support.BeanDefinitionRegistry;
14+
import org.m1a2st.context.annotation.ClassPathBeanDefinitionScanner;
1115
import org.m1a2st.core.io.Resource;
1216
import org.m1a2st.core.io.ResourceLoader;
13-
import org.w3c.dom.Document;
14-
import org.w3c.dom.Element;
15-
import org.w3c.dom.NodeList;
1617

1718
import java.io.IOException;
1819
import java.io.InputStream;
20+
import java.util.List;
1921

2022
/**
2123
* @Author m1a2st
@@ -34,6 +36,8 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
3436
public static final String INIT_METHOD_ATTRIBUTE = "init-method";
3537
public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
3638
public static final String SCOPE_ATTRIBUTE = "scope";
39+
public static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
40+
public static final String COMPONENT_SCAN_ELEMENT = "component-scan";
3741

3842
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
3943
super(registry);
@@ -54,81 +58,97 @@ public void loadBeanDefinitions(String location) throws BeansException {
5458
public void loadBeanDefinitions(Resource resource) throws BeansException {
5559
try {
5660
try (InputStream inputStream = resource.getInputStream()) {
57-
doLoadBeanDefinitions(inputStream);
61+
try {
62+
doLoadBeanDefinitions(inputStream);
63+
} catch (DocumentException e) {
64+
throw new RuntimeException(e);
65+
}
5866
}
5967
} catch (IOException e) {
6068
throw new BeansException("IOException parsing XML document from " + resource, e);
6169
}
6270

6371
}
6472

65-
private void doLoadBeanDefinitions(InputStream inputStream) throws BeansException {
66-
Document document = XmlUtil.readXML(inputStream);
67-
Element root = document.getDocumentElement();
68-
NodeList childNodes = root.getChildNodes();
69-
for (int i = 0; i < childNodes.getLength(); i++) {
70-
if (childNodes.item(i) instanceof Element) {
71-
if (BEAN_ELEMENT.equals(childNodes.item(i).getNodeName())) {
72-
// 解析 bean標籤
73-
Element bean = (Element) childNodes.item(i);
74-
String id = bean.getAttribute(ID_ATTRIBUTE);
75-
String name = bean.getAttribute(NAME_ATTRIBUTE);
76-
String className = bean.getAttribute(CLASS_ATTRIBUTE);
77-
String initMethodAttribute = bean.getAttribute(INIT_METHOD_ATTRIBUTE);
78-
String destroyMethodAttribute = bean.getAttribute(DESTROY_METHOD_ATTRIBUTE);
79-
String beanScope = bean.getAttribute(SCOPE_ATTRIBUTE);
80-
// 加載bean的定義信息
81-
Class<?> clazz;
82-
try {
83-
clazz = Class.forName(className);
84-
} catch (ClassNotFoundException e) {
85-
throw new BeansException("Cannot find class [" + className + "]");
86-
}
87-
// id優先於name
88-
String beanName = StrUtil.isNotEmpty(id) ? id : name;
89-
if (StrUtil.isEmpty(beanName)) {
90-
// 如果id和name都為空,將類名的第一個字母轉為小寫後作為bean的名稱
91-
beanName = StrUtil.lowerFirst(clazz.getSimpleName());
92-
}
93-
// 創建bean定義信息
94-
BeanDefinition beanDefinition = new BeanDefinition(clazz);
95-
beanDefinition.setInitMethodName(initMethodAttribute);
96-
beanDefinition.setDestroyMethodName(destroyMethodAttribute);
97-
if (StrUtil.isNotEmpty(beanScope)) {
98-
beanDefinition.setScope(beanScope);
99-
}
100-
// 解析並設置 bean的屬性
101-
for (int j = 0; j < bean.getChildNodes().getLength(); j++) {
102-
if (bean.getChildNodes().item(j) instanceof Element) {
103-
if (PROPERTY_ELEMENT.equals(bean.getChildNodes().item(j).getNodeName())) {
104-
// 判斷是否為property標籤
105-
Element property = (Element) bean.getChildNodes().item(j);
106-
String nameAttribute = property.getAttribute(NAME_ATTRIBUTE);
107-
String valueAttribute = property.getAttribute(VALUE_ATTRIBUTE);
108-
String refAttribute = property.getAttribute(REF_ATTRIBUTE);
109-
110-
if (StrUtil.isEmpty(nameAttribute)) {
111-
throw new BeansException("The name attribute cannot be null or empty");
112-
}
113-
// 解析value和ref,如果都有值,則優先取ref
114-
Object value = valueAttribute;
115-
if (StrUtil.isNotEmpty(refAttribute)) {
116-
value = new BeanReference(refAttribute);
117-
}
118-
// 將屬性設置到bean定義信息中
119-
PropertyValue propertyValue = new PropertyValue(nameAttribute, value);
120-
beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
121-
}
122-
}
123-
}
124-
// beanName不能重複
125-
if (getRegistry().containsBeanDefinition(beanName)) {
126-
throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
127-
}
128-
129-
getRegistry().registerBeanDefinition(beanName, beanDefinition);
73+
protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentException {
74+
SAXReader reader = new SAXReader();
75+
Document document = reader.read(inputStream);
76+
77+
Element root = document.getRootElement();
78+
79+
// 解析context:component-scan標簽並掃描指定包中的類,提取類信息,組裝成BeanDefinition
80+
Element componentScan = root.element(COMPONENT_SCAN_ELEMENT);
81+
if (componentScan != null) {
82+
String scanPath = componentScan.attributeValue(BASE_PACKAGE_ATTRIBUTE);
83+
if (StrUtil.isEmpty(scanPath)) {
84+
throw new BeansException("The value of base-package attribute can not be empty or null");
85+
}
86+
scanPackage(scanPath);
87+
}
88+
89+
List<Element> beanList = root.elements(BEAN_ELEMENT);
90+
for (Element bean : beanList) {
91+
String beanId = bean.attributeValue(ID_ATTRIBUTE);
92+
String beanName = bean.attributeValue(NAME_ATTRIBUTE);
93+
String className = bean.attributeValue(CLASS_ATTRIBUTE);
94+
String initMethodName = bean.attributeValue(INIT_METHOD_ATTRIBUTE);
95+
String destroyMethodName = bean.attributeValue(DESTROY_METHOD_ATTRIBUTE);
96+
String beanScope = bean.attributeValue(SCOPE_ATTRIBUTE);
97+
98+
Class<?> clazz;
99+
try {
100+
clazz = Class.forName(className);
101+
} catch (ClassNotFoundException e) {
102+
throw new BeansException("Cannot find class [" + className + "]");
103+
}
104+
//id優先於name
105+
beanName = StrUtil.isNotEmpty(beanId) ? beanId : beanName;
106+
if (StrUtil.isEmpty(beanName)) {
107+
// 如果没有指定beanName,則將beanName設置為類名首字母小寫
108+
beanName = StrUtil.lowerFirst(clazz.getSimpleName());
109+
}
110+
111+
BeanDefinition beanDefinition = new BeanDefinition(clazz);
112+
beanDefinition.setInitMethodName(initMethodName);
113+
beanDefinition.setDestroyMethodName(destroyMethodName);
114+
if (StrUtil.isNotEmpty(beanScope)) {
115+
beanDefinition.setScope(beanScope);
116+
}
117+
118+
List<Element> propertyList = bean.elements(PROPERTY_ELEMENT);
119+
for (Element property : propertyList) {
120+
String propertyNameAttribute = property.attributeValue(NAME_ATTRIBUTE);
121+
String propertyValueAttribute = property.attributeValue(VALUE_ATTRIBUTE);
122+
String propertyRefAttribute = property.attributeValue(REF_ATTRIBUTE);
123+
124+
if (StrUtil.isEmpty(propertyNameAttribute)) {
125+
throw new BeansException("The name attribute cannot be null or empty");
130126
}
127+
128+
Object value = propertyValueAttribute;
129+
if (StrUtil.isNotEmpty(propertyRefAttribute)) {
130+
value = new BeanReference(propertyRefAttribute);
131+
}
132+
PropertyValue propertyValue = new PropertyValue(propertyNameAttribute, value);
133+
beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
134+
}
135+
if (getRegistry().containsBeanDefinition(beanName)) {
136+
//beanName不能重名
137+
throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
131138
}
139+
//注册BeanDefinition
140+
getRegistry().registerBeanDefinition(beanName, beanDefinition);
132141
}
133142
}
143+
144+
/**
145+
* 掃描指定包下的類,提取類信息,組裝成BeanDefinition
146+
*
147+
* @param scanPath 指定包路徑
148+
*/
149+
private void scanPackage(String scanPath) {
150+
String[] basePackages = scanPath.split(",");
151+
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry());
152+
scanner.doScan(basePackages);
153+
}
134154
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.m1a2st.context.annotation;
2+
3+
import cn.hutool.core.util.StrUtil;
4+
import org.m1a2st.beans.factory.config.BeanDefinition;
5+
import org.m1a2st.beans.factory.support.BeanDefinitionRegistry;
6+
import org.m1a2st.stereotype.Component;
7+
8+
import java.util.Set;
9+
10+
/**
11+
* @Author m1a2st
12+
* @Date 2023/7/19
13+
* @Version v1.0
14+
*/
15+
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
16+
17+
private final BeanDefinitionRegistry registry;
18+
19+
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
20+
this.registry = registry;
21+
}
22+
23+
public void doScan(String... basePackages) {
24+
for (String basePackage : basePackages) {
25+
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
26+
for (BeanDefinition candidate : candidates) {
27+
// 解析Bean的作用域
28+
String beanScope = resolveScope(candidate);
29+
// 將Bean註冊到BeanDefinition中
30+
if (StrUtil.isNotEmpty(beanScope)) {
31+
candidate.setScope(beanScope);
32+
}
33+
// 生成Bean的名稱
34+
String beanName = determineBeanName(candidate);
35+
// 註冊BeanDefinition
36+
registry.registerBeanDefinition(beanName, candidate);
37+
}
38+
}
39+
}
40+
41+
private String resolveScope(BeanDefinition beanDefinition) {
42+
Class<?> beanClass = beanDefinition.getBeanClass();
43+
Scope scope = beanClass.getAnnotation(Scope.class);
44+
if (scope != null) {
45+
return scope.value();
46+
}
47+
return StrUtil.EMPTY;
48+
}
49+
50+
private String determineBeanName(BeanDefinition beanDefinition) {
51+
Class<?> beanClass = beanDefinition.getBeanClass();
52+
Component component = beanClass.getAnnotation(Component.class);
53+
String value = component.value();
54+
if (StrUtil.isEmpty(value)) {
55+
return StrUtil.lowerFirst(beanClass.getSimpleName());
56+
}
57+
return value;
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.m1a2st.context.annotation;
2+
3+
import cn.hutool.core.util.ClassUtil;
4+
import org.m1a2st.beans.factory.config.BeanDefinition;
5+
import org.m1a2st.stereotype.Component;
6+
7+
import java.util.LinkedHashSet;
8+
import java.util.Set;
9+
10+
/**
11+
* @Author m1a2st
12+
* @Date 2023/7/19
13+
* @Version v1.0
14+
*/
15+
public class ClassPathScanningCandidateComponentProvider {
16+
17+
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
18+
LinkedHashSet<BeanDefinition> candidates = new LinkedHashSet<>();
19+
// 掃描有org.m1a2st.stereotype.Component註解的類
20+
ClassUtil.scanPackageByAnnotation(basePackage, Component.class)
21+
.forEach(clazz -> {
22+
candidates.add(new BeanDefinition(clazz));
23+
});
24+
return candidates;
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.m1a2st.context.annotation;
2+
3+
import java.lang.annotation.*;
4+
5+
/**
6+
* @Author m1a2st
7+
* @Date 2023/7/19
8+
* @Version v1.0
9+
*/
10+
@Target({ElementType.TYPE, ElementType.METHOD})
11+
@Retention(RetentionPolicy.RUNTIME)
12+
@Documented
13+
public @interface Scope {
14+
15+
String value() default "singleton";
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.m1a2st.stereotype;
2+
3+
import java.lang.annotation.*;
4+
5+
/**
6+
* @Author m1a2st
7+
* @Date 2023/7/19
8+
* @Version v1.0
9+
*/
10+
@Target(ElementType.TYPE)
11+
@Retention(RetentionPolicy.RUNTIME)
12+
@Documented
13+
public @interface Component {
14+
15+
String value() default "";
16+
}

src/main/test/java/org/m1a2st/beans/bean/Car.java

+3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package org.m1a2st.beans.bean;
22

3+
import org.m1a2st.stereotype.Component;
4+
35
/**
46
* @Author m1a2st
57
* @Date 2023/7/1
68
* @Version v1.0
79
*/
10+
@Component
811
public class Car {
912

1013
private String brand;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.m1a2st.beans.ioc;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.Test;
5+
import org.m1a2st.beans.bean.Car;
6+
import org.m1a2st.context.support.ClassPathXmlApplicationContext;
7+
8+
/**
9+
* @Author m1a2st
10+
* @Date 2023/7/19
11+
* @Version v1.0
12+
*/
13+
public class PackageScanTest {
14+
15+
@Test
16+
public void testScanPackage() {
17+
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:package-scan.xml");
18+
19+
Car car = applicationContext.getBean("car", Car.class);
20+
Assertions.assertNotNull(car);
21+
}
22+
}

0 commit comments

Comments
 (0)