Spring记录之模拟IoC(二)
来源:互联网 发布:优绘下载 mac版 编辑:程序博客网 时间:2024/06/08 03:21
模拟Spring IoC容器 2.0
Spring的IoC容器通过解析xml文件,读取当中的配置关系,从而在对象中注入值或其他对象。反射机制在当中扮演重要角色,为了更简便地操作反射,Java中有一种技术Introspector(网上翻译为内省),模拟中,有了它,配置Bean标签的属性和依赖关系,会简便得多。当然,直接用反射也是可以的。
Introspector
- java.beans.Introspector
源码如是说:Introspector 提供一种标准方法来访问目标Java Bean的属性、事件、方法。对于一个Java Bean的这3种信息,Introspector 会别分析,还会分析父类的这3种信息,不论是显式还是隐式。Introspector会根据这些信息创建一个BeanInfo对象来描述这个Java Bean。就像有个Class类来描述class,当中渗透着面向对象思想。
此2.0模拟,运用面向对象思想,把xml解析出来的对象封装到Bean对象中
The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.
该技术的关键方法有
- getBeanInfo(),获得JavaBean的信息。JDK1.7后,增加能够标识分析到哪个父类为止的方法重载
public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass, int flags){...}
//Instrospect一个Java Bean,获取其属性、事件、方法。若已经Instropect过,则从缓存中取相关信息 public static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException { if (!ReflectUtil.isPackageAccessible(beanClass)) { return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo(); } ThreadGroupContext context = ThreadGroupContext.getContext(); BeanInfo beanInfo; synchronized (declaredMethodCache) { beanInfo = context.getBeanInfo(beanClass); } if (beanInfo == null) { beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo(); synchronized (declaredMethodCache) { context.putBeanInfo(beanClass, beanInfo); } } return beanInfo; }
- getPropertyDescriptors(),获得属性的描述,可以采用遍历BeanInfo的方法,来查找、设置类的属性。该方法在BeanInfo类下。Spring的org.springframework.beans.ExtendedBeanInfo继承了BeanInfo.源码如下:
public ExtendedBeanInfo(BeanInfo delegate) throws IntrospectionException { this.delegate = delegate; //获取属性描述器 for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) { try { this.propertyDescriptors.add(pd instanceof IndexedPropertyDescriptor ? new SimpleIndexedPropertyDescriptor((IndexedPropertyDescriptor) pd) : new SimplePropertyDescriptor(pd)); } catch (IntrospectionException ex) { // 可能没有按照JavaBean规范些方法... if (logger.isDebugEnabled()) { logger.debug("Ignoring invalid bean property '" + pd.getName() + "': " + ex.getMessage()); } } } //获取方法描述器 MethodDescriptor[] methodDescriptors = delegate.getMethodDescriptors(); if (methodDescriptors != null) { //获取setter for (Method method : findCandidateWriteMethods(methodDescriptors)) { try { handleCandidateWriteMethod(method); } catch (IntrospectionException ex) { // We're only trying to find candidates, can easily ignore extra ones here... if (logger.isDebugEnabled()) { logger.debug("Ignoring candidate write method [" + method + "]: " + ex.getMessage()); } } } } }
对于内省操作,Apache开发了一套简单、易用的API来操作Bean的属性——BeanUtils工具包,地址http://commons.apache.org/proper/commons-beanutils/
模拟IoC 2.0
目录结构
准备xml文件
<?xml version="1.0" encoding="UTF-8"?><beans> <bean id="chinese" class="spring.beans.Chinese"> <property name="name" value="地球上的中国人"/> </bean> <bean id="english" class="spring.beans.English"> <property name="chinese" ref="chinese"/> </bean></beans>
接收xml解析对象的对象
package spring.config;public class Bean { private String id; private String className; private String scope; private List<Property> properties = new ArrayList<Property>(); ....省略getter setter constructor}////////////////////////////////////////package spring.config;public class Property { private String name; private String value; private String ref; ...省略getter setter constructor}
容器准备
- BeanFactory 标准接口
package spring.container;//容器标准接口public interface BeanFactory { public Object getBean(String id);}
- Dom4jClassPathXmlApplicationContext容器实现
- 首先,在构造方法中,实现xml文件解析。 解析后的对象封装在上述的Bean对象中,当然,此处用Map又封装了一遍,以便通过id属性查找。
- 然后,判断解析出来的对象是否已经注入容器。如果解析出来的对象为空,而且scope为singleton,单例模式,这样才注入容器。容器中的对象默认保持单例。如果不是scope,每次从获取对象时,都创建新对象.
- 创建对象时,配置对象依赖关系,分两种情况,一种是value,注入值,一种是ref,注入对象
public class Dom4jClassPathXmlApplicationContext implements BeanFactory { //beans from xml config file private Map<String, Bean> beans; // container to store bean private Map<String, Object> contextMap = new HashMap<String, Object>(); // parse xml file instantly this class is initializing public Dom4jClassPathXmlApplicationContext(String path) { beans = ConfigParser.dom4jParser(path); if (beans != null) { // loop beans for (Entry<String, Bean> en : beans.entrySet()) { String id = en.getKey(); Bean bean = en.getValue(); Object existBean = contextMap.get("id"); // bean is not initialized and scope is singleton if (existBean == null && bean.getScope().equals("singleton")) { // initialize bean. 需检验是否已经注入 // Bean must be checked if it is already been initialized // before this process Object obj = createBean(bean); // put beans into the container contextMap.put(id, obj); } } } } /** * @param bean * @return */ private Object createBean(Bean bean) { String className = null; // user reflection Class clazz = null; try { className = bean.getClassName(); clazz = Class.forName(className); } catch (ClassNotFoundException | NullPointerException e) { e.printStackTrace(); throw new RuntimeException(className + " not found in xml config file."); } // generate Objection Object object = null; try { object = clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); throw new RuntimeException( "no default constructor defined in " + className); } /** * <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> * <property name="driverClassName" value="${jdbc.driverClass}"/> * <property name="url" value="${jdbc.url}"/> * </bean> */ // inject property if (bean.getProperties() != null) { Object param = null; for (Property properties : bean.getProperties()) { String propName = null; Method setterMethod = null; try { propName = properties.getName(); // using setter to inject value <property name="name" // value="changing"/>, <property name="name" ref="ref"/> setterMethod = BeanUtils.getWriterMethod(object,propName); } catch (NullPointerException e) { e.printStackTrace(); throw new RuntimeException("property name is not defined."); } // inject value into object using setter if (properties.getValue() != null) { String value = properties.getValue(); param = value; } // inject another Object if (properties.getRef() != null) { // get ref instance Object refBean = contextMap.get(properties.getRef()); // refBean is not generated. Generate it by recursion if (refBean == null) { refBean = createBean(beans.get(properties.getRef())); if(beans.get(properties.getRef()).getScope().equals("singleton")){ // put refBean into Container if scope in the xml file is singleton contextMap.put(properties.getRef(), refBean); } } param = refBean; } //setter to inject value or ref try { setterMethod.invoke(object, param); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } return object; } @Override public Object getBean(String id) { Object bean = contextMap.get(id); //if scope is not singleton, then bean does not exist in contextMap and should be null if(bean == null){ bean = createBean(beans.get(id)); } return bean; }}
解析xml文件的解析器
返回的对象封装到Bean对象中,并用Map再次封装
public class ConfigParser { private static Map<String, Bean> beans = new HashMap<String, Bean>();/** * dom4j parse xml file * @param path * @return */ public static Map<String, Bean> dom4jParser(String path) { // 1.parse xml configuration SAXReader reader = new SAXReader(); org.dom4j.Document document = null; try { document = reader.read(Dom4jClassPathXmlApplicationContext.class .getResourceAsStream(path)); } catch (DocumentException e) { e.printStackTrace(); throw new RuntimeException( "please check your configuration file. Make sure it is correct."); } // 2. define xPath to fetch all <bean> String xpath = "//bean"; List<Element> list = document.selectNodes(xpath); if (list.size() > 0) { for (Element beanEle : list) { String id = beanEle.attributeValue("id"); String clazz = beanEle.attributeValue("class"); String scope = beanEle.attributeValue("scope"); // default scope is singleton if (scope == null) { scope = "singleton"; } //Encapsulate the Object into Object Bean Bean bean = new Bean(id, clazz,scope); //loop <property/> List<Element> properties = beanEle.elements("property"); if (properties!=null) { for(Element propertyEle : properties) { String name = propertyEle.attributeValue("name"); String value = propertyEle.attributeValue("value"); String ref = propertyEle.attributeValue("ref"); Property property = new Property(); if(name!=null){ property.setName(name); } if(value!=null){ property.setValue(value); } if(ref!=null){ property.setRef(ref); } bean.getProperties().add(property); } } beans.put(id, bean); } } return beans; }
Introspector获得setter
通过属性描述器,获取该属性的setter方法
public class BeanUtils { /** * @param object,propName * @return setter method in object */ public static Method getWriterMethod(Object object, String propName) { Method method = null; /* * Introspect on a Java Bean and learn about all its properties, exposed * methods, and events. */ try { // 1.analyze bean BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass()); // 2.get propertyDescriptors PropertyDescriptor[] propertyDescriptors = beanInfo .getPropertyDescriptors(); // loop propertyDescriptors if (propertyDescriptors != null) { for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // get attribute name from current propertyDescriptor // 相当于 获取类的成员变量名称 String pName = propertyDescriptor.getName(); // 如果当前的名称等于传入的<property name=""/>的name值 if (pName.equals(propName)) { // get setter method,can return null method = propertyDescriptor.getWriteMethod(); } } } } catch (IntrospectionException e) { e.printStackTrace(); } if (method == null) { throw new RuntimeException("no setter in" + object.getClass()); } return method; }}
测试
- English类中注入了Chinese类,Chinese类在XML文件中配置了name属性值
@Test public void testBean(){ Dom4jClassPathXmlApplicationContext context = new Dom4jClassPathXmlApplicationContext("/bean.xml"); English english = (English)context.getBean("english"); System.out.println(english.getChinese().getName()); }
结果
说明成功在English中注入Chinese实例,也成功往Chinese实例注入name值
- scope默认为singleton,通过构造方法来测试
<!--配置对象关系--><bean id="animal" class="spring.beans.Animal"></bean><bean id="cat" class="spring.beans.Cat" scope="prototype"> <property name="blackCat" ref="blackCat"/></bean><bean id="blackCat" class="spring.beans.BlackCat" scope="prototype"></bean>
@Test//测试1:容器初始化,singleton实例会注入容器(这是隐式创建对象),下面创建了三次Animal对象,结果如何? public void testBean(){ Dom4jClassPathXmlApplicationContext context = new Dom4jClassPathXmlApplicationContext("/bean.xml"); Animal animal = (Animal)context.getBean("animal"); Animal animal2 = (Animal)context.getBean("animal"); }
结果:只调用了1次Animal的构造方法
@Test//测试2:容器初始化,singleton实例会注入容器,其他的不会注入。从容器获取两次Cat对象,Cat对象引用了BlackCat对象,结果如何? public void testBean(){ Dom4jClassPathXmlApplicationContext context = new Dom4jClassPathXmlApplicationContext("/bean.xml"); Cat cat = (Cat)context.getBean("cat"); Cat cat2 = (Cat)context.getBean("cat"); }
结果:Animal是单例,在容器初始化时调用1次构造方法;Cat没有在初始化时注入容器,而是,每次都由容器创建,所以调用2次构造方法;Cat引用了BlackCat,BlackCat不是单例,每次创建Cat也就创建一个BlackCat实例,所以调用2次构造方法.
- Spring记录之模拟IoC(二)
- Spring记录之模拟IoC(一)
- Spring记录之模拟IoC(三)
- Spring IOC之 原理模拟
- Spring 之 模拟Spring中的IOC
- spring之IOC详解二
- spring学习之二:IoC,AOP
- (二)spring 之IOC容器
- Java拾遗之Spring IoC(二)
- Spring框架学习之IOC(二)
- 二、初学SpringMVC+Mybatis之Spring IOC
- Spring之IoC容器解析(二)
- Spring学习之IOC容器(二)
- Spring框架学习之二(IOC机制)
- Spring模拟(DI,IOC)
- 模拟spring的IOC
- spring ioc 模拟
- Spring IOC(DI)模拟
- delegate 用assign还weak?
- Joystick手柄输入
- Class 'PDO' not found 错误
- 出现次数最多的数
- 字符串的全排列和组合
- Spring记录之模拟IoC(二)
- Express-jade-stylus框架web开发
- 如何解决struts2中上传文件大小限制的问题
- linux 64位操作系统安装配置java JDK
- Android笔记37: Sensor.TYPE_ORIENTATION 方向传感器,转向晕了个头
- jsp page指令详解
- 我是一个线程
- bzoj 3531: [Sdoi2014]旅行 树链剖分
- 减肥计划走起