从零开始造Spring02---实现setter注入

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 本节我们将要学习如何实现setter 注入。此博文是学习刘欣老师《从零开始造Spring》的学习笔记。

前言


本节我们将要学习如何实现setter 注入。此博文是学习刘欣老师《从零开始造Spring》的学习笔记。


为啥要实现setter 注入

在上一篇博客中我们实现了Bean实例的生成,但是 Bean与Bean之间的依赖关系我们还没有实现,例如:当A类(Bean) 依赖于B类(Bean)时,我们就需要将B类的实例注入到A类中。常见的注入方式有三种:

- setter 注入

- 构造器注入

- 接口注入

接口注入不常用,此处我们主要实现setter注入和构造器注入。首先,我们这篇博客主要讲的就是如何实现setter注入。


数据结构的表达

xml中的配置

<bean id="petStoreService"
          class="com.jay.spring.service.v2.PetStoreService">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <property name="owner" value="xiangwei"/>
        <property name="version" value="2"/>
    </bean>
    <bean id="accountDao" class="com.jay.spring.dao.v2.AccountDao"></bean>
    <bean id="itemDao" class="com.jay.spring.dao.v2.ItemDao"/>


思考:我们使用BeanDefinition表达了<bean>标签中的id,class。 那么对于property 怎么表达?ref 怎么表达? value 怎么表达?

我们新建一个PropertyValue类用于存放<property> 标签中的name和 ref 或者value。

类图如下:

88eb357d46ba73894fb700b5f8ffbae4_70.png

如果是ref 类型的我们还需要将其实例名保存到RuntimeBeanReference中,如果是value 类型的我们需要将其值保存到TypedStringValue 中

67ddabe7d79efe790794272d32539779_70.png


关键代码实例

GenericBeanDefinition 类,存放property的数据结构

public class GenericBeanDefinition implements BeanDefinition{
        private String beanClassName;
        private List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
        public String getBeanClassName() {
        return this.beanClassName;
    }
     @Override
    public List<PropertyValue> getPropertyValues() {
        return this.propertyValueList;
    }
}


XmlBeanDefinitionReader 类,主要用于解析xml,读取其中的配置

