手写Spring:第18章-数据类型转换工厂设计实现

news2024/9/27 21:21:38

文章目录

  • 一、目标:数据类型转换工厂
  • 二、设计:数据类型转换工厂
  • 三、实现:数据类型转换工厂
    • 3.1 工程结构
    • 3.2 数据类型转换工厂类图
    • 3.3 定义类型转换接口
      • 3.3.1 类型转换处理接口
      • 3.3.2 类型转换工厂
      • 3.3.3 通用类型转换接口
      • 3.3.4 类型转换注册接口
    • 3.4 实现类型转换服务
      • 3.4.1 数字工具类
      • 3.4.2 字符串转数字类型转换工厂
      • 3.4.3 类型转换抽象接口
      • 3.4.4 通用类型转换类
      • 3.4.5 实现类型转换服务
      • 3.4.6 创建类型转换工厂
    • 3.5 类型转换服务使用
      • 3.5.1 Bean工厂接口添加方法
      • 3.5.2 配置Bean工厂接口
      • 3.5.3 抽象Bean工厂基类实现Bean工厂接口
      • 3.5.4 应用上下文抽象类
      • 3.5.5 实现默认Bean创建的抽象Bean工厂超类
  • 四、测试:数据类型转换工厂
    • 4.1 添加测试配置
      • 4.1.1 老公类
      • 4.1.2 字符串转Int类型
      • 4.1.3 字符串转LocalDate
      • 4.1.4 类型转换工厂
      • 4.1.5 Spring属性配置文件
    • 4.2 单元测试
      • 4.2.1 单元测试
      • 4.2.2 字符串转Int测试
      • 4.2.3 字符串转数字测试
  • 五、总结:数据类型转换工厂

一、目标:数据类型转换工厂

💡 数据类型转换在 Spring 中是如何实现?

  • 类型转换也叫做 数据转换。比如:StringIntegerStringDateDoubleLong 等等。
  • 但是这些操作不能在已经使用框架的情况下还需要手动处理,所以要把功能扩展到 Spring 框架中。

二、设计:数据类型转换工厂

💡 设计:数据类型转换

  • 将一个简单的类型转换操作抽象成框架,那么它需要一个标准的接口。谁实现这个接口就具备类型转换的具体实现,提供类型转换的能力。
  • 那么有了这样的接口后,还需要类型转换服务的注册、工厂等内容,才可以把类型转换抽象成一个组件服务。

在这里插入图片描述

  • 首先从工厂出发,我们需要实现一个 ConversionServiceFactoryBean 来对类型转换服务进行操作。
  • 实现类型转换的服务,需要定义: Converter 转换类型、ConverterRegistry 注册类型转换功能。
  • 另外转换类型的操作较多,所以这里也会定义一个类型转换工厂 ConverterFactory 各个具体的转换操作来实现这个工厂接口。

三、实现:数据类型转换工厂

3.1 工程结构

spring-step-17
|-src
  |-main
  | |-java
  |   |-com.lino.springframework
  |     |-aop
  |     | |-aspectj
  |     | | |-AspectJExpressionPointcut.java
  |     | | |-AspectJExpressionPointcutAdvisor.java
  |     | |-framework
  |     | | |-adapter
  |     | | | |-MethodBeforeAdviceInterceptor.java
  |     | | |-autoproxy
  |     | | | |-DefaultAdvisorAutoProxyCreator.java
  |     | | |-AopProxy.java
  |     | | |-Cglib2AopProxy.java
  |     | | |-JdkDynamicAopProxy.java
  |     | | |-ProxyFactory.java
  |     | | |-ReflectiveMethodInvocation.java
  |     | |-AdvisedSupport.java
  |     | |-Advisor.java
  |     | |-BeforeAdvice.java
  |     | |-ClassFilter.java
  |     | |-MethodBeforeAdvice.java
  |     | |-MethodMatcher.java
  |     | |-Pointcut.java
  |     | |-PointcutAdvisor.java
  |     | |-TargetSource.java
  |     |-beans
  |     | |-factory
  |     | | |-annotation
  |     | | | |-Autowired.java
  |     | | | |-AutowiredAnnotationBeanPostProcessor.java
  |     | | | |-Qualifier.java
  |     | | | |-Value.java
  |     | | |-config
  |     | | | |-AutowireCapableBeanFactory.java
  |     | | | |-BeanDefinition.java
  |     | | | |-BeanFactoryPostProcessor.java
  |     | | | |-BeanPostProcessor.java
  |     | | | |-BeanReference.java
  |     | | | |-ConfigurableBeanFactory.java
  |     | | | |-InstantiationAwareBeanPostProcessor.java
  |     | | | |-SingletonBeanRegistry.java
  |     | | |-support
  |     | | | |-AbstractAutowireCapableBeanFactory.java
  |     | | | |-AbstractBeabDefinitionReader.java
  |     | | | |-AbstractBeabFactory.java
  |     | | | |-BeabDefinitionReader.java
  |     | | | |-BeanDefinitionRegistry.java
  |     | | | |-CglibSubclassingInstantiationStrategy.java
  |     | | | |-DefaultListableBeanFactory.java
  |     | | | |-DefaultSingletonBeanRegistry.java
  |     | | | |-DisposableBeanAdapter.java
  |     | | | |-FactoryBeanRegistrySupport.java
  |     | | | |-InstantiationStrategy.java
  |     | | | |-SimpleInstantiationStrategy.java
  |     | | |-xml
  |     | | | |-XmlBeanDefinitionReader.java
  |     | | |-Aware.java
  |     | | |-BeanClassLoaderAware.java
  |     | | |-BeanFactory.java
  |     | | |-BeanFactoryAware.java
  |     | | |-BeanNameAware.java
  |     | | |-ConfigurableListableBeanFactory.java
  |     | | |-DisposableBean.java
  |     | | |-FactoryBean.java
  |     | | |-HierarcgicalBeanFactory.java
  |     | | |-InitializingBean.java
  |     | | |-ListableBeanFactory.java
  |     | | |-ObjectFactory.java
  |     | | |-PropertyPlaceholderConfigurer.java
  |     | |-BeansException.java
  |     | |-PropertyValue.java
  |     | |-PropertyValues.java
  |     |-context
  |     | |-annotation
  |     | | |-ClassPathBeanDefinitionScanner.java
  |     | | |-ClassPathScanningCandidateComponentProvider.java
  |     | | |-Scope.java
  |     | |-event
  |     | | |-AbstractApplicationEventMulticaster.java
  |     | | |-ApplicationContextEvent.java
  |     | | |-ApplicationEventMulticaster.java
  |     | | |-ContextclosedEvent.java
  |     | | |-ContextRefreshedEvent.java
  |     | | |-SimpleApplicationEventMulticaster.java
  |     | |-support
  |     | | |-AbstractApplicationContext.java
  |     | | |-AbstractRefreshableApplicationContext.java
  |     | | |-AbstractXmlApplicationContext.java
  |     | | |-ApplicationContextAwareProcessor.java
  |     | | |-ClassPathXmlApplicationContext.java
  |     | | |-ConversionServiceFactoryBean.java
  |     | |-ApplicationContext.java
  |     | |-ApplicationContextAware.java
  |     | |-ApplicationEvent.java
  |     | |-ApplicationEventPublisher.java
  |     | |-ApplicationListener.java
  |     | |-ConfigurableApplicationContext.java
  |     |-core
  |     | |-convert
  |     | | |-converter
  |     | | | |-Converter.java
  |     | | | |-ConverterFactory.java
  |     | | | |-ConverterRegistry.java
  |     | | | |-GenericConverter.java
  |     | | |-support
  |     | | | |-DefaultConversionService.java
  |     | | | |-GenericConversionService.java
  |     | | | |-StringToNumberConverterFactory.java
  |     | | |-ConversionService
  |     | |-io
  |     | | |-ClassPathResource.java
  |     | | |-DefaultResourceLoader.java
  |     | | |-FileSystemResource.java
  |     | | |-Resource.java
  |     | | |-ResourceLoader.java
  |     | | |-UrlResource.java
  |     |-stereotype
  |     | |-Component.java
  |     |-util
  |     | |-ClassUtils.java
  |     | |-StringValueResolver.java
  |-test
    |-java
      |-com.lino.springframework.test
                |-bean
                | |-Husband.java
                |-converter
                | |-ConvertersFactoryBean.java
                | |-StringToIntegerConverter.java
                | |-StringToLocalDateConverter.java
                |-ApiTest.java
    |-resources
      |-spring.xml

