手写源码(二):自己实现SpringIOC

栏目: 编程语言 · XML · 发布时间: 5年前

内容简介:IOC控制反转就是通过反射机制帮我们托管了所有的类。我想要自己实现的就是使用XML注入Bean和使用注解(SpringIOC的XML版本使用Dom4j和反射技术解析XML和注入类

IOC控制反转就是通过反射机制帮我们托管了所有的类。

我想要自己实现的就是使用XML注入Bean和使用注解( @Service 之类的)注入Bean

Spring的Xml版本IOC原理

SpringIOC的XML版本使用Dom4j和反射技术解析XML和注入类

所有的Bean在ApplicationContext创建的时候就会初始化

XML版本注入

自行解析XML

一个自己解析XML的小Demo,使用Dom4j解析XML,如下

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一样)

使用注解注入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("没有类加上了注解");
        }
    }
复制代码

全文如下

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方法传入类名的首字母小写就可以了

实现自动装配(依赖注入)

  • 自动装配/依赖注入原理(实现步骤)
    • 使用反射机制获取当前类的所有属性
    • 判断当前类是否存在注解
    • 使用默认名称在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全文如下

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);
            }
        }

    }
}

复制代码

以上所述就是小编给大家介绍的《手写源码(二):自己实现SpringIOC》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

信息检索导论

信息检索导论

Christopher D.Manning、Hinrich Schütze、Prabhakar Raghavan / 王斌 / 人民邮电出版社 / 201008 / 69.00元

封面图片为英国伯明翰塞尔福瑞吉百货大楼,其极具线条感的轮廓外型优美,犹如水波的流动。其外表悬挂了1.5万个铝碟,创造出一种极具现代气息的纹理装饰效果,有如夜空下水流的波光粼粼,闪烁于月光之下,使建筑的商业氛围表现到极致。设计该建筑的英国“未来系统建筑事物所”,将商场内部围合成一个顶部采光的中庭,配以交叉的自动扶梯,使购物环境呈现出一种凝聚的向心力和商业广告的展示效应。作为英国第二商业城市伯明翰的建......一起来看看 《信息检索导论》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具