提交 ec71c6cd 编写于 作者: 希川's avatar 希川

<feat>: 属性转换

上级 a9925f1b
package cn.noexception.container.context.support;
import cn.noexception.container.factory.FactoryBean;
import cn.noexception.container.factory.InitializingBean;
import cn.noexception.container.factory.convert.ConversionService;
import cn.noexception.container.factory.convert.converter.Converter;
import cn.noexception.container.factory.convert.converter.ConverterFactory;
import cn.noexception.container.factory.convert.converter.ConverterRegistry;
import cn.noexception.container.factory.convert.converter.GenericConverter;
import cn.noexception.container.factory.convert.support.DefaultConversionService;
import cn.noexception.container.factory.convert.support.GenericConversionService;
import jdk.internal.jline.internal.Nullable;
import java.util.Set;
/**
* ConversionServiceFactoryBean
*
* @author 吕滔
* @Date 2021/11/19 16:21
*/
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();
registerConversions(converters, conversionService);
}
private void registerConversions(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.addConverterFactory((ConverterFactory<?, ?>) converter);
} else if (converter instanceof ConverterFactory<?, ?>) {
registry.addConverterFactory((ConverterFactory<?, ?>) converter);
} else {
throw new IllegalArgumentException("Each converter object must implememnt one of the " +
"Converter, ConverterFactory, or GenericConverter interfaces");
}
}
}
}
public void setConverters(Set<?> converters) {
this.converters = converters;
}
}
package cn.noexception.container.factory.convert;
import jdk.internal.jline.internal.Nullable;
/**
* ConversionService
*
* @author 吕滔
* @Date 2021/11/19 12:00
*/
public interface ConversionService {
boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
}
package cn.noexception.container.factory.convert.converter;
/**
* Converter
* 定义类型转换接口
*
* @author 吕滔
* @Date 2021/11/18 16:42
*/
public interface Converter<S, T> {
T convert(S source);
}
package cn.noexception.container.factory.convert.converter;
/**
* ConverterFactory
* 类型转换工厂
*
* @author 吕滔
* @Date 2021/11/18 16:43
*/
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
package cn.noexception.container.factory.convert.converter;
/**
* ConverterRegistry
* 类型转换注册接口
*
* @author 吕滔
* @Date 2021/11/18 16:45
*/
public interface ConverterRegistry {
void addConverter(Converter<?, ?> converter);
void addConverter(GenericConverter converter);
void addConverterFactory(ConverterFactory<?, ?> converterFactory);
}
package cn.noexception.container.factory.convert.converter;
import cn.hutool.core.lang.Assert;
import java.util.Set;
/**
* GenericConverter
*
* @author 吕滔
* @Date 2021/11/18 16:56
*/
public interface GenericConverter {
Set<ConvertiblePair> getConvertibleTypes();
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, "来源类型不能为空。");
Assert.notNull(targetType, "目标类型不能为空。");
this.sourceType = sourceType;
this.targetType = targetType;
}
public Class<?> getSourceType() {
return sourceType;
}
public Class<?> getTargetType() {
return 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();
}
}
}
package cn.noexception.container.factory.convert.support;
import cn.noexception.container.factory.convert.converter.ConverterRegistry;
/**
* DefaultConversionService
*
* @author 吕滔
* @Date 2021/11/18 16:50
*/
public class DefaultConversionService extends GenericConversionService {
public DefaultConversionService(){
addDefaultConverters(this);
}
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
// 添加各类类型转换工厂
converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
}
}
package cn.noexception.container.factory.convert.support;
import cn.noexception.container.factory.convert.ConversionService;
import cn.noexception.container.factory.convert.converter.Converter;
import cn.noexception.container.factory.convert.converter.ConverterFactory;
import cn.noexception.container.factory.convert.converter.ConverterRegistry;
import cn.noexception.container.factory.convert.converter.GenericConverter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
/**
* GenericConversionService
*
* @author 吕滔
* @Date 2021/11/19 10:35
*/
public class GenericConversionService implements ConversionService, ConverterRegistry {
private Map<GenericConverter.ConvertiblePair, GenericConverter> converters = new HashMap();
@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);
}
}
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 GenericConverter.ConvertiblePair getRequiredTypeInfo(Object object) {
Type[] types = object.getClass().getGenericInterfaces();
ParameterizedType parameterizedType = (ParameterizedType) types[0];
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Class sourceType = (Class) actualTypeArguments[0];
Class targetType = (Class) actualTypeArguments[1];
return new GenericConverter.ConvertiblePair(sourceType, targetType);
}
@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 final class ConverterFactoryAdapter implements GenericConverter{
private final ConvertiblePair typeInfo;
private final ConverterFactory<Object, Object> converterFactory;
private 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);
}
}
@Override
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
GenericConverter converter = getConverter(sourceType, targetType);
return converter != null;
}
protected 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;
}
@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);
}
}
package cn.noexception.container.factory.convert.support;
import cn.noexception.container.factory.convert.converter.Converter;
import cn.noexception.container.factory.convert.converter.ConverterFactory;
import cn.noexception.container.factory.utils.NumberUtils;
import jdk.internal.jline.internal.Nullable;
/**
* StringToNumberConverterFactory
*
* @author 吕滔
* @Date 2021/11/19 15:39
*/
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 final 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);
}
}
}
......@@ -2,11 +2,13 @@ package cn.noexception.container.factory.support;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import cn.noexception.container.BeansException;
import cn.noexception.container.PropertyValue;
import cn.noexception.container.PropertyValues;
import cn.noexception.container.factory.*;
import cn.noexception.container.factory.config.*;
import cn.noexception.container.factory.convert.ConversionService;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
......@@ -178,6 +180,16 @@ public abstract class AbstractAutowiredCapableBeanFactory extends AbstractBeanFa
// 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);
......
......@@ -6,6 +6,7 @@ import cn.noexception.container.factory.FactoryBean;
import cn.noexception.container.factory.config.BeanDefinition;
import cn.noexception.container.factory.config.BeanPostProcessor;
import cn.noexception.container.factory.config.ConfigurableBeanFactory;
import cn.noexception.container.factory.convert.ConversionService;
import cn.noexception.container.factory.utils.ClassUtils;
import cn.noexception.container.factory.utils.StringValueResolver;
......@@ -28,6 +29,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
*/
private final List<StringValueResolver> embeddedValueResolvers = new ArrayList<>();
private ConversionService conversionService;
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null);
......@@ -97,4 +100,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
}
return result;
}
public ConversionService getConversionService() {
return conversionService;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
}
package cn.noexception.container.factory.utils;
import cn.hutool.core.lang.Assert;
import jdk.internal.jline.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;
/**
* NumberUtils
*
* @author 吕滔
* @Date 2021/11/19 15:44
*/
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 java.lang.Byte
* @see java.lang.Short
* @see java.lang.Integer
* @see java.lang.Long
* @see java.math.BigInteger
* @see java.lang.Float
* @see java.lang.Double
* @see java.math.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 java.math.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 java.text.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 java.math.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);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册