1. 程式人生 > >Spring 五、使用構造器注入:實現constructor-arg標籤注入ConstructorArgument

Spring 五、使用構造器注入:實現constructor-arg標籤注入ConstructorArgument

第四周: 實現建構函式注入

引入ConstructorArgument

如何找到合適的構造器: ConstructorResolver

//petstore-v3.xml
 <bean id="petStore" class="org.litespring.service.v3.PetStoreService">
        <constructor-arg ref="accountDao"/>
        <constructor-arg ref="itemDao"/>
        <constructor-arg value="1" />
 </bean>
 <bean id="accountDao" class="org.litespring.dao.v3.AccountDao"/>
 <bean id="itemDao" class="org.litespring.dao.v3.ItemDao"/>

//新加測試方法
@Test
public void TestConstructorArgument(){
    DefaultBeanFactory factory = new DefaultBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    ClassPathResource resource = new ClassPathResource("petstore-v3.xml");
    reader.loadBeanDefiniton(resource);

    BeanDefinition bd = factory.getBeanDefinition("petStore");
    Assert.assertEquals("org.litespring.service.v3.PetStoreService",bd.getBeanClassName());

    ConstructorArgument args =bd.getConstructorArgument();
    List<ConstructorArgument.ValueHolder> valueHolders =  args.getArgumentValues();
    Assert.assertEquals(3,valueHolders.size());

    RuntimeBeanReference ref1 = (RuntimeBeanReference)valueHolders.get(0).getValue();
    Assert.assertEquals("accountDao",ref1.getBeanName());

    RuntimeBeanReference ref2 = (RuntimeBeanReference)valueHolders.get(1).getValue();
    Assert.assertEquals("itemDao",ref2.getBeanName());

    TypedStringValue ref3 = (TypedStringValue)valueHolders.get(2).getValue();
    Assert.assertEquals("1",ref3.getValue());
}
//增加 org.litespring.beans.ConstructorArgument
public class ConstructorArgument {

    private final List<ValueHolder> argumentValues = new LinkedList<ValueHolder>();

    //create a new empty constructorArgumentValues object
    public ConstructorArgument() {
    }

    public void addArgumentValue(ValueHolder valueHolder){
        this.argumentValues.add(valueHolder);
    }

    public List<ValueHolder> getArgumentValues() {
        return Collections.unmodifiableList(argumentValues);
    }

    public int getArgumentCount(){
        return this.argumentValues.size();
    }

    public boolean isEmpty(){
        return this.argumentValues.isEmpty();
    }

    //Clear this holder , remove all argument values
    public void  clear(){
         this.argumentValues.clear();
    }

    /**
     * Holder for a constructor argument value, with an optional type
     * attibute indicating the target type of actual construct argument
     * 高內聚
     * 靜態內部類
     */
    public static class ValueHolder{
        private Object value;
        private String type;
        private String name;

        public ValueHolder(Object value) {
            this.value = value;
        }

        public ValueHolder(Object value, String type) {
            this.value = value;
            this.type = type;
        }

