Java手写简单版的IOC

昨天看了SpringBoot,发现里面有很多注解,很好奇原理是什么,所以想自己动手写写看;

我看了《Java核心技术 卷2 高级特性》这本书的第8章,里面有注解的一些概念;

然后也参考了GitHub上有人写的一份代码https://github.com/zhuchangwu/CIOC;

发现@在Java中就是作为注解的标记来使用的,除非@是在注释里面。通过反射机制能够获取到对应的注解,之前我还一直以为是自己读取源代码As字符串,然后慢慢找@,原来这些是编译器已经帮你做好的了。

先看看我的目录结构

image-20200507093606946

我这里把组件、自动注入、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());
    }
}

image-20200507095123972

这里工厂我就直接实例化了,不搞单例模式;

上面我给了工厂一个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工具,这里我就不拓展了,感兴趣的可以自己去研究看看。

posted @ 2020-05-07 22:54  木灬匕禾页  阅读(312)  评论(0)    收藏  举报