实现极简IoC容器

创建Apple类
public class Apple {
    private String title;
    private String color;
    private String origin;

    public Apple(){

    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getOrigin() {
        return origin;
    }

    public void setOrigin(String origin) {
        this.origin = origin;
    }
}
编写applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
        <property name="title" value="红富士"/>
        <property name="color" value="红色"/>
        <property name="origin" value="欧洲"/>
    </bean>
</beans>

 

创建ApplicationContext接口,接口里创建getBean抽象类
public interface ApplicationContext {
    public Object getBean(String beanId);
}

 

创建ClassPathXmlApplicationContext实现类,实现ApplicationContext接口。
public class ClassPathXmlApplicationContext implements ApplicationContext {
    private Map iocContainer = new HashMap();
    public ClassPathXmlApplicationContext(){
        try {
            String filePath = this.getClass().getResource("/applicationContext.xml ").getPath();
        }catch (Exception e){

        }
    }
    public Object getBean(String beanId){
        return null;
    }
}

 

作为java使用Map对象保存beanId和所对应的对象,Mao是一个键值对的结构,作为键就对应了beanId,作为值就对应了容器创建过程中所产生的对象。
创建一个HashMap作为IoC容器
private Map iocContainer = new HashMap();

 

下一步就是在实例化ClassPathXmlApplicationContext对象过程中去加载处理xml文件。在ClassPathXmlApplicationContext构造方法中,在对象初始化的时候读取刚才编写的applicationContext.xml文件。
getResource方法用于从classpath下获取指定的文件资源。之后通过getPath得到这个文件的路径。这样就能得到配置文件的物理地址了。
public ClassPathXmlApplicationContext(){
        try {
            String filePath = this.getClass().getResource("/applicationContext.xml ").getPath();
        }catch (Exception e){

        }
    }

 

但是作为这个地址如果中间包含中文,可能出现路径找不到的问题,所以还需要url的解码 
filePath = new URLDecoder().decode(filePath,"UTF-8");

 

接下来作为获取到的xml路径如何对xml进行解析?
需要引入dom4j和jaxen,在pom.xml中加入依赖。
Dom4j是java的xml解析组件。Jaxen是Xpath表达式解释器。Dom4j底层是依赖Jaxen的,所以都要引入
    <dependencies>
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>

 

利用Dom4j提供的SAXReader对象去加载解析filePath对应的xml。reader对象中提供了一个read方法用于读取指定文件。这里新创建一个File,将filePath传入其中,代表了所对应的文件对象。然后再提供给reader进行读取解析。这样就能得到xml对应的文档对象document。所有的文档数据都包含在document对象中。
        try {
            String filePath = this.getClass().getResource("/applicationContext.xml ").getPath();
            filePath = new URLDecoder().decode(filePath,"UTF-8");
            SAXReader reader = new SAXReader();
            Document document = reader.read(new File(filePath));
        }catch (Exception e){

        }

 

之后的工作就是按照xml格式,依次进行读取。首先文档的根节点是beans,beans下面拥有若干bean标签,先把它提取出来。document.getRootElement()得到根节点,然后在根节点下用selectNodes(),selectNodes是按照指定的Xpath表达式来得到节点的集合。直接传入bean就会将当前根节点下所有bean子标签获取了。返回的是一个List集合,集合中每个对象都是一个Node节点。
List<Node> beans = document.getRootElement().selectNodes("bean");

 

接下来for循环遍历。作为Node有多种子类型,作为每一个beans,它的实际类型为Element(元素)。在遍历过程中,每一个bean标签都包含了两个属性,一个id,一个class,需要读取出来。使用ele.attributeValue()读取当前节点对应的属性。就将xml两个属性值提取了出来
            for (Node node : beans){
                Element ele = (Element) node;
                String id = ele.attributeValue("id");
                String className = ele.attributeValue("class");
            }

 

但是作为这两个属性,用于实例化对象。如何对Apple实例化呢?
就可以使用反射技术了。Class.forName()用于加载指定的类,将className传入,便可以得到与之对应的类对象了。通过newInstance便可以通过默认构造方法创建Apple类的实例。
                Class c = Class.forName(className);
                Object obj = c.newInstance();

 

这时候beanId有了对象也有了,下面只需要利用iocContainer中的put方法将id和obj放入其中,就相当于IoC容器对刚才新创建的对象赋予了beanId进行管理
        try {
            String filePath = this.getClass().getResource("/applicationContext.xml ").getPath();
            filePath = new URLDecoder().decode(filePath,"UTF-8");
            SAXReader reader = new SAXReader();
            Document document = reader.read(new File(filePath));
            List<Node> beans = document.getRootElement().selectNodes("bean");
            for (Node node : beans){
                Element ele = (Element) node;
                String id = ele.attributeValue("id");
                String className = ele.attributeValue("class");
                Class c = Class.forName(className);
                Object obj = c.newInstance();
                iocContainer.put(id,obj);
            }
            System.out.println("IoC容器初始化完毕");
        }catch (Exception e){
            e.printStackTrace();
        }

 

getBean方法,利用iocContainer.get方法对指定的beanId进行提取,并且进行返回就可以了
    public Object getBean(String beanId){
        return iocContainer.get(beanId);
    }

 

这样就完成了一个简单的IoC容器
验证。创建测试类Application
在Application中调用getBean传入beanId打印出来地址,在ClassPathXmlApplicationContext中打印iocContainer地址。两者地址相同
 
接下来在ioc容器中,通过set方法进行属性注入。还是通过反射来完成
 
在获取到对象以后,通过ele.selectNodes得到每一个bean标签下的每一个property属性标签。且同样使用List<Node>进行保存
    List<Node> properties = ele.selectNodes("property");

 

这个List集合中就包含了bean中的三个属性name和value数据。之后再利用for循环对属性进行遍历。
                for (Node p : properties){
                    Element property = (Element)p;
                    
                }

 

之后提取每一个属性中的name和value
                for (Node p : properties){
                    Element property = (Element)p;
                    String propName = property.attributeValue("name");
                    String propValue = property.attributeValue("value");
                }

 

属性名字和对应的值获取到以后,如何在运行时动态注入呢?
基于property这种形式完成动态注入,是通过属性的set方法完成的。而set方法命名的规则,前面是set,后面增加属性名,属性名首字母大写。只要按照这个规则组织set方法,就可以利用反射进行动态调用。
首先获取set方法名
                    String setMethodName = "set" + propName.substring(0,1).toUpperCase() + propName.substring(1);

 

之后通过方法名完成调用。反射中的getMethod方法。
                    // 获取Method对象
                    Method setMethod = c.getMethod(setMethodName, String.class);
                    // 通过set方法注入数据
                    setMethod.invoke(obj,propValue);

 

动态注入就完成了。
 

附件列表

 https://files.cnblogs.com/files/sx1011/s06.zip

posted @ 2020-09-15 16:17  看清楚了吗  阅读(137)  评论(0编辑  收藏  举报