springIOC---2、手工简单实现基于annotation的IOC框架

序言

……上一篇中我们探讨了基于xml配置文件的方式如何实现一个IOC容器,然而实际情况是这种方式在现在的开发中应用场景已经不多了,如果你们公司还在使用这种方式那说明你们公司的技术是真的还挺落后的,在敏捷开发至上的时代,我们往往使用的是另外一种方式—基于annotation来实现IOC管理,springboot中就已经很少去使用xml文件的方式了,基本上都是采用注解的方式,下面我们就来看看,基于annotation我们该如何去实现一个IOC容器。

如何基于注解实现一个IOC容器

……这里我也不多做关于spring的相关介绍,大家可自行去官网了解相关内容,我们直接进入正题,承接上一篇文章,今天就来讲解一下第2种方式,通过annotation的方式来实现IOC容器。

代码结构

……整体的代码结构如下图,下面就逐一解析
图p1

**代码结构**

入口类TestMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hk.annospringioc;

public class TestMain {
public static void main(String[] args) {
MyAnnotationApplicationContext fac = new MyAnnotationApplicationContext("com.hk.annospringioc");
Boy bean1 = (Boy) fac.getBean("boy");
bean1.printAll();
Girl bean2 = (Girl) fac.getBean("girl");
bean2.printAll();
Person bean = (Person) fac.getBean("person");
Person beancopy = (Person) fac.getBean("person");
bean.printAll();
System.out.println(bean == beancopy ? "是同一个person" : "不是同一个person");
}
}

整个程序的main方法中我们new了一个MyAnnotationApplicationContext这样的一个对象,并传入了一个类似包路径的参数,这里我们管它叫包扫描根目录,springboot就有这个概念,默认在主函数同级目录,这个类也是整个注解IOC构建过程中的核心类,介绍该类之前,先说明两个接口
ApplicationContext用于IOC容器构建

1
2
3
4
5
package com.hk.springioc;

public interface ApplicationContext {
public void springIOC();
}

BeanFactory用于对象实例管理与获取

1
2
3
4
5
package com.hk.springioc;

public interface BeanFactory {
public Object getBean(String name);
}

MyAnnotationApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.hk.annospringioc;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class MyAnnotationApplicationContext implements BeanFactory,ApplicationContext {

private ConfigManager configManager;

private Map<String,AnnoBean> beanContainer;

private Map<String,Object> instanceContainer ;

public MyAnnotationApplicationContext(String path){
configManager = new ConfigManager();
instanceContainer = new HashMap<String,Object>();
beanContainer = configManager.ScanPackage(path);
springIOC();
}



//依賴注入的關鍵
@Override
public void springIOC() {
Set<Entry<String, AnnoBean>> entrySet = beanContainer.entrySet();
for(Entry<String, AnnoBean> entry : entrySet){
// String name = entry.getKey();
AnnoBean bean = entry.getValue();
Object instance = createBean(bean);
// instanceContainer.put(name, instance);
}
}

private Object createBean(AnnoBean bean) {
if(instanceContainer.get(bean.getName())!=null){
return instanceContainer.get(bean.getName());
}
String className = bean.getClassName();
List<Property> props = bean.getProps();
Object newInstance = null;
try {
Class<?> forName = Class.forName(className);
newInstance = forName.newInstance();
for(Property pro : props){
String name = pro.getName();
Field field = forName.getDeclaredField(name);
field.setAccessible(true);
if(pro.getRef()!=null){
String ref = pro.getRef();
Object existBean = getBean(ref);
if(existBean==null){
Object createBean = createBean(beanContainer.get(ref));
field.set(newInstance,createBean);
}else{
field.set(newInstance,existBean);
}
}else{
Class<?> type = field.getType();
if(type==int.class){
field.set(newInstance,Integer.parseInt(pro.getValue()));
}else{
field.set(newInstance,pro.getValue());
}

}
}
if(bean.getSingleton().equals("1")){
instanceContainer.put(bean.getName(), newInstance);
}

} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return newInstance;
}



@Override
public Object getBean(String name) {
return instanceContainer.get(name);
}

}

解释一下这个类主要做了什么:
1、new了一个ConfigManager对象
2、构建了一个HashMap类型的实例化容器instanceContainer
3、用第一步的ConfigManager对象对包扫描根目录下面的所有类进行扫描,获取一个Bean容器,这里的Bean就是annotation注解的相关bean信息,后面会有一个类进行封装
4、对Bean容器进行解析,构建最终的IOC实例容器,也就是第二步中的instanceContainer
下面就是我们相关自定义注解以及实体类中的注解表达方式

VO组装已经相关类和属相的注解信息

实体组装结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

封装Person信息,依赖注入Boy和Girl
package com.hk.annospringioc;


@MyComponent(value="person")
public class Person {

@MyAutoWired(value = "boy")
private Boy boy;
@MyAutoWired(value = "girl")
private Girl girl;

public void printAll(){
boy.printAll();
girl.printAll();
}
}