        public ValueHolder(Object value, String type, String name) {
            this.value = value;
            this.type = type;
            this.name = name;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

//修改org.litespring.beans.BeanDefinition,增加ConstructorArgument 
public interface BeanDefinition {

    public static final String SCOPE_SINGLETON = "singleton";
    public static final String SCOPE_PROTOTYPE = "prototype";
    public static final String SCOPE_DEFAULT = "";

    public boolean isSingleton();
    public boolean isPrototype();
    String getScope();
    void setScope(String scope);

    public String getBeanClassName();

    public List<PropertyValue> getPropertyValues();

    public ConstructorArgument getConstructorArgument();
}

//修改BeanDefinition 實現類 org.litespring.beans.factory.support.GenericBeanDefinition
public class GenericBeanDefinition implements BeanDefinition {
    private String id;
    private String beanClassName;
    private String scope = SCOPE_DEFAULT;
    private boolean singleton = true;
    private boolean prototype = false;

    List<PropertyValue> propertyValues = new ArrayList<PropertyValue>();
    private ConstructorArgument constructorArgument = new ConstructorArgument();

    public GenericBeanDefinition(String id, String beanClassName) {
        this.id = id;
        this.beanClassName = beanClassName;
    }

    public String getBeanClassName() {
        return this.beanClassName;
    }

    public List<PropertyValue> getPropertyValues() {
        return this.propertyValues;
    }

    public ConstructorArgument getConstructorArgument() {
        return this.constructorArgument;
    }

    public boolean isSingleton() {
        return this.singleton;
    }

    public boolean isPrototype() {
        return this.prototype;
    }

    public String getScope() {
        return this.scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
        this.singleton = SCOPE_SINGLETON.equals(scope) || SCOPE_DEFAULT.equals(scope);
        this.prototype = SCOPE_PROTOTYPE.equals(scope);
    }
}

//修改org.litespring.beans.factory.xml.XmlBeanDefinitionReader,增加    parseConstructArgElements 方法
public class XmlBeanDefinitionReader {
    private static final String ID_ATTRIBUTE = "id";
    private static final String CLASS_ATTRIBUTE = "class";
    private static final String SCOPE_ATTRIBUTE = "scope";
    private static final String PROPERTY_ELEMENT = "property";
    private static final String REF_ATTRIBUTE = "ref";
    private static final String VALUE_ATTRIBUTE = "value";
    private static final String NAME_ATTRIBUTE = "name";
    private static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg";
    private static final String TYPE_ATTRIBUTE = "type";

    BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this.registry = registry;
    }

    public void loadBeanDefiniton(Resource resource){
        InputStream is = null;
        try {
            is = resource.getInputStream();
            ClassLoader cl = ClassUtils.getDefaultClassLoader();
            SAXReader reader = new SAXReader();
            Document doc = reader.read(is);

            Element rootElement = doc.getRootElement();
            Iterator<Element> iterator = rootElement.elementIterator();
            while(iterator.hasNext()){
                Element nextElement = iterator.next();
                String id = nextElement.attributeValue(ID_ATTRIBUTE);
                String beanClassName = nextElement.attributeValue(CLASS_ATTRIBUTE);
                BeanDefinition bd = new GenericBeanDefinition(id,beanClassName);
                if(nextElement.attributeValue(SCOPE_ATTRIBUTE) != null){
                    bd.setScope(nextElement.attributeValue(SCOPE_ATTRIBUTE));
                }
                parsePropertyElement(nextElement,bd);
                parseConstructArgElements(nextElement,bd);
                this.registry.regiterBeanDefiniton(id, bd);
            }

        } catch (Exception e) {
            throw new BeanDefinitionStoreException("IOException parsing XML document " + resource.getDescription() + "fail ",e.getCause());
        }finally {
            if(is !=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void parseConstructArgElements(Element beanEle, BeanDefinition bd){
        Iterator iterator = beanEle.elementIterator(CONSTRUCTOR_ARG_ELEMENT);
        while (iterator.hasNext()){
            Element ele = (Element) iterator.next();
            parseConstructArgElement(ele,bd);
        }
    }

    public void parseConstructArgElement(Element ele, BeanDefinition bd){
        String typeAttr = ele.attributeValue(TYPE_ATTRIBUTE);
        String nameAttr = ele.attributeValue(NAME_ATTRIBUTE);
        Object value = parsePropertyValue(ele, bd, null);
        ConstructorArgument.ValueHolder valueHolder = new ConstructorArgument.ValueHolder(value);
        if(StringUtils.hasLength(typeAttr)){
            valueHolder.setType(typeAttr);
        }
        if(StringUtils.hasLength(nameAttr)){
            valueHolder.setType(nameAttr);
        }
        //addArgumentValue
        bd.getConstructorArgument().addArgumentValue(valueHolder);
    }

    public void parsePropertyElement(Element beanEle, BeanDefinition bd){
        Iterator iterator = beanEle.elementIterator();
        while (iterator.hasNext()){
            Element propElem = (Element) iterator.next();
            String name = propElem.attributeValue(NAME_ATTRIBUTE);
            if(!StringUtils.hasLength(name)){
                System.out.println("Tag 'property' value must have a 'name' attribue");
                return ;
            }

            Object value = parsePropertyValue(propElem, bd, name);
            PropertyValue pv = new PropertyValue(name,value);
            bd.getPropertyValues().add(pv);
        }

    }

    public Object parsePropertyValue(Element ele,BeanDefinition bd,String propertyName){
        String elementName = (propertyName!=null) ?
                "<property> element for property'"+propertyName + "'":
                "<constructor-arg> element";
        boolean hasRefAttribut = (ele.attribute(REF_ATTRIBUTE)!=null);
        boolean hasValueAttribut = (ele.attribute(VALUE_ATTRIBUTE)!=null);
        if(hasRefAttribut){
            String refName = ele.attributeValue(REF_ATTRIBUTE);
            if(!StringUtils.hasText(refName)){
                //logger.error(elementName + " contains empty 'ref' attribute");
                System.out.println(elementName + " contains empty 'ref' attribute");
            }

            RuntimeBeanReference reference = new RuntimeBeanReference(refName);
            return reference;
        }else if(hasValueAttribut){
            TypedStringValue valueHolder = new TypedStringValue(ele.attributeValue(VALUE_ATTRIBUTE));
            return valueHolder;
        }else{
            throw new RuntimeException(elementName + "must specify a ref or value");

        }
    }
}

//測試方法
   @Test
    public void TestConstructResolver(){
        DefaultBeanFactory factory = new DefaultBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        ClassPathResource resource = new ClassPathResource("petstore-v3.xml");
        reader.loadBeanDefiniton(resource);

        BeanDefinition bd = factory.getBeanDefinition("petStore");
        Assert.assertEquals("org.litespring.service.v3.PetStoreService",bd.getBeanClassName());


        ConstructorResolver resolver = new ConstructorResolver(factory);
        PetStoreService petStore = (PetStoreService)resolver.autowireConstructor(bd);

        //validate arg version,正確的通過此建構函式做了初始化
        Assert.assertEquals(1,petStore.getVersion());

        Assert.assertNotNull(petStore.getAccountDao());
        Assert.assertNotNull(petStore.getItemDao());
    }

//新加類org.litespring.context.support.ConstructorResolver
public class ConstructorResolver {
    protected final Log logger = LogFactory.getLog(getClass());
    protected final ConfigurableBeanFactory beanFactory;

    public ConstructorResolver(ConfigurableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public Object autowireConstructor(final BeanDefinition bd) {
        Constructor<?> constructorToUse = null;
        Object[] argsToUse = null;
        Class<?> beanClass = null;

        try {
            beanClass = this.beanFactory.getBeanClassLoader().loadClass(bd.getBeanClassName());
        } catch (ClassNotFoundException e) {
            throw new BeanCreationException(bd.getID(),"Instantiation of bean failed,can't resolver");
        }
        Constructor<?>[] candidates = beanClass.getConstructors();
        BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(beanFactory);
        ConstructorArgument conArgs = bd.getConstructorArgument();
        SimpleTypeConverter typeConverter = new SimpleTypeConverter();

        for (int i = 0; i<candidates.length; i++){
            Class<?>[] parameterTypes = candidates[i].getParameterTypes();
            if(parameterTypes.length != conArgs.getArgumentCount()){
                //構造器引數數量不一致,跳出當前迴圈進入下一次迴圈
                continue;
            }
            argsToUse = new Object[parameterTypes.length];
            
            boolean result = this.valuesMatchTypes(parameterTypes,
                    conArgs.getArgumentValues(),
                    argsToUse,
                    valueResolver,
                    typeConverter);
            if(result){
                constructorToUse = candidates[i];
                break;
            }
        }
        //no constructor To Use
        if(constructorToUse == null){
            throw new BeanCreationException(bd.getID(),"Can't find a apporiate constructor");
        }
        try {
            return constructorToUse.newInstance(argsToUse);
        } catch (Exception e) {
            throw new BeanCreationException(bd.getID(),"Can't find a create instance user");
        }
    }

    private boolean valuesMatchTypes(Class<?>[] parameterTypes,
             List<ConstructorArgument.ValueHolder> valueHolders,
             Object[] argsToUse,
             BeanDefinitionValueResolver valueResolver,
             SimpleTypeConverter typeConverter){
        for (int i = 0;i<parameterTypes.length; i++){
            Class<?> parameterType = parameterTypes[i];
            ConstructorArgument.ValueHolder valueHolder = valueHolders.get(i);
            //originalValue can be RuntimeBeanReference or TypedStringValue
            Object originalValue = valueHolder.getValue();
            Object resolverValue = valueResolver.resolveValueIfNacessary(originalValue);

            Object convertedValue = null;
            try {
                convertedValue = typeConverter.convertIfNecessary(resolverValue, parameterType);
                argsToUse[i] = convertedValue;
            } catch (TypeMisMatchException e) {
                logger.error(e);
                return false;
            }
        }
        return true;
    }
}