实现极简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