手寫源碼(二):本身實現SpringIOC

手寫IOC

IOC控制反轉就是經過反射機制幫咱們託管了全部的類。
我想要本身實現的就是使用XML注入Bean和使用註解(@Service之類的)注入Bean安全

Spring的Xml版本IOC原理

SpringIOC的XML版本使用Dom4j和反射技術解析XML和注入類
全部的Bean在ApplicationContext建立的時候就會初始化bash

XML版本注入

自行解析XML

一個本身解析XML的小Demo,使用Dom4j解析XML,以下app

public class XmlUtils {
    public static void main(String[] args) throws DocumentException {
        XmlUtils xmlUtils = new XmlUtils();
        xmlUtils.readXml("student.xml");
    }

    public void readXml(String xmlPath) throws DocumentException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(getResourceAsSteam(xmlPath));
        Element rootElement = document.getRootElement();
        getNodes(rootElement);
    }

    private static void getNodes(Element rootElement) {
        //獲取節點名稱
        System.out.print("節點名稱:" + rootElement.getName()+"\t\t");
        //獲取節點屬性
        List<Attribute> attributes = rootElement.attributes();
        for (Attribute attribute : attributes) {
            System.out.print("屬性:"+attribute.getName()+"---"+attribute.getText()+"\t\t");
        }
        //獲取屬性值
        String value = rootElement.getTextTrim();
        if (!StringUtils.isEmpty(value)) {
            System.out.print("節點值:" + value+"\t\t");
        }
        System.out.println();
        //遍歷子節點
        Iterator<Element> elementIterator = rootElement.elementIterator();
        while (elementIterator.hasNext()) {
            Element next = elementIterator.next();
            getNodes(next);
        }
    }

    private InputStream getResourceAsSteam(String xmlPath) {
        return this.getClass().getClassLoader().getResourceAsStream(xmlPath);
    }
}

複製代碼

本身實現XML獲取Bean的ApplicationContext

  • 實現步驟
    • 讀取配置XML
    • 查看傳入的BeanId和Xml中的BeanId是否一致
    • 使用反射建立對象而且返回
  • 按照上面的步驟實現本身的ApplicationContext
    核心方法代碼以下(這個getBean方法就是按照上面的步驟實現的,我把步驟的具體實現都抽取出去了)
/** 用於GetBean的方法*/
    public Object getBean(String beanId) throws DocumentException, IllegalAccessException, InstantiationException, ClassNotFoundException {
        if (StringUtils.isEmpty(beanId)) {
            throw new RuntimeException("BeanId爲空");
        }
        //解析Xml,獲取全部節點
        List<Element> elements = readXml();
        if (elements == null||elements.isEmpty()) {
            throw new RuntimeException("沒有任何Bean信息");
        }
        //查找對應的ClassName
        String className = getClassName(beanId, elements);
        if (StringUtils.isEmpty(className)) {
            throw new RuntimeException("沒有配置類信息");
        }
        //利用反射機制建立Bean
        return newInstance(className);
    }
複製代碼

全文以下工具

public class ExtClassPathXmlApplicationContext {
    private String xmlPath;

    public ExtClassPathXmlApplicationContext(String xmlPath) {
        this.xmlPath = xmlPath;
    }

    /** 用於GetBean的方法*/
    public Object getBean(String beanId) throws DocumentException, IllegalAccessException, InstantiationException, ClassNotFoundException {
        if (StringUtils.isEmpty(beanId)) {
            throw new RuntimeException("BeanId爲空");
        }
        //解析Xml,獲取全部節點
        List<Element> elements = readXml();
        if (elements == null||elements.isEmpty()) {
            throw new RuntimeException("沒有任何Bean信息");
        }
        //查找對應的ClassName
        String className = getClassName(beanId, elements);
        if (StringUtils.isEmpty(className)) {
            throw new RuntimeException("沒有配置類信息");
        }
        //利用反射機制建立Bean
        return newInstance(className);
    }
    
    /**解析Xml文件,獲取全部節點*/
    private List<Element> readXml() throws DocumentException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(getResourceAsSteam());
        Element rootElement = document.getRootElement();
        List<Element> elements = rootElement.elements();
        return elements;
    }
}
複製代碼

而後在主方法裏建立上面的Context,使用getBean方法,就能夠拿到想要的Bean了(和Spring的ClassPathApplicationContext同樣)ui

使用註解注入Bean