public class XmlBeanDefinitionReader {
    public static final String PROPERTY_ELEMENT = "property";
    public static final String REF_ATTRIBUTE = "ref";
    public static final String VALUE_ATTRIBUTE = "value";
    public static final String NAME_ATTRIBUTE = "name";
     public void parsePropertyElement(Element beanElem, BeanDefinition bd) {
        Iterator iterator = beanElem.elementIterator(PROPERTY_ELEMENT);
        while (iterator.hasNext()) {
            Element propElem = (Element) iterator.next();
            // 取出name 元素
            String propertyName = propElem.attributeValue(NAME_ATTRIBUTE);
              // 元素如果为空直接返回
            if (!StringUtils.hasLength(propertyName)) {
                logger.fatal("Tag 'property' must have a 'name' attribute");
                return;
            }
            Object val = parsePropertyValue(propElem, bd, propertyName);
            PropertyValue pv = new PropertyValue(propertyName, val);
            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";
        //分别取出ref属性和value属性。
        boolean hasRefAttribute = (ele.attribute(REF_ATTRIBUTE) != null);
        boolean hasValueAttribute = (ele.attribute(VALUE_ATTRIBUTE) != null);
        if (hasRefAttribute) {
            String refName = ele.attributeValue(REF_ATTRIBUTE);
            if (!StringUtils.hasText(refName)) {
                logger.error(elementName + " contains empty 'ref' attribute");
            }
            RuntimeBeanReference ref = new RuntimeBeanReference(refName);
            return ref;
        } else if (hasValueAttribute) {
            TypedStringValue typedStringValue = new TypedStringValue(ele.attributeValue(VALUE_ATTRIBUTE));
            return typedStringValue;
        } else {
            throw new RuntimeException(elementName + " must specify a ref or value");
        }
    }
}


读取出来ref 或者value之后,我们如何将其实例化呢?对此我们新建了一个类BeanDefinitionValueResolve来处理value 的值。

代码如下:

public class BeanDefinitionValueResolve {
    private final BeanFactory beanFactory;
    public BeanDefinitionValueResolve(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }
    public Object resolveValueIfNecessary(Object value) {
        if (value instanceof RuntimeBeanReference) {
            RuntimeBeanReference ref = (RuntimeBeanReference) value;
            String refName = ref.getBeanName();
            Object bean = this.beanFactory.getBean(refName);
            return bean;
        } else if (value instanceof TypedStringValue) {
            return ((TypedStringValue) value).getValue();
        } else {
            throw new RuntimeException("the value " + value + " has not implemented");
        }
    }
}


那么如果获取bean,并且设置属性呢:

关键代码如下:(DefaultBeanFactory类中)

public Object createBean(BeanDefinition bd) {
//        创建实例
        Object bean = instantiateBean(bd);
        //设置属性
        populateBean(bd, bean);
//        populateBeanUseCommonBeanUtils(bd,bean);
        return bean;
    }
    private Object instantiateBean(BeanDefinition bd) {
        ClassLoader beanClassLoader = this.getBeanClassLoader();
        String beanClassName = bd.getBeanClassName();
        try {
            Class<?> clz = beanClassLoader.loadClass(beanClassName);
            return clz.newInstance();
        } catch (Exception e) {
            throw new BeanCreationException("create bean for "+ beanClassName +" failed",e);
        }
    }
       /**
     * 调用set方法进行setter注入
     * @param bd
     * @param bean
     */
    protected void populateBean(BeanDefinition bd, Object bean) {
        // 获取一个bean下所有的PropertyValue
        List<PropertyValue> pvs = bd.getPropertyValues();
        if (pvs == null || pvs.isEmpty()) {
            return;
        }
        // 实例化BeanDefinitionValueResolve,并传入当前的DefaultBeanFactory
        BeanDefinitionValueResolve valueResolve = new BeanDefinitionValueResolve(this);
        SimpleTypeCoverter coverter = new SimpleTypeCoverter();
        try {
            for (PropertyValue pv : pvs) {
                String propertyName = pv.getName();
                //对于ref来说就是beanName,对于value 来说就是value
                Object originalValue = pv.getValue();
                Object resolvedValue = valueResolve.resolveValueIfNecessary(originalValue);
//                假设现在originalValue表示的是ref=accountDao,已经通过resolve得到了accountDao对象,接下来
//                如何调用petStoreService的setAccountDao方法?
//                注释:使用到了java.beans 中的Introspector类拿到bean的相关信息,包括其属性,方法
                BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
                PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
                for (PropertyDescriptor pd : pds) {
                    if (pd.getName().equals(propertyName)) {
                        Object convertedValue = coverter.convertIfNecessary(resolvedValue, pd.getPropertyType());
                        //通过反射的方式调用set方法
                        pd.getWriteMethod().invoke(bean, convertedValue);
                        break;
                    }
                }
            }
        } catch (Exception e) {
            throw new BeanCreationException("Failed to obtain BeanInfo for class["+bd.getBeanClassName()+"]",e);
        }
    }


调试如下:

ce044e9e7d3acddd133d6ddb7de99fba_70.jpg


类型转化

类图如下:

92f84ff565c12290b43fd7b56b7651e8_70.png

解析说明:

TypeConverter 类型转化接口,将传入的值转化为其需要的类型。

SimpleTypeCoverter 是TypeConverter接口的一个实现。其依赖于java.beans中的PropertyEditor,其类似于java GUI中的编程,例如:拖拽一个button, 然后,设置其颜色,长度,宽度,这些都属于button的属性,在java.beans中将这些抽象成了一个PropertyEditor 接口。 setAsText(), 例如button 的高度,值是什么跟属性的类型密切相关。


代码示例

public class CustomNumberEditor extends PropertyEditorSupport {
    private final Class<? extends Number> numberClass;
    private final boolean allowEmpty;
    private final NumberFormat numberFormat;
    public CustomNumberEditor(Class<? extends Number> numberClass, boolean allowEmpty) {
        this(numberClass, allowEmpty,null);
    }
    public CustomNumberEditor(Class<? extends Number> numberClass, boolean allowEmpty, NumberFormat numberFormat) {
        if (numberClass == null || !Number.class.isAssignableFrom(numberClass)) {
            throw new IllegalArgumentException("Property class must be a subclass of Number");
        }
        this.numberClass = numberClass;
        this.allowEmpty = allowEmpty;
        this.numberFormat = numberFormat;
    }
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (this.allowEmpty && !StringUtils.hasText(text)) {
            setValue(null);
        } else if (this.numberFormat != null) {
            setValue(NumberUtils.parseNumber(text, this.numberClass, this.numberFormat));
        } else {
            setValue(NumberUtils.parseNumber(text, this.numberClass));
        }
    }
    public void setValue(Object value) {
        if (value instanceof Number) {
            super.setValue(NumberUtils.convertNumberToTargetClass((Number) value, this.numberClass));
        } else {
            super.setValue(value);
        }
    }
    public String getAsText() {
        Object value = getValue();
        if (value == null) {
            return "";
        } else if (this.numberFormat != null) {
            return this.numberFormat.format(value);
        } else {
            return value.toString();
        }
    }
}


