Spring 五、使用構造器注入:實現constructor-arg標籤注入ConstructorArgument
阿新 • • 發佈:2019-01-06
第四周: 實現建構函式注入
引入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; } }