3.2 数据类型转换工厂类图

在这里插入图片描述

  • 首先,通过添加类型转换接口,类型转换工厂和类型转换的具体操作服务,选择需要被转换的类型,如字符串类型转换为数值类型。
  • 然后,通过与 Spring Bean 工厂的整合把类型转换的服务包装进来,便于配置 Bean 对象的属性信息 applyPropertyValues,在填充属性时可以进行自动转换处理。

3.3 定义类型转换接口

3.3.1 类型转换处理接口

Converter.java

package com.lino.springframework.core.convert.converter;

/**
 * @description: 类型转换处理接口
 */
public interface Converter<S, T> {

    /**
     * 类型转换
     *
     * @param source 来源对象
     * @return 转换后的对象
     */
    T convert(S source);
}

3.3.2 类型转换工厂

ConverterFactory.java

package com.lino.springframework.core.convert.converter;

/**
 * @description: 类型转换工厂
 */
public interface ConverterFactory<S, R> {

    /**
     * 获取类型转换对象
     *
     * @param targetType 类类型
     * @param <T>        泛型
     * @return 类型转换对象
     */
    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

3.3.3 通用类型转换接口

GenericConverter.java

package com.lino.springframework.core.convert.converter;

import cn.hutool.core.lang.Assert;
import java.util.Set;

/**
 * @description: 通用的转换接口
 */
public interface GenericConverter {

    /**
     * 获取类型转换列表
     */
    Set<ConvertiblePair> getConvertibleTypes();

    /**
     * 类型转换
     *
     * @param source     目标对象
     * @param sourceType 目标类型
     * @param targetType 转换类型
     * @return 转换后的对象
     */
    Object convert(Object source, Class sourceType, Class targetType);

    final class ConvertiblePair {

        private final Class<?> sourceType;

        private final Class<?> targetType;

        public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
            Assert.notNull(sourceType, "Source type must not be null");
            Assert.notNull(targetType, "Target type must not be null");
            this.sourceType = sourceType;
            this.targetType = targetType;
        }

        public Class<?> getSourceType() {
            return this.sourceType;
        }

        public Class<?> getTargetType() {
            return this.targetType;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null || obj.getClass() != ConvertiblePair.class) {
                return false;
            }
            ConvertiblePair other = (ConvertiblePair) obj;
            return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType);
        }

        @Override
        public int hashCode() {
            return this.sourceType.hashCode() * 31 + this.targetType.hashCode();
        }
    }
}

3.3.4 类型转换注册接口

ConverterRegistry.java

package com.lino.springframework.core.convert.converter;

/**
 * @description: 类型转换注册接口
 */
public interface ConverterRegistry {

    /**
     * 添加类型转换对象
     *
     * @param converter 类型转换对象
     */
    void addConverter(Converter<?, ?> converter);

    /**
     * 添加类型转换对象
     *
     * @param converter 通用的转换接口
     */
    void addConverter(GenericConverter converter);

    /**
     * 添加类型转换工厂
     *
     * @param converterFactory 类型转换工厂
     */
    void addConverterFactory(ConverterFactory<?, ?> converterFactory);
}
  • ConverterConverterFactoryConverterRegistry,都是用于定义类型转换操作的相关接口。

3.4 实现类型转换服务

3.4.1 数字工具类

NumberUtils.java

package com.lino.springframework.util;

import cn.hutool.core.lang.Assert;
import com.sun.istack.internal.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * @description: 数字工具类
 */
public class NumberUtils {