SimpleTypeConverter 类


/**
     * 值的类型转化
     * @param value  3
     * @param requiredType  Integer.class
     * @param <T>
     * @return
     * @throws TypeMismatchException
     */
    @Override
    public <T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException {
        // 值能不能直接赋值呢,能,直接返回
        if (ClassUtils.isAssignableValue(requiredType, value)) {
            return (T) value;
        } else {
//            只支持字符串
            if (value instanceof String) {
                PropertyEditor defaultEditor = findDefaultEditor(requiredType);
                try {
                    defaultEditor.setAsText((String) value);
                } catch (IllegalArgumentException e) {
                    //非法参数异常
                    throw new TypeMismatchException(value, requiredType);
                }
                return (T) defaultEditor.getValue();
            } else {
                throw new RuntimeException("Todo : can't convert value for "+value +" class:"+requiredType);
            }
        }
    }
    private PropertyEditor findDefaultEditor(Class<?> requiredType) {
        // 查找DefaultEditor
        PropertyEditor defaultEditor = this.getDefaultEditor(requiredType);
        if (defaultEditor == null) {
            throw new RuntimeException("Editor for" + requiredType + "has not been implemented");
        }
        return defaultEditor;
    }
    public PropertyEditor getDefaultEditor(Class<?> requiredType) {
        if (defaultEditors == null) {
            createDefaultEditors();
        }
        return defaultEditors.get(requiredType);
    }
    private void createDefaultEditors() {
        this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);
        this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
        this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
        this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
        this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
    }


———————————-分割线——————————————————–


使用common-beanutil设置bean的属性

说明: Commons BeanUtils 类的

BeanUtils.setProperty(bean, propertyName,propertyValue)

bean : java对象,例如petStoreService

propertyName,例如: “version”,”accountDao”

propertyValue,例如:3, accountDao对象


代码示例

private void populateBeanUseCommonBeanUtils(BeanDefinition bd, Object bean) {
        List<PropertyValue> pvs = bd.getPropertyValues();
        if (pvs == null || pvs.isEmpty()) {
            return;
        }
        BeanDefinitionValueResolve valueResolve = new BeanDefinitionValueResolve(this);
        try {
            for (PropertyValue pv : pvs) {
                String propertyName = pv.getName();
                Object originalValue = pv.getValue();
                Object resolve = valueResolve.resolveValueIfNecessary(originalValue);
                BeanUtils.copyProperty(bean, propertyName, resolve);
            }
        } catch (Exception e) {
            throw new BeanDefinitionException("Populate bean property failed for["+bd.getBeanClassName()+"");
        }
    }


———————-答疑总结—————————-


职责分离的问题:解析的时候只做解析的事,不要做额外的工作。

e369437b32e73f66259d0ae01ad12182_70.jpg

Open Close 对修改封闭,对扩展开放,例如:AbstractApplicationContext。流程定下来了。通过

public abstract Resource getResourceByPath(String configFile);


进行扩展。

3. 单一职责

4. 对接口编程,不对实现编程

5. 优先使用组合。

6. 现在PropertyValue中value有两种类型,是否可以封装成两种PropertyValue

RuntimeBeanReferencePropertyValue和TypedStringValuePropertyValue呢?

答:

1. PropertyValue类中新增一个resolve的抽象方法

public abstract Object resolve(BeanFactory beanFactory);

1.RuntimeBeanReferencePropertyValue类

public class RuntimeBeanReferencePropertyValue extends PropertyValue {
    public RuntimeBeanReferencePropertyValue(String name,String value) {
        this.name = name;
        this.value = value;
    }
    @Override
    public Object resolve(BeanFactory factory) {
        return factory.getBean(name);
    }
}


TypedStringValuePropertyValue类

public class TypedStringValuePropertyValue extends PropertyValue {
    public TypedStringValuePropertyValue(String name,String value) {
        this.name = name;
        this.value = value;
    }
    @Override
    public Object resolve(BeanFactory factory) {
        return value;
    }
}


XmlBeanDefinitionReader中做的修改