一些須要注意的性質

  • 須要把已知帶有註解的類裝入一個集合裏,便於隨時取用
  • 加載時才初始化上面的集合,還須要注意線程安全問題
  • 使用懶加載模式加載Bean
  • 只實現了單例,全部getBean都是建立了一個實例
  • 只實現了使用默認的beanId進行注入(類名第一個字母小寫),不能自定義ID

實現註解裝配Bean而且經過getBean方法獲取Bean

  • 實現步驟
    • 使用反射機制,掃包,獲取全部的類(使用了一個開源的掃包的工具類。。沒有本身實現)
    • 判斷每一個類上是否有注入bean的註解
    • 使用反射機制進行初始化類
  • 按照上面的步驟實現本身的ApplicationContext
    核心代碼以下
/**初始化Bean容器*/
    private void initBeans() throws IllegalAccessException, InstantiationException {
        beans = new ConcurrentHashMap<String, Object>();
        //使用掃包工具得到包下全部的類
        List<Class<?>> classes = ClassUtils.getClasses(packageName);
        //判斷全部的類上面是否有註解,有的話就會加入到Bean容器裏面去
        findClassExistAnnotation(classes);
        if (beans == null || beans.isEmpty()) {
            throw new RuntimeException("沒有類加上了註解");
        }
    }
複製代碼

全文以下this

public class ExtAnnotationApplicationContext {
    private String packageName;
    /**保存有Service註解的類*/
    private ConcurrentHashMap<String, Object> beans = null;

    public ExtAnnotationApplicationContext(String packageName) throws InstantiationException, IllegalAccessException {
        this.packageName = packageName;
        initBeans();
    }

    /**初始化Bean容器*/
    private void initBeans() throws IllegalAccessException, InstantiationException {
        beans = new ConcurrentHashMap<String, Object>();
        //使用掃包工具得到包下全部的類
        List<Class<?>> classes = ClassUtils.getClasses(packageName);
        //判斷全部的類上面是否有註解,有的話就會加入到Bean容器裏面去
        findClassExistAnnotation(classes);
        if (beans == null || beans.isEmpty()) {
            throw new RuntimeException("沒有類加上了註解");
        }
    }

    /**掃包,把有註解的類加入到bean容器裏*/
    private void findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
        for (Class classInfo : classes) {
            //判斷是否有註解
            Annotation annotation = classInfo.getAnnotation(ExtService.class);
            if (annotation != null) {
                //到這裏表示有這個註解
                String className = classInfo.getName();
                //默認Id是首字母小寫
                beans.put(toLowerCaseFirestOne(classInfo.getSimpleName()), newInstance(classInfo));
            }
        }
    }

    /**類名的首字母小寫*/
    private String toLowerCaseFirestOne(String className) {
        return new StringBuilder().append(Character.toLowerCase(className.charAt(0))).append(className.substring(1)).toString();
    }

    /**獲取Bean的方法*/
    public Object getBean(String beanId) throws IllegalAccessException, InstantiationException {
        if (StringUtils.isEmpty(beanId)) {
            throw new RuntimeException("BeanID爲空");
        }
        return beans.get(beanId);
    }

    /**利用反射機制建立Bean*/
    private Object newInstance(Class classInfo) throws IllegalAccessException, InstantiationException {
        if (classInfo == null) {
            throw new RuntimeException("沒有這個ID的bean");
        }
        return classInfo.newInstance();
    }

    /**依賴注入傳入類的屬性*/
    private void attrAssign(Class<?> classInfo) {
        //獲取這個類全部的屬性
        Field[] fields = classInfo.getFields();
        //判斷當前屬性是否有註解
        for (Field field : fields) {
            ExtService extService = field.getAnnotation(ExtService.class);
            if (extService != null) {
                //到這裏說明這個屬性裏有這個註解
                String fieldName = field.getName();
            }
        }

    }
}
複製代碼

如今獲取到Bean只用在類上加上本身的Service註解而後使用getBean方法傳入類名的首字母小寫就能夠了spa

實現自動裝配(依賴注入)

  • 自動裝配/依賴注入原理(實現步驟)
    • 使用反射機制獲取當前類的全部屬性
    • 判斷當前類是否存在註解
    • 使用默認名稱在Bean容器裏查找對象,而後賦值
  • 核心代碼