    private static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE);

    private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);

    /**
     * Standard number types (all immutable):
     * Byte, Short, Integer, Long, BigInteger, Float, Double, BigDecimal.
     */
    public static final Set<Class<?>> STANDARD_NUMBER_TYPES;

    static {
        Set<Class<?>> numberTypes = new HashSet<>(8);
        numberTypes.add(Byte.class);
        numberTypes.add(Short.class);
        numberTypes.add(Integer.class);
        numberTypes.add(Long.class);
        numberTypes.add(BigInteger.class);
        numberTypes.add(Float.class);
        numberTypes.add(Double.class);
        numberTypes.add(BigDecimal.class);
        STANDARD_NUMBER_TYPES = Collections.unmodifiableSet(numberTypes);
    }

    /**
     * Convert the given number into an instance of the given target class.
     *
     * @param number      the number to convert
     * @param targetClass the target class to convert to
     * @return the converted number
     * @throws IllegalArgumentException if the target class is not supported
     *                                  (i.e. not a standard Number subclass as included in the JDK)
     * @see Byte
     * @see Short
     * @see Integer
     * @see Long
     * @see BigInteger
     * @see Float
     * @see Double
     * @see BigDecimal
     */
    @SuppressWarnings("unchecked")
    public static <T extends Number> T convertNumberToTargetClass(Number number, Class<T> targetClass)
            throws IllegalArgumentException {

        Assert.notNull(number, "Number must not be null");
        Assert.notNull(targetClass, "Target class must not be null");

        if (targetClass.isInstance(number)) {
            return (T) number;
        } else if (Byte.class == targetClass) {
            long value = checkedLongValue(number, targetClass);
            if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
                raiseOverflowException(number, targetClass);
            }
            return (T) Byte.valueOf(number.byteValue());
        } else if (Short.class == targetClass) {
            long value = checkedLongValue(number, targetClass);
            if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
                raiseOverflowException(number, targetClass);
            }
            return (T) Short.valueOf(number.shortValue());
        } else if (Integer.class == targetClass) {
            long value = checkedLongValue(number, targetClass);
            if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
                raiseOverflowException(number, targetClass);
            }
            return (T) Integer.valueOf(number.intValue());
        } else if (Long.class == targetClass) {
            long value = checkedLongValue(number, targetClass);
            return (T) Long.valueOf(value);
        } else if (BigInteger.class == targetClass) {
            if (number instanceof BigDecimal) {
                // do not lose precision - use BigDecimal's own conversion
                return (T) ((BigDecimal) number).toBigInteger();
            } else {
                // original value is not a Big* number - use standard long conversion
                return (T) BigInteger.valueOf(number.longValue());
            }
        } else if (Float.class == targetClass) {
            return (T) Float.valueOf(number.floatValue());
        } else if (Double.class == targetClass) {
            return (T) Double.valueOf(number.doubleValue());
        } else if (BigDecimal.class == targetClass) {
            // always use BigDecimal(String) here to avoid unpredictability of BigDecimal(double)
            // (see BigDecimal javadoc for details)
            return (T) new BigDecimal(number.toString());
        } else {
            throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" +
                    number.getClass().getName() + "] to unsupported target class [" + targetClass.getName() + "]");
        }
    }

    /**
     * Check for a {@code BigInteger}/{@code BigDecimal} long overflow
     * before returning the given number as a long value.
     *
     * @param number      the number to convert
     * @param targetClass the target class to convert to
     * @return the long value, if convertible without overflow
     * @throws IllegalArgumentException if there is an overflow
     * @see #raiseOverflowException
     */
    private static long checkedLongValue(Number number, Class<? extends Number> targetClass) {
        BigInteger bigInt = null;
        if (number instanceof BigInteger) {
            bigInt = (BigInteger) number;
        } else if (number instanceof BigDecimal) {
            bigInt = ((BigDecimal) number).toBigInteger();
        }
        // Effectively analogous to JDK 8's BigInteger.longValueExact()
        if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) {
            raiseOverflowException(number, targetClass);
        }
        return number.longValue();
    }

    /**
     * Raise an <em>overflow</em> exception for the given number and target class.
     *
     * @param number      the number we tried to convert
     * @param targetClass the target class we tried to convert to
     * @throws IllegalArgumentException if there is an overflow
     */
    private static void raiseOverflowException(Number number, Class<?> targetClass) {
        throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" +
                number.getClass().getName() + "] to target class [" + targetClass.getName() + "]: overflow");
    }

    /**
     * Parse the given {@code text} into a {@link Number} instance of the given
     * target class, using the corresponding {@code decode} / {@code valueOf} method.
     * <p>Trims all whitespace (leading, trailing, and in between characters) from
     * the input {@code String} before attempting to parse the number.
     * <p>Supports numbers in hex format (with leading "0x", "0X", or "#") as well.
     *
     * @param text        the text to convert
     * @param targetClass the target class to parse into
     * @return the parsed number
     * @throws IllegalArgumentException if the target class is not supported
     *                                  (i.e. not a standard Number subclass as included in the JDK)
     * @see Byte#decode
     * @see Short#decode
     * @see Integer#decode
     * @see Long#decode
     * @see #decodeBigInteger(String)
     * @see Float#valueOf
     * @see Double#valueOf
     * @see BigDecimal#BigDecimal(String)
     */
    @SuppressWarnings("unchecked")
    public static <T extends Number> T parseNumber(String text, Class<T> targetClass) {
        Assert.notNull(text, "Text must not be null");
        Assert.notNull(targetClass, "Target class must not be null");
        String trimmed = trimAllWhitespace(text);

        if (Byte.class == targetClass) {
            return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed));
        } else if (Short.class == targetClass) {
            return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed));
        } else if (Integer.class == targetClass) {
            return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed));
        } else if (Long.class == targetClass) {
            return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed));
        } else if (BigInteger.class == targetClass) {
            return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed));
        } else if (Float.class == targetClass) {
            return (T) Float.valueOf(trimmed);
        } else if (Double.class == targetClass) {
            return (T) Double.valueOf(trimmed);
        } else if (BigDecimal.class == targetClass || Number.class == targetClass) {
            return (T) new BigDecimal(trimmed);
        } else {
            throw new IllegalArgumentException(
                    "Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]");
        }
    }

    /**
     * Parse the given {@code text} into a {@link Number} instance of the
     * given target class, using the supplied {@link NumberFormat}.
     * <p>Trims the input {@code String} before attempting to parse the number.
     *
     * @param text         the text to convert
     * @param targetClass  the target class to parse into
     * @param numberFormat the {@code NumberFormat} to use for parsing (if
     *                     {@code null}, this method falls back to {@link #parseNumber(String, Class)})
     * @return the parsed number
     * @throws IllegalArgumentException if the target class is not supported
     *                                  (i.e. not a standard Number subclass as included in the JDK)
     * @see NumberFormat#parse
     * @see #convertNumberToTargetClass
     * @see #parseNumber(String, Class)
     */
    public static <T extends Number> T parseNumber(
            String text, Class<T> targetClass, @Nullable NumberFormat numberFormat) {

        if (numberFormat != null) {
            Assert.notNull(text, "Text must not be null");
            Assert.notNull(targetClass, "Target class must not be null");
            DecimalFormat decimalFormat = null;
            boolean resetBigDecimal = false;
            if (numberFormat instanceof DecimalFormat) {
                decimalFormat = (DecimalFormat) numberFormat;
                if (BigDecimal.class == targetClass && !decimalFormat.isParseBigDecimal()) {
                    decimalFormat.setParseBigDecimal(true);
                    resetBigDecimal = true;
                }
            }
            try {
                Number number = numberFormat.parse(trimAllWhitespace(text));
                return convertNumberToTargetClass(number, targetClass);
            } catch (ParseException ex) {
                throw new IllegalArgumentException("Could not parse number: " + ex.getMessage());
            } finally {
                if (resetBigDecimal) {
                    decimalFormat.setParseBigDecimal(false);
                }
            }
        } else {
            return parseNumber(text, targetClass);
        }
    }

    public static String trimAllWhitespace(String str) {
        if (!hasLength(str)) {
            return str;
        }

        int len = str.length();
        StringBuilder sb = new StringBuilder(str.length());
        for (int i = 0; i < len; i++) {
            char c = str.charAt(i);
            if (!Character.isWhitespace(c)) {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    public static boolean hasLength(@Nullable String str) {
        return (str != null && !str.isEmpty());
    }

    /**
     * Determine whether the given {@code value} String indicates a hex number,
     * i.e. needs to be passed into {@code Integer.decode} instead of
     * {@code Integer.valueOf}, etc.
     */
    private static boolean isHexNumber(String value) {
        int index = (value.startsWith("-") ? 1 : 0);
        return (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index));
    }

    /**
     * Decode a {@link BigInteger} from the supplied {@link String} value.
     * <p>Supports decimal, hex, and octal notation.
     *
     * @see BigInteger#BigInteger(String, int)
     */
    private static BigInteger decodeBigInteger(String value) {
        int radix = 10;
        int index = 0;
        boolean negative = false;

        // Handle minus sign, if present.
        if (value.startsWith("-")) {
            negative = true;
            index++;
        }

        // Handle radix specifier, if present.
        if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
            index += 2;
            radix = 16;
        } else if (value.startsWith("#", index)) {
            index++;
            radix = 16;
        } else if (value.startsWith("0", index) && value.length() > 1 + index) {
            index++;
            radix = 8;
        }

        BigInteger result = new BigInteger(value.substring(index), radix);
        return (negative ? result.negate() : result);
    }

}

