Java手写简单版的IOC
昨天看了SpringBoot,发现里面有很多注解,很好奇原理是什么,所以想自己动手写写看;
我看了《Java核心技术 卷2 高级特性》这本书的第8章,里面有注解的一些概念;
然后也参考了GitHub上有人写的一份代码https://github.com/zhuchangwu/CIOC;
发现@在Java中就是作为注解的标记来使用的,除非@是在注释里面。通过反射机制能够获取到对应的注解,之前我还一直以为是自己读取源代码As字符串,然后慢慢找@,原来这些是编译器已经帮你做好的了。
先看看我的目录结构

我这里把组件、自动注入、xml配置都写了
先看看自定义注解
package com.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: jane
* @CreateTime: 2020/5/6
* @Description:
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowire {
}
package com.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: jane
* @CreateTime: 2020/5/6
* @Description:
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
两个的内容几乎一样,就是Target的内容不一样,因为一个要放到类上面,一个放到成员属性上面;
看看定义的Bean
package com.bean;
/**
* @author: jane
* @CreateTime: 2020/5/6
* @Description:
*/
public class Cat {
String name;
String hobby;
public Cat(){
}
public Cat(String name, String hobby){
this.name = name;
this.hobby = hobby;
}
@Override
public String toString() {
return String.format("A cat named %s, likes %s", name, hobby);
}
}
package com.bean;
import com.anno.Autowire;
import com.anno.Component;
/**
* @author: jane
* @CreateTime: 2020/5/6
* @Description:
*/
@Component
public class Person {
@Autowire
private Cat cat;
public Cat getCat() {
return cat;
}
}
这里应该很清楚了,我想通过注解的方式实例化一个Person,后面通过Xml实例化一只Cat,然后把这只Cat自动注入到Person的cat成员属性中。
配置文件写了啥
<?xml version="1.0"?>
<config>
<packet-scan>com.bean</packet-scan>
<bean id="cat" class="com.bean.Cat">
<property name="name">DuoLaAMen</property>
<property name="hobby">TuoLuoSao</property>
</bean>
</config>
Xml的格式就不仿造Spring了,原理都一样的;
这里还开了一个包扫描,虽然我只有一个注解类需要实例化;
先看看客户端怎么写的
import com.bean.Person;
import com.factory.Factory;
/**
* @author: jane
* @CreateTime: 2020/5/6
* @Description:
*/
public class Client {
public static void main(String[] args) {
Factory factory = new Factory("src/config.xml");
Person person = (Person)factory.get("Person");
System.out.println(person.getCat());
}
}

这里工厂我就直接实例化了,不搞单例模式;
上面我给了工厂一个config.xml的路径后,后面就已经能获取到这个person;
而且person里面的cat也已经注入了。
所以new一个Factory的时候,大概干了这些事:
- 根据配置文件路径,打开config.xml并解析
- 根据xml的内容,通过反射实例化一只Cat,放到HashMap里面
- 根据xml的包扫描路径,去指定位置搜索,找到那些有注解的class字节码
- 根据不同注解,对class类进行实例化和自动注入,放到HashMap里面
- 客户端通过get请求就可访问到对应Bean
工厂大揭秘
package com.factory;
import com.anno.Autowire;
import com.anno.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
/**
* @author: jane
* @CreateTime: 2020/5/6
* @Description:
*/
public class Factory {
public HashMap<String, Object> hashMap = new HashMap<>();
private String xmlPath;
private String packetName;
public Factory(String xmlPath){
this.xmlPath = xmlPath;
initXml();
initAnnotation();
}
private void initXml(){
try{
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File(xmlPath));
//获取包扫描路径
NodeList nl = doc.getElementsByTagName("packet-scan");
Node classNode = nl.item(0).getFirstChild();
packetName = classNode.getNodeValue().trim();
//创造喵星人~
NodeList beans = doc.getElementsByTagName("bean");
for(int i=0;i<beans.getLength();i++){
Node bean = beans.item(i);
Element element = (Element)bean;
//获取bean的id和class
String key = element.getAttribute("id");
String className = element.getAttribute("class");
//
Class<?> clz = Class.forName(className);
Object obj = clz.getDeclaredConstructor().newInstance();
hashMap.put(key, obj);
//注入bean的属性值
NodeList nodeList = element.getElementsByTagName("property");
for(int j=0;j<nodeList.getLength();j++){
Element node = (Element)nodeList.item(j);
String propertyName = node.getAttribute("name");
String propertyValue = node.getFirstChild().getNodeValue();
//自动注入变量
Field field = obj.getClass().getDeclaredField(propertyName);
field.setAccessible(true);//可注入private
field.set(obj, propertyValue);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
private void initAnnotation(){
packetScan("out/production/Annotation/"+packetName.replaceAll("\\.", "/"));
}
public Object get(String key){
return hashMap.get(key);
}
//扫描路径下所有类,通过放射获取注解
public void packetScan(String path){
File file = new File(path);
if(file.isFile()){
try {
//处理绝对路径,转换为包名,使反射能够获取这个class文件
String className = produce(path);
Class<?> clz = Class.forName(className);
//找到有添加注解的class
if(clz.isAnnotationPresent(Component.class)){
Object obj = clz.getDeclaredConstructor().newInstance();
hashMap.put(clz.getSimpleName(), obj);
//自动注入变量
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field : fields){
if(field.getDeclaredAnnotation(Autowire.class) != null){
field.setAccessible(true);//可注入private
field.set(obj, hashMap.get(field.getName()));
}
}
}
}
catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}else{
String[] fileNames = file.list();
assert fileNames != null;
for(String name: fileNames){
packetScan(path+"/"+name);
}
}
}
public String produce(String path){
String tmp1 = path.replaceAll("/", ".");
String tmp2 = tmp1.substring(tmp1.lastIndexOf("com.bean"),tmp1.length()-".class".length());
return tmp2;
}
}
OK,写完了,其实代码写得挺粗糙的,完全跟Spring的比不了,而且上面有很多情况都没有考虑,我都是以最好情况去处理,因为我也只是想知道它原理的什么,怎么做到的;
现在有一些注解工具,像lombok,它的实现貌似没这么简单。因为它是要修改源代码的,加入set和get的方法。这里我了解到是字节码方面的处理了,可以借助ASM工具,这里我就不拓展了,感兴趣的可以自己去研究看看。

IOC是Spring中一个重要的技术,叫做控制反转,即把实例化对象的操作交给Spring去执行,从而达到工程上的解耦。本文将动手写一个IOC,包括注解和Xml的方式,项目比较粗糙,有bug请多包涵。
浙公网安备 33010602011771号