/**自動注入注入這個對象的屬性*/
    private void attrAssign(Object object) throws IllegalAccessException {
        //獲取這個類全部的屬性
        Field[] fields = object.getClass().getDeclaredFields();
        //判斷當前屬性是否有註解
        for (Field field : fields) {
            ExtService extService = field.getAnnotation(ExtService.class);
            if (extService != null) {
                //到這裏說明這個屬性裏有這個註解,在從容器裏獲取對象而後給這個屬性賦值
                String fieldName = field.getName();
                Object target = beans.get(fieldName);
                if (target == null) {
                    throw new RuntimeException("注入\"" + fieldName + "\"屬性失敗,bean容器裏沒有這個對象");
                }
                //容許訪問私有屬性
                field.setAccessible(true);
                //第一個參數是這個屬性所在的對象
                field.set(object,target);
            }
        }
    }
複製代碼

這個方法咱們須要在Bean容器初始化完成以後,把全部的bean容器的Object裏作一遍,達到依賴注入的效果,以下(若是給全部的類都實現注入Bean容器裏的bean的話,就是依賴注入@Autowired了)線程

public ExtAnnotationApplicationContext(String packageName) throws InstantiationException, IllegalAccessException {
        this.packageName = packageName;
        initBeans();
        //在全部Bean容器裏全部bean自動注入全部的Bean
        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            System.out.println("beanId:"+entry.getKey());
            Object bean = entry.getValue();
            attrAssign(bean);
        }
    }
複製代碼

增長了依賴注入的Context全文以下rest

public class ExtAnnotationApplicationContext {
    private String packageName;
    /**保存有Service註解的類*/
    private ConcurrentHashMap<String, Object> beans = null;

    public ExtAnnotationApplicationContext(String packageName) throws InstantiationException, IllegalAccessException {
        this.packageName = packageName;
        initBeans();
        //在全部Bean容器裏全部bean自動注入全部的Bean
        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            System.out.println("beanId:"+entry.getKey());
            Object bean = entry.getValue();
            attrAssign(bean);
        }
    }

    /**初始化Bean容器*/
    private void initBeans() throws IllegalAccessException, InstantiationException {
        beans = new ConcurrentHashMap<String, Object>();
        //使用掃包工具得到包下全部的類
        List<Class<?>> classes = ClassUtils.getClasses(packageName);
        //判斷全部的類上面是否有註解,有的話就會加入到Bean容器裏面去
        findClassExistAnnotation(classes);
        if (beans == null || beans.isEmpty()) {
            throw new RuntimeException("沒有類加上了註解");
        }
    }

    /**掃包,把有註解的類加入到bean容器裏*/
    private void findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
        for (Class classInfo : classes) {
            //判斷是否有註解
            Annotation annotation = classInfo.getAnnotation(ExtService.class);
            if (annotation != null) {
                //到這裏表示有這個註解
                String className = classInfo.getName();
                //默認Id是首字母小寫
                beans.put(toLowerCaseFirestOne(classInfo.getSimpleName()), newInstance(classInfo));
            }
        }
    }

    /**類名的首字母小寫*/
    private String toLowerCaseFirestOne(String className) {
        return new StringBuilder().append(Character.toLowerCase(className.charAt(0))).append(className.substring(1)).toString();
    }

    /**獲取Bean的方法*/
    public Object getBean(String beanId) throws IllegalAccessException, InstantiationException {
        if (StringUtils.isEmpty(beanId)) {
            throw new RuntimeException("BeanID爲空");
        }
        return beans.get(beanId);
    }

    /**利用反射機制建立Bean*/
    private Object newInstance(Class classInfo) throws IllegalAccessException, InstantiationException {
        if (classInfo == null) {
            throw new RuntimeException("沒有這個ID的bean");
        }
        return classInfo.newInstance();
    }

    /**自動注入注入這個對象的屬性*/
    private void attrAssign(Object object) throws IllegalAccessException {
        //獲取這個類全部的屬性
        Field[] fields = object.getClass().getDeclaredFields();
        //判斷當前屬性是否有註解
        for (Field field : fields) {
            ExtResource extResource = field.getAnnotation(ExtResource.class);
            if (extResource != null) {
                //容許訪問私有屬性
                field.setAccessible(true);
                //到這裏說明這個屬性裏有這個註解,在從容器裏獲取對象而後給這個屬性賦值
                String fieldName = field.getName();
                Object target = beans.get(fieldName);
                if (target == null) {
                    throw new RuntimeException("注入\"" + fieldName + "\"屬性失敗,bean容器裏沒有這個對象");
                }
                //第一個參數是這個屬性所在的對象
                field.set(object,target);
            }
        }

    }
}

複製代碼
相關文章
相關標籤/搜索