3.4.2 字符串转数字类型转换工厂

StringToNumberConverterFactory.java

package com.lino.springframework.core.convert.support;

import com.lino.springframework.core.convert.converter.Converter;
import com.lino.springframework.core.convert.converter.ConverterFactory;
import com.lino.springframework.util.NumberUtils;
import com.sun.istack.internal.Nullable;

/**
 * @description: 字符串转数字类型转换工厂
 */
public class StringToNumberConverterFactory implements ConverterFactory<String, Number> {

    @Override
    public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToNumber<>(targetType);
    }

    private static class StringToNumber<T extends Number> implements Converter<String, T> {

        private final Class<T> targetType;

        public StringToNumber(Class<T> targetType) {
            this.targetType = targetType;
        }

        @Override
        @Nullable
        public T convert(String source) {
            if (source.isEmpty()) {
                return null;
            }
            return NumberUtils.parseNumber(source, this.targetType);
        }
    }
}

3.4.3 类型转换抽象接口

ConversionService.java

package com.lino.springframework.core.convert;

import com.sun.istack.internal.Nullable;

/**
 * @description: 类型转换抽象接口
 */
public interface ConversionService {

    /**
     * 判断类型转换
     *
     * @param sourceType 目标对象类型
     * @param targetType 结果对象类型
     * @return 是否转换
     */
    boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);

    /**
     * 类型转换
     *
     * @param source     目标对象
     * @param targetType 结果类型
     * @param <T>        泛型
     * @return 转换后的对象
     */
    <T> T convert(Object source, Class<T> targetType);
}

3.4.4 通用类型转换类

GenericConversionService.java

package com.lino.springframework.core.convert.support;

import com.lino.springframework.core.convert.ConversionService;
import com.lino.springframework.core.convert.converter.Converter;
import com.lino.springframework.core.convert.converter.ConverterFactory;
import com.lino.springframework.core.convert.converter.ConverterRegistry;
import com.lino.springframework.core.convert.converter.GenericConverter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @description: 通用类型转换类
 */
public class GenericConversionService implements ConversionService, ConverterRegistry {

    private Map<GenericConverter.ConvertiblePair, GenericConverter> converters = new HashMap<>();

    @Override
    public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
        GenericConverter converter = getConverter(sourceType, targetType);
        return converter != null;
    }

    @Override
    public <T> T convert(Object source, Class<T> targetType) {
        Class<?> sourceType = source.getClass();
        GenericConverter converter = getConverter(sourceType, targetType);
        return (T) converter.convert(source, sourceType, targetType);
    }

    @Override
    public void addConverter(Converter<?, ?> converter) {
        GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter);
        ConverterAdapter converterAdapter = new ConverterAdapter(typeInfo, converter);
        for (GenericConverter.ConvertiblePair convertibleType : converterAdapter.getConvertibleTypes()) {
            converters.put(convertibleType, converterAdapter);
        }
    }

    @Override
    public void addConverter(GenericConverter converter) {
        for (GenericConverter.ConvertiblePair convertibleType : converter.getConvertibleTypes()) {
            converters.put(convertibleType, converter);
        }
    }

    @Override
    public void addConverterFactory(ConverterFactory<?, ?> converterFactory) {
        GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converterFactory);
        ConverterFactoryAdapter converterFactoryAdapter = new ConverterFactoryAdapter(typeInfo, converterFactory);
        for (GenericConverter.ConvertiblePair convertibleType : converterFactoryAdapter.getConvertibleTypes()) {
            converters.put(convertibleType, converterFactoryAdapter);
        }
    }

    private GenericConverter.ConvertiblePair getRequiredTypeInfo(Object object) {
        Type[] types = object.getClass().getGenericInterfaces();
        ParameterizedType parameterized = (ParameterizedType) types[0];
        Type[] actualTypeArguments = parameterized.getActualTypeArguments();
        Class sourceType = (Class) actualTypeArguments[0];
        Class targetType = (Class) actualTypeArguments[1];
        return new GenericConverter.ConvertiblePair(sourceType, targetType);
    }

    private GenericConverter getConverter(Class<?> sourceType, Class<?> targetType) {
        List<Class<?>> sourceCandidates = getClassHierarchy(sourceType);
        List<Class<?>> targetCandidates = getClassHierarchy(targetType);
        for (Class<?> sourceCandidate : sourceCandidates) {
            for (Class<?> targetCandidate : targetCandidates) {
                GenericConverter.ConvertiblePair convertiblePair = new GenericConverter.ConvertiblePair(sourceCandidate, targetCandidate);
                GenericConverter converter = converters.get(convertiblePair);
                if (converter != null) {
                    return converter;
                }
            }
        }
        return null;
    }

    private List<Class<?>> getClassHierarchy(Class<?> clazz) {
        List<Class<?>> hierarchy = new ArrayList<>();
        while (clazz != null) {
            hierarchy.add(clazz);
            clazz = clazz.getSuperclass();
        }
        return hierarchy;
    }

    private final class ConverterAdapter implements GenericConverter {

        private final ConvertiblePair typeInfo;

        private final Converter<Object, Object> converter;

        public ConverterAdapter(ConvertiblePair typeInfo, Converter<?, ?> converter) {
            this.typeInfo = typeInfo;
            this.converter = (Converter<Object, Object>) converter;
        }

        @Override
        public Set<ConvertiblePair> getConvertibleTypes() {
            return Collections.singleton(typeInfo);
        }

        @Override
        public Object convert(Object source, Class sourceType, Class targetType) {
            return converter.convert(source);
        }
    }

    private final class ConverterFactoryAdapter implements GenericConverter {

        private final ConvertiblePair typeInfo;

        private final ConverterFactory<Object, Object> converterFactory;

        public ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory<?, ?> converterFactory) {
            this.typeInfo = typeInfo;
            this.converterFactory = (ConverterFactory<Object, Object>) converterFactory;
        }

        @Override
        public Set<ConvertiblePair> getConvertibleTypes() {
            return Collections.singleton(typeInfo);
        }

        @Override
        public Object convert(Object source, Class sourceType, Class targetType) {
            return converterFactory.getConverter(targetType).convert(source);
        }
    }
}

