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次构造方法.

这里写图片描述

0 0