public PropertyValue parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
        String elementName = (propertyName != null) ?
                "<property> element for property '" + propertyName + "'" :
                "<constructor-arg> element";
        boolean hasRefAttribute = (ele.attribute(REF_ATTRIBUTE) != null);
        boolean hasValueAttribute = (ele.attribute(VALUE_ATTRIBUTE) != null);
        if (hasRefAttribute) {
            String refName = ele.attributeValue(REF_ATTRIBUTE);
            if (!StringUtils.hasText(refName)) {
                logger.error(elementName + " contains empty 'ref' attribute");
            }
            PropertyValue ref = new RuntimeBeanReferencePropertyValue(ele.attributeValue(NAME_ATTRIBUTE),refName);
            return ref;
        } else if (hasValueAttribute) {
            PropertyValue typedStringValue = new TypedStringValuePropertyValue(ele.attributeValue(NAME_ATTRIBUTE),ele.attributeValue(VALUE_ATTRIBUTE));
            return typedStringValue;
        } else {
            throw new RuntimeException(elementName + " must specify a ref or value");
        }
    }


参考源代码

https://github.com/XWxiaowei/spring-learn/releases/tag/testcase-3


相关文章
|
8月前
|
XML Java 开发者
Spring Boot中的bean注入方式和原理
Spring Boot中的bean注入方式和原理
769 0
|
3月前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
196 3
|
3月前
|
Java 测试技术 程序员
为什么Spring不推荐@Autowired用于字段注入?
作为Java程序员,Spring框架在日常开发中使用频繁,其依赖注入机制带来了极大的便利。然而,尽管@Autowired注解简化了依赖注入,Spring官方却不推荐在字段上使用它。本文将探讨字段注入的现状及其存在的问题,如难以进行单元测试、违反单一职责原则及易引发NPE等,并介绍为何Spring推荐构造器注入,包括增强代码可读性和维护性、方便单元测试以及避免NPE等问题。通过示例代码展示如何将字段注入重构为构造器注入,提高代码质量。
121 1
|
28天前
|
Java Spring
一键注入 Spring 成员变量,顺序编程
介绍了一款针对Spring框架开发的插件,旨在解决开发中频繁滚动查找成员变量注入位置的问题。通过一键操作(如Ctrl+1),该插件可自动在类顶部添加`@Autowired`注解及其成员变量声明,同时保持光标位置不变,有效提升开发效率和代码编写流畅度。适用于IntelliJ IDEA 2023及以上版本。
一键注入 Spring 成员变量,顺序编程
|
8月前
|
XML Java 程序员
Spring6框架中依赖注入的多种方式(推荐构造器注入)
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
121 3
|
3月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
78 1
|
5月前
|
XML Java 数据格式
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
这篇文章是Spring5框架的实战教程,主题是IOC容器中Bean的集合属性注入,通过XML配置方式。文章详细讲解了如何在Spring中注入数组、List、Map和Set类型的集合属性,并提供了相应的XML配置示例和Java类定义。此外,还介绍了如何在集合中注入对象类型值,以及如何使用Spring的util命名空间来实现集合的复用。最后,通过测试代码和结果展示了注入效果。
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
|
5月前
|
缓存 Java 数据库连接
Spring Boot 资源文件属性配置,紧跟技术热点,为你的应用注入灵动活力!
【8月更文挑战第29天】在Spring Boot开发中,资源文件属性配置至关重要,它让开发者能灵活定制应用行为而不改动代码,极大提升了可维护性和扩展性。Spring Boot支持多种配置文件类型,如`application.properties`和`application.yml`,分别位于项目的resources目录下。`.properties`文件采用键值对形式,而`yml`文件则具有更清晰的层次结构,适合复杂配置。此外,Spring Boot还支持占位符引用和其他外部来源的属性值,便于不同环境下覆盖默认配置。通过合理配置,应用能快速适应各种环境与需求变化。
57 0
|
5月前
|
安全 Java 开发者
开发者必看!@Resource与private final的较量,Spring Boot注入技巧大揭秘,你不可不知的细节!
【8月更文挑战第29天】Spring Boot作为热门Java框架,其依赖注入机制备受关注。本文通过对比@Resource(JSR-250规范)和@Autowired(Spring特有),并结合private final声明的字段注入,详细探讨了两者的区别与应用场景。通过示例代码展示了@Resource按名称注入及@Autowired按类型注入的特点,并分析了它们在注入时机、依赖性、线程安全性和单一职责原则方面的差异,帮助开发者根据具体需求选择最合适的注入策略。
209 0
|
6月前
|
Java Spring
spring注入的几种方式
spring注入的几种方式
33 0