3.4.5 实现类型转换服务

DefaultConversionService.java

package com.lino.springframework.core.convert.support;

import com.lino.springframework.core.convert.converter.ConverterRegistry;

/**
 * @description: 实现类型转换服务
 */
public class DefaultConversionService extends GenericConversionService {

    public DefaultConversionService() {
        addDefaultConverters(this);

    }

    public static void addDefaultConverters(ConverterRegistry converterRegistry) {
        // 添加各类类型转换工厂
        converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
    }
}
  • DefaultConversionService 继承 GenericConversionService 的实现类,而 GenericConversionService 实现了 ConversionService, ConverterRegistry 两个接口,用于 canConvert 判断和转换接口 convert 操作。

3.4.6 创建类型转换工厂

ConversionServiceFactoryBean.java

package com.lino.springframework.context.support;

import com.lino.springframework.beans.factory.FactoryBean;
import com.lino.springframework.beans.factory.InitializingBean;
import com.lino.springframework.core.convert.ConversionService;
import com.lino.springframework.core.convert.converter.Converter;
import com.lino.springframework.core.convert.converter.ConverterFactory;
import com.lino.springframework.core.convert.converter.ConverterRegistry;
import com.lino.springframework.core.convert.converter.GenericConverter;
import com.lino.springframework.core.convert.support.DefaultConversionService;
import com.lino.springframework.core.convert.support.GenericConversionService;
import com.sun.istack.internal.Nullable;
import java.util.Set;

/**
 * @description: 提供创建 ConversionService 工厂
 */
public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {

    @Nullable
    private Set<?> converters;

    @Nullable
    private GenericConversionService conversionService;

    @Override
    public ConversionService getObject() throws Exception {
        return conversionService;
    }

    @Override
    public Class<?> getObjectType() {
        return conversionService.getClass();
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.conversionService = new DefaultConversionService();
        registerConverters(converters, conversionService);
    }

    private void registerConverters(Set<?> converters, ConverterRegistry registry) {
        if (converters != null) {
            for (Object converter : converters) {
                if (converter instanceof GenericConverter) {
                    registry.addConverter((GenericConverter) converter);
                } else if (converter instanceof Converter<?, ?>) {
                    registry.addConverter((Converter<?, ?>) converter);
                } else if (converter instanceof ConverterFactory<?, ?>) {
                    registry.addConverterFactory((ConverterFactory<?, ?>) converter);
                } else {
                    throw new IllegalArgumentException("Each converter object must implement one of the " +
                            "Converter, ConverterFactory, or GenericConverter interfaces");
                }
            }
        }
    }

    public void setConverters(Set<?> converters) {
        this.converters = converters;
    }
}
  • 有了 FactoryBean 的实现就可以完成工程对象的操作,可以提供出转换对象的服务 GenericConversionService
  • 另外在 afterPropertiesSet 中调用了注册操作的类。最终这个类会被配置到 spring.xml 中在启动的过程加载。

3.5 类型转换服务使用

3.5.1 Bean工厂接口添加方法

BeanFactory.java

package com.lino.springframework.beans.factory;

import com.lino.springframework.beans.BeansException;

/**
 * @description: 定义 Bean 工厂接口
 */
public interface BeanFactory {

    /**
     * 返回 Bean 的实例对象
     *
     * @param name 要检索的bean的名称
     * @return 实例化的 Bean 对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    Object getBean(String name) throws BeansException;

    /**
     * 返回含构造函数的 Bean 实例对象
     *
     * @param name 要检索的bean的名称
     * @param args 构造函数入参
     * @return 实例化的 Bean 对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    Object getBean(String name, Object... args) throws BeansException;

    /**
     * 返回指定泛型的对象
     *
     * @param name         要检索的bean的名称
     * @param requiredType 类型
     * @param <T>          泛型
     * @return 实例化的的 Bean 对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    /**
     * 返回指定泛型的对象
     *
     * @param requiredType 类型
     * @param <T>          泛型
     * @return 实例化的的 Bean 对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    <T> T getBean(Class<T> requiredType) throws BeansException;

    /**
     * 根据对象名称判断是否包含此对象
     *
     * @param name 对象名称
     * @return 是否包含
     */
    boolean containsBean(String name);
}
  • 添加 containsBean(String name) 方法判断是否包含此对象。

3.5.2 配置Bean工厂接口

ConfigurableBeanFactory.java

package com.lino.springframework.beans.factory.config;

import com.lino.springframework.beans.factory.HierarchicalBeanFactory;
import com.lino.springframework.core.convert.ConversionService;
import com.lino.springframework.util.StringValueResolver;
import com.sun.istack.internal.Nullable;

/**
 * @description: 配置Bean工厂接口
 */
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {

    String SCOPE_SINGLETON = "singleton";

    String SCOPE_PROTOTYPE = "prototype";

    /**
     * 添加修改新实例化 Bean 对象的扩展点
     *
     * @param beanPostProcessor 新实例化 Bean 对象
     */
    void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);

    /**
     * 销毁单例
     */
    void destroySingletons();

    /**
     * 添加字符串解析器
     *
     * @param valueResolver 解析器
     */
    void addEmbeddedValueResolver(StringValueResolver valueResolver);

    /**
     * 解析嵌入值
     *
     * @param value 嵌入值
     * @return 解析后的结果
     */
    String resolveEmbeddedValue(String value);

    /**
     * 设置类型转换抽象接口
     *
     * @param conversionService 类型转换抽象接口
     */
    void setConversionService(ConversionService conversionService);

    /**
     * 获取类型转换抽象接口
     *
     * @return 类型转换抽象接口
     */
    @Nullable
    ConversionService getConversionService();
}

3.5.3 抽象Bean工厂基类实现Bean工厂接口

AbstractBeanFactory.java

package com.lino.springframework.beans.factory.support;

import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.FactoryBean;
import com.lino.springframework.beans.factory.config.BeanDefinition;
import com.lino.springframework.beans.factory.config.BeanPostProcessor;
import com.lino.springframework.beans.factory.config.ConfigurableBeanFactory;
import com.lino.springframework.core.convert.ConversionService;
import com.lino.springframework.util.ClassUtils;
import com.lino.springframework.util.StringValueResolver;
import java.util.ArrayList;
import java.util.List;

/**
 * @description: 抽象的 Bean 工厂基类,定义模板方法
 */
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {

    private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

    private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();

    private final List<StringValueResolver> embeddedValueResolvers = new ArrayList<>();

    private ConversionService conversionService;

    @Override
    public Object getBean(String name) throws BeansException {
        return doGetBean(name, null);
    }

    @Override
    public Object getBean(String name, Object... args) throws BeansException {
        return doGetBean(name, args);
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return (T) getBean(name);
    }

    @Override
    public boolean containsBean(String name) {
        return containsBeanDefinition(name);
    }

    /**
     * 根据对象名判断是否包含此对象
     *
     * @param beanName 对象名称
     * @return 是否包含此对象
     */
    protected abstract boolean containsBeanDefinition(String beanName);

    ...

    @Override
    public void setConversionService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    @Override
    public ConversionService getConversionService() {
        return conversionService;
    }

    ...
}

3.5.4 应用上下文抽象类

AbstractApplicationContext.java

package com.lino.springframework.context.support;

import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.ConfigurableListableBeanFactory;
import com.lino.springframework.beans.factory.config.BeanFactoryPostProcessor;
import com.lino.springframework.beans.factory.config.BeanPostProcessor;
import com.lino.springframework.context.ApplicationEvent;
import com.lino.springframework.context.ApplicationListener;
import com.lino.springframework.context.ConfigurableApplicationContext;
import com.lino.springframework.context.event.ApplicationEventMulticaster;
import com.lino.springframework.context.event.ContextClosedEvent;
import com.lino.springframework.context.event.ContextRefreshedEvent;
import com.lino.springframework.context.event.SimpleApplicationEventMulticaster;
import com.lino.springframework.core.convert.ConversionService;
import com.lino.springframework.core.io.DefaultResourceLoader;
import java.util.Collection;
import java.util.Map;

/**
 * @description: 抽象应用上下文
 */
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

    public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

    private ApplicationEventMulticaster applicationEventMulticaster;

    @Override
    public void refresh() throws BeansException {
        // 1.创建 BeanFactory,并加载 BeanDefinition
        refreshBeanFactory();

        // 2.获取 BeanFactory
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        // 3.添加 ApplicationContextAwareProcessor,让继承自 ApplicationContextAware 的 Bean 对象都能感知所属的 ApplicationContext
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

        // 4.在 Bean 实例化之前,执行 BeanFactoryPostProcess
        invokeBeanFactoryPostProcessor(beanFactory);

        // 5.BeanPostProcessor 需要提前与其他 Bean 对象实例化之前执行注册操作
        registerBeanPostProcessor(beanFactory);

        // 6.初始化事件发布者
        initApplicationEventMulticaster();

        // 7.注册事件监听器
        registerListeners();

        // 8.设置类型转换器、提前实例化单例 Bean 对象
        finishBeanFactoryInitialization(beanFactory);

        // 9.发布容器刷新完成事件
        finishRefresh();
    }

    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        // 设置类型转换器
        if (beanFactory.containsBean("conversionService")) {
            Object conversionService = beanFactory.getBean("conversionService");
            if (conversionService instanceof ConversionService) {
                beanFactory.setConversionService((ConversionService) conversionService);
            }
        }
        // 提前实例化单例 Bean 对象
        beanFactory.preInstantiateSingletons();
    }

    ...

    @Override
    public boolean containsBean(String name) {
        return getBeanFactory().containsBean(name);
    }

    ...

}
  • AbstractApplicationContext#finishBeanFactoryInitialization 设置类型转换器。

3.5.5 实现默认Bean创建的抽象Bean工厂超类

AbstractAutowireCapableBeanFactory.java

package com.lino.springframework.beans.factory.support;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValue;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.*;
import com.lino.springframework.beans.factory.config.*;
import com.lino.springframework.core.convert.ConversionService;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * @description: 实现默认bean创建的抽象bean工厂超类
 */
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy();

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
        // 判断是否返回代理 Bean 对象
        Object bean = resolveBeforeInstantiation(beanName, beanDefinition);
        if (null != bean) {
            return bean;
        }
        return doCreateBean(beanName, beanDefinition, args);
    }

    protected Object doCreateBean(String beanName, BeanDefinition beanDefinition, Object[] args) {
        Object bean = null;
        try {
            // 实例化Bean
            bean = createBeanInstance(beanDefinition, beanName, args);

            // 处理循环依赖,将实例化后的Bean对象提前放入缓存中暴露出来
            if (beanDefinition.isSingleton()) {
                Object finalBean = bean;
                addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));
            }

            // 实例化后判断
            boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean);
            if (!continueWithPropertyPopulation) {
                return bean;
            }
            // 在设置Bean属性之前,允许 BeanPostProcessor修改属性值
            applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);
            // 给bean填充属性
            applyPropertyValues(beanName, bean, beanDefinition);
            // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        // 注册实现 DisposableBean 接口的 Bean 对象
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);

        // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE
        Object exposedObject = bean;
        if (beanDefinition.isSingleton()) {
            // 获取代理对象
            exposedObject = getSingleton(beanName);
            registerSingletonBean(beanName, exposedObject);
        }
        return exposedObject;
    }

    ...

    private void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
        try {
            PropertyValues propertyValues = beanDefinition.getPropertyValues();
            for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {

                String name = propertyValue.getName();
                Object value = propertyValue.getValue();

                if (value instanceof BeanReference) {
                    // A 依赖 B,获取 B 的实例化
                    BeanReference beanReference = (BeanReference) value;
                    value = getBean(beanReference.getBeanName());
                }
                // 类型转换
                else {
                    Class<?> sourceType = value.getClass();
                    Class<?> targetType = (Class<?>) TypeUtil.getFieldType(bean.getClass(), name);
                    ConversionService conversionService = getConversionService();
                    if (conversionService != null) {
                        if (conversionService.canConvert(sourceType, targetType)) {
                            value = conversionService.convert(value, targetType);
                        }
                    }
                }

                // 属性填充
                BeanUtil.setFieldValue(bean, name, value);
            }
        } catch (Exception e) {
            throw new BeansException("Error setting property values: " + beanName + "message: " + e);
        }
    }

    ...
}
  • AbstractAutowireCapableBeanFactory#applyPropertyValues 填充属性的操作中,具体使用了类转换的功能。
  • AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues 也有同样的属性类型转换操作。

四、测试:数据类型转换工厂

4.1 添加测试配置

4.1.1 老公类

Husband.java

package com.lino.springframework.test.bean;

import java.time.LocalDate;

/**
 * @description: 老公类
 */
public class Husband {

    private String wifeName;

    private LocalDate marriageDate;

    public String getWifeName() {
        return wifeName;
    }

    public void setWifeName(String wifeName) {
        this.wifeName = wifeName;
    }

    public LocalDate getMarriageDate() {
        return marriageDate;
    }

    public void setMarriageDate(LocalDate marriageDate) {
        this.marriageDate = marriageDate;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "wifeName='" + wifeName + '\'' +
                ", marriageDate=" + marriageDate +
                '}';
    }
}

4.1.2 字符串转Int类型

StringToIntegerConverter.java

package com.lino.springframework.test.converter;

import com.lino.springframework.core.convert.converter.Converter;

/**
 * @description: 字符串转int
 */
public class StringToIntegerConverter implements Converter<String, Integer> {
    @Override
    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}

4.1.3 字符串转LocalDate

StringToLocalDateConverter.java

package com.lino.springframework.test.converter;

import com.lino.springframework.core.convert.converter.Converter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

/**
 * @description: 字符串转LocalDate
 */
public class StringToLocalDateConverter implements Converter<String, LocalDate> {

    private final DateTimeFormatter DATE_TIME_FORMATTER;

    public StringToLocalDateConverter(String pattern) {
        DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(pattern);
    }

    @Override
    public LocalDate convert(String source) {
        return LocalDate.parse(source, DATE_TIME_FORMATTER);
    }
}

4.1.4 类型转换工厂

ConvertersFactoryBean.java

package com.lino.springframework.test.converter;

import com.lino.springframework.beans.factory.FactoryBean;
import java.util.HashSet;
import java.util.Set;

/**
 * @description: 类型转换工厂对象
 */
public class ConvertersFactoryBean implements FactoryBean<Set<?>> {

    @Override
    public Set<?> getObject() throws Exception {
        HashSet<Object> converters = new HashSet<>();
        StringToLocalDateConverter stringToLocalDateConverter = new StringToLocalDateConverter("yyyy-MM-dd");
        converters.add(stringToLocalDateConverter);
        return converters;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

4.1.5 Spring属性配置文件

spring.xml

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	         http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="husband" class="com.lino.springframework.test.bean.Husband">
        <property name="wifeName" value="张三"/>
        <property name="marriageDate" value="2022-12-08"/>
    </bean>

    <bean id="conversionService" class="com.lino.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters" ref="converters"/>
    </bean>

    <bean id="converters" class="com.lino.springframework.test.converter.ConvertersFactoryBean"/>

</beans>

4.2 单元测试

4.2.1 单元测试

ApiTest.java

@Test
public void test_convert() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    Husband husband = applicationContext.getBean("husband", Husband.class);
    System.out.println("测试结果:" + husband);
}

测试结果

测试结果:Husband{wifeName='张三', marriageDate=2022-12-08}
  • 测试结果看,正常通过了。

4.2.2 字符串转Int测试

ApiTest.java

@Test
public void test_StringToIntegerConverter() {
    StringToIntegerConverter converter = new StringToIntegerConverter();
    Integer num = converter.convert("1234");
    System.out.println("测试结果:" + num);
}

测试结果

测试结果:1234

4.2.3 字符串转数字测试

测试结果:1234

@Test
public void test_StringToNumberConverterFactory() {
    StringToNumberConverterFactory converterFactory = new StringToNumberConverterFactory();

    Converter<String, Integer> stringToIntegerConverter = converterFactory.getConverter(Integer.class);
    System.out.println("测试结果:" + stringToIntegerConverter.convert("1234"));

    Converter<String, Long> stringToLongConverter = converterFactory.getConverter(Long.class);
    System.out.println("测试结果:" + stringToLongConverter.convert("1234"));
}

测试结果

测试结果:1234
测试结果:1234

五、总结:数据类型转换工厂

  • 本节实现的类型转换操作如果只是功能性的开发,可能只是简单 if 判断就可以了。但是放在一个成熟的框架中要考虑的是可复用性、可扩展性。
    • 所以会看到接口的定义、工厂的使用等等设计模式的使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/985035.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

共筑安全防线|与安全狗相约2023年国家网络安全宣传周

在《关于网络安全和信息化工作重要指示》里 “坚持筑牢国家网络安全屏障”被提及 网络安全的重要性再次被重申 实际上 在“十四五”数字经济发展规划及“二十大”上 网络安全也被多次强调 “网络安全”不再简单停留在字面意思上 它成为囊括 你我个人隐私安全、信息安全 …

Linux:LVS (NAT群集搭建)

模拟环境 外网入口服务器 外网 192.168.8.88 内网ip 192.168.254.4 web1 服务器 ip 192.168.254.1 网关&#xff1a; 192.168.254.4 web2 服务器 ip 192.168.254.2 网关&#xff1a; 192.168.254.4 共享存储服务器 ip 192.168.254.3 介绍 访问 外网192.16…

方案:TSINGSEEE青犀AI智能分析网关森林防火智慧监管平台方案

一、方案背景 森林是地球上最重要的生态系统之一&#xff0c;对环境、气候、水循环和空气质量具有重要影响。森林火灾会造成巨大的经济损失&#xff0c;具有发生面广、突发性强、破坏性大、危险性高、处置扑救特别困难等特点&#xff0c;严重危及人民生命财产和森林资源安全&a…

video标签自动播放音视频并绘制波形图

html中的<video>标签可以用来播放常见的音视频格式&#xff0c;支持的格式包括:MP3、Ogg、WAV、AAC、MP4、WebM、AVI等&#xff0c;当然支持的格式也和浏览器和操作系统有关。这里以一个可以自动播放音视频并绘制波形图的页面为例说明一下<video>标签的用法。 vid…

NPM使用技巧

NPM使用技巧 前言技巧全局模块位置PowerShell报错安装模块冲突 NPM介绍NPM命令使用方法基本命令模块命令查看模块运行命令镜像管理 常用模块rimrafyarn 前言 本文包含NodeJS中NPM包管理器的使用技巧&#xff0c;具体内容包含NPM介绍、NPM命令、常用模块等内容&#xff0c;还包…

Android开发常见问题:字符串乱码问题

前言 最近的项目里遇到了字符串乱码的问题&#xff0c;记录一下研究心得。 正文 一、byte数组如何保存字符串 通常情况下&#xff0c;数据的传输和读取都是通过socket&#xff0c;读取socket需要使用byte数组&#xff0c;例如要写入一个字符串到socket中&#xff0c;我们需…

Redis-带你深入学习数据类型Hash【面试重点】

目录 1、基础知识 2、相关命令 2.1、hset、hget 2.2、hexists 2.3、hdel 2.4、hkeys、hvals、hgetall 2.5、hmget、hmset 2.6、hlen 2.7、hsetNX 2.8、Hincrby、Hincrbyfloat 3、应用场景-缓存 4、缓存方式对比 1、基础知识 redis中存储数据本身就是以键值对的形式…

华为OD机试 - 真正的密码 - 数据结构list(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路具体解题思路&#xff1a; 五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#…

智慧商城项目

智慧商城 - 授课大纲 接口文档&#xff1a;https://apifox.com/apidoc/shared-12ab6b18-adc2-444c-ad11-0e60f5693f66/doc-2221080 演示地址&#xff1a;http://cba.itlike.com/public/mweb/#/ 01. 项目功能演示 1.明确功能模块 启动准备好的代码&#xff0c;演示移动端面…

视频用二维码怎么分享?扫码看视频在线制作方法

怎么将本地的视频生成二维码呢&#xff1f;相信很多小伙伴经常会储存许多自己喜欢的视频&#xff0c;那么如果想要给多人分享时&#xff0c;一个个发送会比较麻烦&#xff0c;那么将视频生成二维码就是提高分享效率的一种方式。制作视频二维码的方法也很简单&#xff0c;使用二…

【python】PySide中QMessageBox设置中文按钮及使用

PyQt、PySide使用QMessageBox的时候会发现按钮都是英文的&#xff0c;对于中文的应用软件来说会降低使用体验。本文将以问答对话框为例&#xff0c;介绍如何设置中文按钮&#xff0c;以及如何使用。 实验环境 本文实验环境为&#xff1a;Windows 10&#xff0c;Python 3.8&am…

UI基础——UGUI源码架构

文章目录 如何阅读源码UGUI源码整体架构源码整体类图事件模块EventSystem类图点击事件时序图 参考&#xff1a; 如何阅读源码 阅读源码是提高编程能力和技术水平的重要途径之一。以下是一些有效的阅读源码的方法&#xff1a; 确定目标&#xff1a;&#xff08;1&#xff09;了…

Linux系统中实现便捷运维管理和远程访问的1Panel部署方法解析

文章目录 前言 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、文件管理、数据库管理、容器管理等下面我们介绍在Linux 本地安装1Panel 并结合cpolar 内网穿透工具实现远程访问1Panel 管理…

【C刷题训练营】第三讲(c语言入门训练)

前言: 大家好&#xff0c;我决定日后逐渐更新c刷题训练营的内容&#xff0c;或许能帮到入门c语言的初学者&#xff0c;如果文章有错误&#xff0c;非常欢迎你的指正&#xff01; &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388;&…

如何做见效快的SEO推广?

答案是&#xff1a;见效快的推广可以选择谷歌SEO谷歌Ads双向运营。 关键词研究 对于任何SEO推广&#xff0c;一切始于准确的关键词研究。 使用专业工具 利用如SEMrush、Ahrefs等工具&#xff0c;找到与你业务相关&#xff0c;但竞争程度较低的关键词。 分析竞争对手 查看…

Mysql性能调优——1.深入理解Mysql索引数据结构和算法

本系列所说的Mysql性能调优&#xff0c;主要是针对开发者在实际环境中的sql调优&#xff0c;代码层面上的优化。不涉及到mysql底层代码的调优。 我们知道&#xff0c;一个mysql数据表&#xff0c;数据量小的时候&#xff0c;可能简单的查询耗时不会太久&#xff0c;性能也可以…

孤儿僵尸守护进程的简单理解

孤儿进程&#xff1a; 一个父进程退出&#xff0c;而它的一个或多个子进程还在运行&#xff0c;那么那些子进程将成为孤儿进程。孤儿进程将被init进程所收养&#xff0c;并由init进程对它们完成状态收集工作。 如何模仿一个孤儿进程&#xff1a; 答案是&#xff1a; kill 父…

Docker基础入门:Docker基础总结篇--超详细

Docker基础入门&#xff1a;Docker基础总结篇[docker3要素、docker安装配置、容器使用、镜像管理发布] 一、Docker 3要素1.1、镜像&#xff08;Image&#xff09;1.2、容器&#xff08;Container&#xff09;1.3、仓库&#xff08;Registry&#xff09;1.4 、总结 二、Docker安…

2023 年高教社杯全国大学生数学建模竞赛-E 题 黄河水沙监测数据分析详解+思路+Python代码

2023 年高教社杯全国大学生数学建模竞赛-E 题 黄河水沙监测数据分析 十分激动啊啊啊题目终于出来了&#xff01;&#xff01;官网6点就进去了结果直接卡死现在才拿到题目&#xff0c;我是打算A-E题全部做一遍。简单介绍一下我自己&#xff1a;博主专注建模四年&#xff0c;参与…

手写Spring:第12章-基于JDK、Cglib实现AOP切面

文章目录 一、目标&#xff1a;基于JDK、Cglib实现AOP切面二、设计&#xff1a;基于JDK、Cglib实现AOP切面三、实现&#xff1a;基于JDK、Cglib实现AOP切面3.0 引入依赖3.1 工程结构3.2 AOP切点表达式和使用以及基于JDK和CGLIB的动态代理类图3.3 切点表达式3.3.1 类匹配接口3.3…