类封装girl信息
package com.hk.annospringioc;

@MyComponent("girl")
public class Girl {

@MyValue("lily")
private String name;
@MyValue("17")
private int age;

public void printAll(){
System.out.println(name+":"+age);
}
}


封装boy的信息
package com.hk.annospringioc;

@MyComponent("boy")
public class Boy {

@MyValue("lilei")
private String name;

@MyValue("12")
private int age;

public void printAll(){
System.out.println(name+":"+age);
}
}


封装在类级别打上了注解的bean信息
package com.hk.annospringioc;

import java.util.List;

public class AnnoBean {
private String name;
private String className;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getSingleton() {
return singleton;
}
public void setSingleton(String singleton) {
this.singleton = singleton;
}
public List<Property> getProps() {
return props;
}
public void setProps(List<Property> props) {
this.props = props;
}
private String singleton = "1";
private List<Property> props;
}


封装在属性级别打上了注解的property的bean信息
package com.hk.annospringioc;

public class Property {
private String name;
private String value;
private String ref;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

public String getRef() {
return ref;
}

public void setRef(String ref) {
this.ref = ref;
}

}

ConfigManager

……这里我们只需要对包扫描根路径下的所有类进行扫描,通过类以及属性的注解扫描组装好对应bean容器即可,其实跟xml配置文件的整个流程是一样一样的,只是在实际实现上面,我们不用去解析xml文件,而是通过reflect对每一个类进行扫描解析,扫描到对应的注解就做对应的操作,仅此而已,大家稍看代码自己体会一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.hk.annospringioc;

import java.io.File;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
*
* @ClassName: ConfigManager
* @Description: TODO(这里用一句话描述这个类的作用)
* @author A18ccms a18ccms_gmail_com
* @date 2019年4月9日 下午5:33:23
*
*/
public class ConfigManager {

public Map<String,AnnoBean> ScanPackage(String basePackage){
List<String> list = new ArrayList<String>();
Map<String,AnnoBean> map = new HashMap<String,AnnoBean>();
final URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
final File basePackageFile = new File(url.getPath());
File[] listFiles = basePackageFile.listFiles();
for(File file : listFiles){
if(file.isFile()){
list.add(basePackage+"."+file.getName().split("\\.")[0]);
}
}

for(String strr : list){
String className = strr;
try {
Class<?> forName = Class.forName(className);
if(forName.isAnnotationPresent(MyComponent.class)){
MyComponent annotation = forName.getAnnotation(MyComponent.class);
String value = annotation.value();
AnnoBean bean = new AnnoBean();
bean.setName(value);
bean.setClassName(className);
List<Property> propList = new ArrayList<Property>();
Field[] fields = forName.getDeclaredFields();
for(Field field : fields){
if(field.isAnnotationPresent(MyAutoWired.class)){
Property prop = new Property();
MyAutoWired annotation2 = field.getAnnotation(MyAutoWired.class);
prop.setName(field.getName());
prop.setRef(annotation2.value());
propList.add(prop);
}
if(field.isAnnotationPresent(MyValue.class)){
Property prop = new Property();
MyValue annotation2 = field.getAnnotation(MyValue.class);
prop.setName(field.getName());
prop.setValue(annotation2.value());
propList.add(prop);
}
}
bean.setProps(propList);
map.put(value,bean);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

}
return map;
}

}

写到这里还没有完,现在重点来了,有没有发现,上面这些类的类名或者属性上面多了三个这个东西,@MyAutoWired,@MyComponent,@MyValue,这就是我们的自定义注解,下面就看看这三个东西如何编写。

自定义注解编写

……自定义注解其实就是告诉框架,需要识别的注解是哪些,当构建bean容器的时候,框架初始化时见到对应的注解去做对应的操作,MyComponent是类级别注解,MyAutowired、MyValue是属性级别注解,只能在对应区域进行使用!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

package com.hk.annospringioc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.dom4j.Element;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAutoWired {
String value();
}


package com.hk.annospringioc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.dom4j.Element;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponent {
String value();
}


package com.hk.annospringioc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.dom4j.Element;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyValue {
String value();
}

好了,到此整个基于注解的ioc实现方式代码就写完了,下面我们来运行一下看看结果

最后运行TestMain

查看最终运行结果: 同样IOC容器内的实例也是单例的
图p2

**运行结果**

总结

……ok,写到这里我们就简单的实现了一个基于注解方式的IOC容器,原理其实也很简单,只需要你了解reflect以及java基础你都不用去了解xml相关的内容,本篇文章重点只是向大家介绍一个最基本的实现原理,理解这个原理对于以后的开发会有极大的帮助,所以现在你有稍微了解到我们在使用springboot开发过程中那些@Controller、@Service、@Component、@Autowired注解的含义了吗?下一篇文章我们就基于本篇的原理来自己实现一个简单的springmvc框架…谢谢!