理解spring的ioc容器功能并且手动(java)实现该功能
spring的确是一个非常好用的轻量级框架。关于spring的几大特色我这里就不说了,网上一大堆。我这里主要说说spring中的ioc容器的理解。
IOC全称:Inversion of Control,意思是控制反转。那么什么叫控制反转?提出这个概念目的是解决什么?
那我们首先来回答第一个问题(什么叫控制反转?):在我们平时用java写程序的时候,如果要用到别的类,那么我们会创建一个类的对象--new Object()。这样的话这个对象是由自己主动创建的,与使用该类的对象的耦合程度很高。而控制反转就是spring的一个容器来创建对象,在一个类中需要另一个类的对象时,只需要从该容器中取出来就是了,该对象的控制权在该容器中,控制权发生了反转。
第二个问题(提出这个概念的目的是什么?):IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
提到IOC就不得不提DI(依赖注入)。
DI全称:Dependency Injection,意思是依赖注入。那么这个概念怎么理解?
自然是从字面拆分来理解:依赖谁?注入谁?谁依赖?谁注入?目的是什么?
第一个问题(依赖谁?):应用程序依赖于IOC容器
第二个问题(注入谁?):IOC容器将应用程序需要的对象和资源注入到应用程序里面
第三个问题(谁依赖?):应用程序依赖。(这里也可以看到控制反转的概念,一般而言,我们平时写代码需要资源时,就是从该类中主动去获取,使用IOC后,从IOC中获取)
第四个问题(谁注入?):IOC容器向应用程序中注入资源
第五个问题(目的是什么?):依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
那么IOC和DI的关系呢?
个人理解一句话:IOC容器中的对象需要依赖注入来完成具体的配置。两者其实是一种概念的不同方面所看待。
注:以上理解是看完以下博客后掺杂自己的思路所写。
https://www.cnblogs.com/liubin1988/p/8909610.html
那我们就来看看如何通过思路来简单实现IOC容器。
在spring中,一般而言获取对象的方法如下:
ApplicationContext bean = new ClassPathXmlApplicationContext("bean.xml"); StudentInfo student = (StudentInfo) bean.getBean("student");
配置项如下:
<beans>
<bean id="student" class = "myspring_ioc.bean.StudentInfo">
<propertity name="age" value = "18"></propertity>
<propertity name="name" value = "张三"></propertity>
</bean>
</beans>
那么我们这里就要模拟两个类:ApplicationContext 和 ClassPathXmlApplicationContext。其中ApplicationContext 我们可以定义为抽象类,给个抽象方法,ClassPathXmlApplicationContext要继承该抽象类并且要实现该抽象方法。
ApplicationContext 类如下:
package myspring_ioc.org.springframework.context.support.ApplicationContext; /** * * @author xujinren * applicationContext * */ public abstract class ApplicationContext { /** * get bean * @return */ public abstract Object getBean(String id); }
ClassPathXmlApplicationContext类如下:
/** * * @author xujinren * *parse the bean.xml and create BeanFactory * */ public class ClassPathXmlApplicationContext extends ApplicationContext{
public ClassPathXmlApplicationContext(String xmlPath){
}
@Override public Object getBean(String id){ } }
然后怎么做?
按照思路,我们首先要将传进来的xml文件解析好,并通过反射机制创建好对象,接着就是给该对象中的属性来初始化,最后放到一个map中,以id为键,对象为值。
1.解析xml文件,解析的方法有很多种,我这里使用SAXReader来解析,需要的jar包为dom4j,请自行到网上下载。
这里说说SAXReader解析的思路。
将xml文件以流的方式读入内存,然后生成一个document,通过操作节点的方式获得该文档中一系列的数据。注:需要导入的类都是dom4j里面的。
我写的解析类如下:SAXReaderParseXml.java
package myspring_ioc.utils; import java.io.IOException; import java.io.InputStream; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.io.SAXReader; public class SAXReaderParseXml { /** * init class ClassPathXmlApplicationContext and parse xml by * SAXReader using dom4j.jar * @param xmlPath * @return document */ public Document saxReaderParseXml(String xmlPath){ InputStream is = null; SAXReader reader = new SAXReader(); Document doc = null; try { is = this.getClass().getClassLoader().getResourceAsStream(xmlPath); if(is == null){ //if the xmlPath is invalid throw new IOException("this xmlPath is null"); }else{ doc = reader.read(is); if(doc == null){ throw new IOException("this document is null"); } } } catch (IOException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } return doc; } }
bean.xml配置如下:
<?xml version="1.0" encoding="utf-8"?> <beans> <bean id="student" class = "myspring_ioc.bean.StudentInfo"> <propertity name="age" value = "18"></propertity> <propertity name="name" value = "张三"></propertity> <propertity name="address" ref="address"></propertity> </bean> <bean id = "address" class = "myspring_ioc.bean.Address"> <propertity name = "city" value = "衡阳市"></propertity> <propertity name = "province" value = "湖南省"></propertity> </bean> </beans>
将该bean.xml放到src目录下面
解析生成文档返回后,就需要ClassPathXmlApplicationContext来操作该文档获得数据。首先要获得配置文件中的bean。在获得之前,要创建一个Bean类来存储信息和一个proertity类来存储要创建对象中属性的初始化值。 如下:
1 package myspring_ioc.bean; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * 8 * @author xujinren 9 * this is a bean class coming from xml 10 * 11 */ 12 public class Bean { 13 14 private List<Propertity> properties = new ArrayList<>(); 15 16 private String id; 17 18 private String classPath; 19 20 @Override 21 public String toString() { 22 return "Bean [properties=" + properties + ", id=" + id + ", classPath=" + classPath + "]"; 23 } 24 25 public List<Propertity> getProperties() { 26 return properties; 27 } 28 29 public void setProperties(List<Propertity> properties) { 30 this.properties = properties; 31 } 32 33 public String getId() { 34 return id; 35 } 36 37 public void setId(String id) { 38 this.id = id; 39 } 40 41 public String getClassPath() { 42 return classPath; 43 } 44 45 public void setClassPath(String classPath) { 46 this.classPath = classPath; 47 } 48 49 @Override 50 public int hashCode() { 51 final int prime = 31; 52 int result = 1; 53 result = prime * result + ((classPath == null) ? 0 : classPath.hashCode()); 54 result = prime * result + ((id == null) ? 0 : id.hashCode()); 55 result = prime * result + ((properties == null) ? 0 : properties.hashCode()); 56 return result; 57 } 58 59 @Override 60 public boolean equals(Object obj) { 61 if (this == obj) 62 return true; 63 if (obj == null) 64 return false; 65 if (getClass() != obj.getClass()) 66 return false; 67 Bean other = (Bean) obj; 68 if (classPath == null) { 69 if (other.classPath != null) 70 return false; 71 } else if (!classPath.equals(other.classPath)) 72 return false; 73 if (id == null) { 74 if (other.id != null) 75 return false; 76 } else if (!id.equals(other.id)) 77 return false; 78 if (properties == null) { 79 if (other.properties != null) 80 return false; 81 } else if (!properties.equals(other.properties)) 82 return false; 83 return true; 84 } 85 86 public Bean(List<Propertity> properties, String id, String classPath) { 87 super(); 88 this.properties = properties; 89 this.id = id; 90 this.classPath = classPath; 91 } 92 93 public Bean() { 94 } 95 96 }
package myspring_ioc.bean; /** * * @author xujinren * Properties in XML * */ public class Propertity { private String name; private String value; private String ref; public Propertity(String name, String value, String ref) { this.name = name; this.value = value; this.ref = ref; } public String getRef() { return ref; } public void setRef(String ref) { this.ref = ref; } @Override public String toString() { return "Propertity [name=" + name + ", value=" + value + ", ref=" + ref + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((ref == null) ? 0 : ref.hashCode()); result = prime * result + ((value == null) ? 0 : value.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Propertity other = (Propertity) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (ref == null) { if (other.ref != null) return false; } else if (!ref.equals(other.ref)) return false; if (value == null) { if (other.value != null) return false; } else if (!value.equals(other.value)) return false; return true; } public Propertity(String name, String value) { this.name = name; this.value = value; } public Propertity() { super(); } }
ClassPathXmlApplicationContext操作获取bean信息的方法如下:
/** * store bean to beans from Document * @param doc */ private void toStoreBean(Document doc) { Element root = doc.getRootElement(); //get the chidren Element Iterator Iterator it = root.elementIterator(); if(it != null){ Element el = null; Bean bean = null; while(it.hasNext()){ el = (Element) it.next(); bean = getBeanFromElement(el); //add to beans, beans is a parameter , type is List<Bean> beans.add(bean); } } } /** * get Bean from Element * @param el Element */ private Bean getBeanFromElement(Element el) { Bean bean = new Bean(); //get id bean.setId(el.attributeValue("id")); //get classPath bean.setClassPath(el.attributeValue("class")); //get propertity Iterator it = el.elementIterator(); if(it != null){ Element e = null; Propertity propertity = null; while(it.hasNext()){ propertity = new Propertity(); e = (Element) it.next(); //Matching propertity if(e.getName().trim().equalsIgnoreCase("propertity")){ propertity = new Propertity(); if(e.attributeValue("name") != null){ propertity.setName(e.attributeValue("name")); } if(e.attributeValue("value") != null){ propertity.setValue(e.attributeValue("value")); } if(e.attributeValue("ref") != null){ propertity.setRef(e.attributeValue("ref")); } bean.getProperties().add(propertity); } } } return bean; }
上面的beans为一个List<Bean>的对象,用于存储bean。
获取了bean以后,就需要处理bean里面的数据,要根据classPath来反射生成对象,然后还要激活propertity里面存储的相关的set方法达到初始化成员变量。
如下:
/** * to create Object from beans, and store Object to BeanTOn * */ private void storeBeanToBeanTon() { try { for(Bean bean : beans){
//beanTon is a Map<String, Object>, storing the Object beanTon.put(bean.getId(), instance(bean)); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * create object * @param bean * @return * @throws Exception */ private Object instance(Bean bean) throws Exception { Object obj = null; Method[] methods = null; Class<?> cls; String type = null; String setMethodName = null; //If the IOC container has the ID of this bean if(beanTon.containsKey(bean.getId())){ throw new Exception("this bean has common's id"); } //get Object from className using reflex obj = Class.forName(bean.getClassPath()).newInstance(); //get the fields methods = obj.getClass().getDeclaredMethods(); for(Method method : methods){ //if this method is start whth "set" if(method.getName().startsWith("set")){ //Then get the parameter type in this method cls = method.getParameterTypes()[0]; //Get the short name of the parameter type type = cls.getSimpleName(); //Initialize the parameters in the bean for(Propertity protertity : bean.getProperties()){ /*If the parameter is equal to the method and the string beginning with set is removed */ if(protertity.getName() != null && protertity.getName().equalsIgnoreCase(method.getName().substring(3))){ //Then the value in the property has a value and ref has no value if(protertity.getRef() == null && protertity.getValue() != null){ if(type.equals("int")){ //Reverse activation method.invoke(obj, Integer.parseInt(protertity.getValue())); }else if(type.equalsIgnoreCase("Double")){ //Reverse activation method.invoke(obj, Double.parseDouble(protertity.getValue())); }else if(type.equalsIgnoreCase("Float")){ //Reverse activation method.invoke(obj, Float.parseFloat(protertity.getValue())); }else{ //Reverse activation method.invoke(obj, protertity.getValue()); } //if ref != null }else if(protertity.getRef().trim().equalsIgnoreCase(method.getName().substring(3).trim()) && protertity.getValue() == null){ //if beanTon has the Object if(beanTon.containsKey(protertity.getRef())){ method.invoke(obj, beanTon.get(bean.getId())); }else{ Object ob = null; for(Bean bean_1 : beans){ if(protertity.getRef().equals(bean_1.getId())){ ob = instance(bean_1); break; } } method.invoke(obj, ob); } } } } } } return obj; }
上面方法中最关键的是:
获取反向生成对象的类中的set开头的方法,接着从bean里面来取出propertity,一一循环与set开头的方法比较,如果那些方法去掉set后,并且忽略大小比较相等的话,就说明该对象的该成员变量在bean里面配置了初始化的参数。
接着就取出该方法里面的参数的类别,通过类别比较来将propertity的value值的String类型转为相应参数的类型。比如:有一个propertity的name属性为“age”,value = “12”,在bean里面配置的value都为字符串。可是setAge(int age)的方法的参数为int型,这就需要进行强制类型转换为int,再激活该对象的方法:
invoke(obj, Integer.valueOf(propertity.getValue()))
上述的obj为反射生成的对象。
在属性中有一个ref引用如何解决?:在spring中的bean配置是可以引用bean的。
我这里的解决方案是:在bean中如果propertity中有ref,则要先判断beanTon中是否有该相对应的id,因为ref引用的都是bean的id。
1.有,则激活就行
方式:首先判断该propertity的name值和循环到的method的方法名是否对应(method的方法名去掉开头的set后忽略大小写与name比较),如果对应,则:
method.invoke(obj, beanTon.get(propertity.getRef()))
2.没有,则来通过反射生成一下该对象并且初始化参数
上面的函数我封装出来了一个方法:
private Object instance(Bean bean)
那么就需要将那个要生成对象的bean传进去就行,如下:
Object ob = null; for(Bean bean_1 : beans){ if(protertity.getRef().equals(bean_1.getId())){ ob = instance(bean_1); break; } } method.invoke(obj, ob);
至此,完毕,完整的ClassPathXmlApplicationContext代码如下:
package myspring_ioc.org.springframework.context.support.ClassPathXmlApplicationContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.Element; import myspring_ioc.bean.Address; import myspring_ioc.bean.Bean; import myspring_ioc.bean.Propertity; import myspring_ioc.bean.StudentInfo; import myspring_ioc.org.springframework.context.support.ApplicationContext.ApplicationContext; import myspring_ioc.utils.SAXReaderParseXml; /** * * @author xujinren * *parse the bean.xml and create BeanFactory * */ public class ClassPathXmlApplicationContext extends ApplicationContext{ private SAXReaderParseXml saxReader; //Used to store beans obtained from XML private List<Bean> beans = new ArrayList<>(); //Map for storing ID and object private Map<String, Object> beanTon = new HashMap<>(); /** * init class ClassPathXmlApplicationContext and parse xml by * SAXReader using dom4j.jar * @param xmlPath */ public ClassPathXmlApplicationContext(String xmlPath){ //parse the xml saxReader = new SAXReaderParseXml(); Document doc = saxReader.saxReaderParseXml(xmlPath); toStoreBean(doc); storeBeanToBeanTon(); } /** * to create Object from beans, and store Object to BeanTOn * */ private void storeBeanToBeanTon() { try { for(Bean bean : beans){ beanTon.put(bean.getId(), instance(bean)); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * create object * @param bean * @return * @throws Exception */ private Object instance(Bean bean) throws Exception { Object obj = null; Method[] methods = null; Class<?> cls; String type = null; String setMethodName = null; //If the IOC container has the ID of this bean if(beanTon.containsKey(bean.getId())){ throw new Exception("this bean has common's id"); } //get Object from className using reflex obj = Class.forName(bean.getClassPath()).newInstance(); //get the fields methods = obj.getClass().getDeclaredMethods(); for(Method method : methods){ //if this method is start whth "set" if(method.getName().startsWith("set")){ //Then get the parameter type in this method cls = method.getParameterTypes()[0]; //Get the short name of the parameter type type = cls.getSimpleName(); //Initialize the parameters in the bean for(Propertity protertity : bean.getProperties()){ /*If the parameter is equal to the method and the string beginning with set is removed */ if(protertity.getName() != null && protertity.getName().equalsIgnoreCase(method.getName().substring(3))){ //Then the value in the property has a value and ref has no value if(protertity.getRef() == null && protertity.getValue() != null){ if(type.equals("int") || type.equals("Integer")){ //Reverse activation method.invoke(obj, Integer.parseInt(protertity.getValue())); }else if(type.equalsIgnoreCase("Double")){ //Reverse activation method.invoke(obj, Double.parseDouble(protertity.getValue())); }else if(type.equalsIgnoreCase("Float")){ //Reverse activation method.invoke(obj, Float.parseFloat(protertity.getValue())); }else{ //Reverse activation method.invoke(obj, protertity.getValue()); } //if ref != null }else if(protertity.getRef().trim().equalsIgnoreCase(method.getName().substring(3).trim()) && protertity.getValue() == null){ //if beanTon has the Object if(beanTon.containsKey(protertity.getRef())){ method.invoke(obj, beanTon.get(bean.getId())); }else{ Object ob = null; for(Bean bean_1 : beans){ if(protertity.getRef().equals(bean_1.getId())){ ob = instance(bean_1); break; } } method.invoke(obj, ob); } } } } } } return obj; } /** * store bean to beans from Document * @param doc */ private void toStoreBean(Document doc) { Element root = doc.getRootElement(); //get the chidren Element Iterator Iterator it = root.elementIterator(); if(it != null){ Element el = null; Bean bean = null; while(it.hasNext()){ el = (Element) it.next(); bean = getBeanFromElement(el); //add to beans beans.add(bean); } } } /** * get Bean from Element * @param el Element */ private Bean getBeanFromElement(Element el) { Bean bean = new Bean(); //get id bean.setId(el.attributeValue("id")); //get classPath bean.setClassPath(el.attributeValue("class")); //get propertity Iterator it = el.elementIterator(); if(it != null){ Element e = null; Propertity propertity = null; while(it.hasNext()){ propertity = new Propertity(); e = (Element) it.next(); //Matching propertity if(e.getName().trim().equalsIgnoreCase("propertity")){ propertity = new Propertity(); if(e.attributeValue("name") != null){ propertity.setName(e.attributeValue("name")); } if(e.attributeValue("value") != null){ propertity.setValue(e.attributeValue("value")); } if(e.attributeValue("ref") != null){ propertity.setRef(e.attributeValue("ref")); } bean.getProperties().add(propertity); } } } return bean; } @Override public Object getBean(String id) { return beanTon.get(id); } }
测试如下:
测试所需要的pojo类如下:
1 package myspring_ioc.bean; 2 /** 3 * 4 * @author xujinren 5 *the entity of StudentInfo 6 */ 7 public class StudentInfo { 8 private String name; 9 private int age; 10 private Address address; 11 12 @Override 13 public String toString() { 14 return "StudentInfo [name=" + name + ", age=" + age + ", address=" + address + "]"; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 public void setName(String name) { 22 this.name = name; 23 } 24 25 public int getAge() { 26 return age; 27 } 28 29 public void setAge(int age) { 30 this.age = age; 31 } 32 33 public Address getAddress() { 34 return address; 35 } 36 37 @Override 38 public int hashCode() { 39 final int prime = 31; 40 int result = 1; 41 result = prime * result + ((address == null) ? 0 : address.hashCode()); 42 result = prime * result + age; 43 result = prime * result + ((name == null) ? 0 : name.hashCode()); 44 return result; 45 } 46 47 @Override 48 public boolean equals(Object obj) { 49 if (this == obj) 50 return true; 51 if (obj == null) 52 return false; 53 if (getClass() != obj.getClass()) 54 return false; 55 StudentInfo other = (StudentInfo) obj; 56 if (address == null) { 57 if (other.address != null) 58 return false; 59 } else if (!address.equals(other.address)) 60 return false; 61 if (age != other.age) 62 return false; 63 if (name == null) { 64 if (other.name != null) 65 return false; 66 } else if (!name.equals(other.name)) 67 return false; 68 return true; 69 } 70 71 public void setAddress(Address address) { 72 this.address = address; 73 } 74 75 public StudentInfo(String name, int age, Address address) { 76 super(); 77 this.name = name; 78 this.age = age; 79 this.address = address; 80 } 81 82 public StudentInfo() { 83 } 84 }
1 package myspring_ioc.bean; 2 /** 3 * 4 * @author xujinren 5 *the entity of Address 6 */ 7 public class Address { 8 9 //city 10 private String city; 11 //province 12 private String province; 13 14 @Override 15 public String toString() { 16 return "Address [city=" + city + ", province=" + province + "]"; 17 } 18 19 public String getCity() { 20 return city; 21 } 22 23 public void setCity(String city) { 24 this.city = city; 25 } 26 27 public String getProvince() { 28 return province; 29 } 30 31 public void setProvince(String province) { 32 this.province = province; 33 } 34 35 @Override 36 public int hashCode() { 37 final int prime = 31; 38 int result = 1; 39 result = prime * result + ((city == null) ? 0 : city.hashCode()); 40 result = prime * result + ((province == null) ? 0 : province.hashCode()); 41 return result; 42 } 43 44 @Override 45 public boolean equals(Object obj) { 46 if (this == obj) 47 return true; 48 if (obj == null) 49 return false; 50 if (getClass() != obj.getClass()) 51 return false; 52 Address other = (Address) obj; 53 if (city == null) { 54 if (other.city != null) 55 return false; 56 } else if (!city.equals(other.city)) 57 return false; 58 if (province == null) { 59 if (other.province != null) 60 return false; 61 } else if (!province.equals(other.province)) 62 return false; 63 return true; 64 } 65 66 public Address(String city, String province) { 67 this.city = city; 68 this.province = province; 69 } 70 71 public Address() { 72 super(); 73 } 74 75 76 77 78 79 }
测试类如下:
package myspring.test; import myspring_ioc.bean.Address; import myspring_ioc.bean.StudentInfo; import myspring_ioc.org.springframework.context.support.ApplicationContext.ApplicationContext; import myspring_ioc.org.springframework.context.support.ClassPathXmlApplicationContext.ClassPathXmlApplicationContext; public class MySpring_Test { public static void main(String[] args) { ApplicationContext application = new ClassPathXmlApplicationContext("bean.xml"); StudentInfo student = (StudentInfo) application.getBean("student"); System.out.println(student); Address address = (Address) application.getBean("address"); System.out.println(address); } }
结果如下: