Spring实例化源码解析之ConversionService(十)

前言

ConversionService(转换服务)是Spring框架中的一个核心接口,用于在不同类型之间进行转换和格式化操作。它提供了一种统一的方式来处理对象之间的类型转换,以及将数据从一种表示形式转换为另一种表示形式。

以下是ConversionService的一些主要用途:

  1. 类型转换:ConversionService允许在不同类型之间进行自动转换。它提供了一组内置的转换器,可以处理常见的类型转换,例如字符串到数字、日期到字符串等。还可以自定义和注册自己的转换器,以处理特定类型之间的转换。

  2. 数据格式化:ConversionService可以用于将数据从一种格式转换为另一种格式,例如将日期对象格式化为特定的日期字符串表示形式。它支持使用标准的格式化模式和自定义的格式化规则。

  3. 数据绑定:ConversionService可以在数据绑定过程中使用,将输入的数据转换为目标对象的属性类型。它可以帮助将用户输入的数据转换为应用程序所需的类型,以便进行验证和处理。

  4. 表达式求值:在Spring表达式语言(SpEL)中,ConversionService用于在表达式求值期间执行类型转换。它允许在表达式中使用类型转换操作,以便在表达式求值过程中进行数据处理和转换。

Converter SPI

实现类型转换逻辑的SPI(Service Provider Interface)是简单且具有强类型,如下所示的接口定义所示:

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

	T convert(S source);
}

要创建属于你自己的转换器,只需要简单的实现以上接口即可。泛型参数S表示你想要进行转换的源类型,而泛型参数T表示你想要转换的目标类型。如果一个包含S类型元素的集合或数组需要转换为一个包含T类型的数组或集合,那么这个转换器也可以被透明地应用,前提是已经注册了一个委托数组或集合的转换器(默认情况下会是DefaultConversionService处理)。

对每次方法convert(S)的调用,source参数值必须确保不为空。如果转换失败,你的转换器可以抛出任何非受检异常(unchecked exception);具体来说,为了报告一个非法的source参数值,应该抛出一个IllegalArgumentException。还有要注意确保你的Converter实现必须是线程安全的。

为方便起见,core.convert.support包已经提供了一些转换器实现,这些实现包括了从字符串到数字以及其他常见类型的转换。考虑将StringToInteger作为一个典型的Converter实现示例:

package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

	public Integer convert(String source) {
		return Integer.valueOf(source);
	}
}

ConverterFactory

当需要对整个类层次结构进行转换逻辑的集中管理时(例如,从字符串转换为枚举对象),可以实现ConverterFactory,如下面的示例所示:

public interface ConverterFactory<S, R> {

    <T extends R> Converter<S, T> getConverter(Class<T> targetType);

}

ConverterFactory是Spring框架提供的一个接口,用于实现类型转换的工厂。通过实现该接口,我们可以定义自己的转换逻辑,并将其应用于整个类层次结构。

在上述代码中,ConverterFactory接口定义了一个泛型方法getConverter,该方法接收目标类型targetType作为参数,并返回一个Converter对象。Converter接口用于定义类型转换的具体实现。

通过实现自定义的ConverterFactory,可以根据目标类型的不同,返回相应的Converter实例,以实现从源类型到目标类型的转换逻辑。这样可以在整个类层次结构中统一管理转换逻辑,提供更灵活和可扩展的转换方式。

package com.qhyu.cloud.conversion;

import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;

/**
 * All rights Reserved, Designed By https://umd.edu/ <br>
 * Title:StringToEnumConverterFactory <br>
 * Package:com.qhyu.cloud.conversion <br>
 * Copyright © 2023 umd.edu. All rights reserved. <br>
 * Company:The University of Maryland  <br>
 *
 * @author candidate <br>
 * @date 2023年 10月10日 10:12 <br>
 */
@SuppressWarnings(value = {"rawtypes","unchecked"})
public class StringToEnumConverterFactory implements ConverterFactory<String,Enum> {

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

	private static final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {

		private final Class<T> enumType;

		public StringToEnumConverter(Class<T> enumType) {
			this.enumType = enumType;
		}

		public T convert(String source) {
			return (T) Enum.valueOf(this.enumType, source.trim());
		}
	}
}

GenericConverter

当需要一个更复杂的Converter实现时,可以考虑使用GenericConverter接口。相较于Converter接口,GenericConverter具有更灵活但类型约束较弱的签名,它支持在多个源类型和目标类型之间进行转换。此外,GenericConverter提供了源和目标字段上下文的信息,可以在实现转换逻辑时使用这些上下文。这样的上下文可以通过字段注解或字段签名上声明的泛型信息来驱动类型转换。下面的代码清单展示了GenericConverter接口的定义:

public interface GenericConverter {

    Set<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

    class ConvertiblePair {
        private final Class<?> sourceType;
        private final Class<?> targetType;

        public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
            this.sourceType = sourceType;
            this.targetType = targetType;
        }

        // getter methods for sourceType and targetType
    }
}

GenericConverter接口定义了三个方法和一个内部类ConvertiblePair。

  • getConvertibleTypes方法返回一个ConvertiblePair集合,其中ConvertiblePair表示可转换的源类型和目标类型的配对。
  • convert方法用于执行实际的类型转换,它接收源对象source、源类型描述符sourceType和目标类型描述符targetType作为参数,并返回转换后的目标对象。
  • ConvertiblePair类表示可转换的源类型和目标类型的配对。它包含两个属性:sourceType和targetType,分别表示源类型和目标类型。通过ConvertiblePair类,可以定义多个不同的源类型和目标类型之间的转换关系。

通过实现GenericConverter接口,可以更灵活地定义转换逻辑,并支持多个源类型和目标类型之间的转换。此外,还可以利用上下文信息,例如字段注解或字段签名上的泛型信息,来驱动转换过程。

我们用一个具体的示例来说明GenericConverter的使用时,我们可以考虑一个简单的场景:将字符串表示的日期转换为不同的日期对象(例如java.util.Datejava.time.LocalDate)。

首先,我们定义一个实现了GenericConverter接口的类,来执行日期字符串到目标日期对象的转换:

import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

public class StringToDateConverter implements GenericConverter {

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        Set<ConvertiblePair> convertiblePairs = new HashSet<>();
        convertiblePairs.add(new ConvertiblePair(String.class, Date.class));
        convertiblePairs.add(new ConvertiblePair(String.class, LocalDate.class));
        return convertiblePairs;
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (source == null || !(source instanceof String)) {
            return null;
        }

        String dateString = (String) source;
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

        try {
            if (targetType.getType().equals(Date.class)) {
                return dateFormat.parse(dateString);
            } else if (targetType.getType().equals(LocalDate.class)) {
                Date date = dateFormat.parse(dateString);
                return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return null;
    }
}

在上述代码中,我们实现了GenericConverter接口,并重写了其中的两个方法:getConvertibleTypesconvert

getConvertibleTypes方法中,我们定义了可转换的源类型和目标类型的配对。对于我们的例子而言,我们可以将String类型和Date类型以及String类型和LocalDate类型作为可转换的配对。

convert方法中,我们首先检查源对象是否为null或不是字符串类型,如果是,则返回null。然后,我们使用SimpleDateFormat将字符串日期解析为Date对象,并根据目标类型的不同,进行相应的转换。在示例中,我们将Date对象转换为LocalDate对象。

接下来,我们可以使用这个自定义的转换器进行日期字符串到日期对象的转换。例如:

import org.springframework.core.convert.support.GenericConversionService;

public class Main {
    public static void main(String[] args) {
        GenericConversionService conversionService = new GenericConversionService();
        conversionService.addConverter(new StringToDateConverter());

        String dateString = "2023-10-10";

        Date date = conversionService.convert(dateString, Date.class);
        System.out.println("Date: " + date);

        LocalDate localDate = conversionService.convert(dateString, LocalDate.class);
        System.out.println("LocalDate: " + localDate);
    }
}

在上述代码中,我们创建了一个GenericConversionService对象,并添加了我们定义的转换器StringToDateConverter。然后,我们使用conversionService对象将字符串日期转换为Date对象和LocalDate对象,并打印输出结果。

这只是一个简单的示例,用于说明如何使用GenericConverter进行转换。在实际应用中,可能需要根据具体的需求和更复杂的转换逻辑来实现自己的GenericConverter。

请注意,上述代码仅展示了GenericConverter接口的定义,具体的实现和转换逻辑将根据的需求和业务逻辑而有所不同。需要根据自己的实际情况来实现GenericConverter接口,并根据ConvertiblePair定义适合的源类型和目标类型的转换关系。

由于GenericConverter是一个更复杂的SPI接口,所以对基本类型的转换需求优先使用Converter或者ConverterFactory。

ConditionalGenericConverter

有时,你希望Converter仅在特定条件为真时运行。例如,可能希望仅在目标字段上存在特定注解时运行Converter,或者仅在目标类上定义了特定方法(如静态的valueOf方法)时运行Converter。ConditionalGenericConverter是GenericConverter和ConditionalConverter接口的结合体,它允许定义这样的自定义匹配条件:

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}

ConditionalGenericConverter接口继承了GenericConverter和ConditionalConverter接口,并定义了两个方法:matches和convert。

  • matches方法用于判断给定的源类型和目标类型是否满足特定条件。根据的需求,可以在matches方法中实现自定义的匹配逻辑,例如检查目标字段上的注解或目标类上的特定方法是否存在。
  • convert方法执行实际的类型转换,它接收源对象source、源类型描述符sourceType和目标类型描述符targetType作为参数,并返回转换后的目标对象。可以在convert方法中实现自定义的转换逻辑,根据特定条件来执行不同的转换方式。

通过实现ConditionalGenericConverter接口,可以根据特定条件来决定是否运行Converter,并定义适合条件的转换逻辑。这样,可以更灵活地控制转换的行为,根据条件来选择合适的转换方式。

请注意,上述代码只是接口定义的示例,具体的实现和匹配条件将根据的需求和业务逻辑而有所不同。需要根据自己的实际情况来实现ConditionalGenericConverter接口,并根据特定条件定义适合的转换逻辑。

接下来用一个示例,演示如何使用ConditionalGenericConverter来根据特定条件运行转换器。

假设我们有一个注解@ConvertToUpperCase,用于指示将字符串转换为大写的转换逻辑。我们希望只有在目标字段上存在该注解时,才执行转换。

首先,定义注解@ConvertToUpperCase

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConvertToUpperCase {
}

接下来,创建一个实现ConditionalGenericConverter接口的转换器类StringToUpperCaseConverter

import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;

import java.util.Collections;
import java.util.Set;

public class StringToUpperCaseConverter implements ConditionalGenericConverter {

    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        return targetType.hasAnnotation(ConvertToUpperCase.class);
    }

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        return Collections.singleton(new ConvertiblePair(String.class, String.class));
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (source == null) {
            return null;
        }

        String stringValue = (String) source;
        return stringValue.toUpperCase();
    }
}

在上述代码中,matches方法检查目标类型描述符targetType是否具有ConvertToUpperCase注解。如果目标字段上存在该注解,则匹配成功,转换器将执行转换操作。

getConvertibleTypes方法返回可转换的源类型和目标类型的配对,这里我们只关心字符串类型的转换。

convert方法执行实际的转换操作,将字符串转换为大写形式。

最后,我们可以使用这个自定义的转换器来执行转换:

import org.springframework.core.convert.support.GenericConversionService;

public class Main {
    public static void main(String[] args) {
        GenericConversionService conversionService = new GenericConversionService();
        conversionService.addConverter(new StringToUpperCaseConverter());

        String lowercaseString = "hello";
        String uppercaseString = conversionService.convert(lowercaseString, String.class);
        System.out.println("Lowercase string: " + lowercaseString);
        System.out.println("Uppercase string: " + uppercaseString);

        // Example with annotation
        class Example {
            @ConvertToUpperCase
            private String annotatedString;

            // Getter and setter for annotatedString
        }

        Example example = new Example();
        example.setAnnotatedString("hello");
        conversionService.convert(example, Example.class);
        System.out.println("Annotated string: " + example.getAnnotatedString());
    }
}

在上述代码中,我们创建了一个GenericConversionService对象,并将自定义的转换器StringToUpperCaseConverter添加到转换服务中。

首先,我们将普通字符串"hello"转换为大写形式,并打印输出结果。

然后,我们创建了一个包含带有@ConvertToUpperCase注解的字符串字段的示例对象,并通过转换服务执行转换。在该示例中,转换器将根据字段上的注解执行转换操作。

请注意,这只是一个简单的示例,用于说明如何使用ConditionalGenericConverter根据特定条件运行转换器。在实际应用中,可能需要根据具体的需求和更复杂的条件来实现自己的转换器和匹配逻辑。

ConversionService API

ConversionService在运行时定义了执行类型转换逻辑的统一API。转换器通常在以下外观接口后运行:

public interface ConversionService {

    boolean canConvert(Class<?> sourceType, Class<?> targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

    <T> T convert(Object source, Class<T> targetType);

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

    // Additional methods for registering and managing converters

}

ConversionService接口定义了一组方法,用于执行类型转换操作。这些方法包括:

  • canConvert(Class<?> sourceType, Class<?> targetType):检查是否可以从源类型转换为目标类型。
  • canConvert(TypeDescriptor sourceType, TypeDescriptor targetType):检查是否可以从源类型描述符转换为目标类型描述符。
  • convert(Object source, Class<T> targetType):将源对象转换为目标类型,并返回转换后的目标对象。
  • convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType):将源对象根据指定的源类型描述符和目标类型描述符进行转换,并返回转换后的目标对象。

通过ConversionService接口,可以执行各种类型之间的转换操作,无论是基本类型还是自定义类型。可以根据源类型和目标类型,或使用类型描述符来执行转换操作。ConversionService还提供了其他方法,用于注册和管理转换器。

使用ConversionService接口,可以隐藏具体的转换实现细节,并通过统一的API来执行类型转换。这使得在应用程序中使用不同的转换器变得更加简单和方便。

当使用Spring Framework时,可以使用ConversionService接口来执行类型转换操作。以下是一个简单的示例,展示如何使用ConversionService进行类型转换:

import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;

public class Main {
    public static void main(String[] args) {
        // 创建 ConversionService 对象
        ConversionService conversionService = new DefaultConversionService();

        // 执行类型转换
        int intValue = conversionService.convert("123", Integer.class);
        double doubleValue = conversionService.convert("3.14", Double.class);
        String stringValue = conversionService.convert(42, String.class);

        // 输出转换结果
        System.out.println("Integer value: " + intValue);
        System.out.println("Double value: " + doubleValue);
        System.out.println("String value: " + stringValue);
    }
}

在上述代码中,我们使用DefaultConversionService实现创建了一个ConversionService对象。

然后,我们使用conversionService.convert()方法执行类型转换操作。我们将字符串"123"转换为Integer类型,将字符串"3.14"转换为Double类型,以及将整数值42转换为String类型。

最后,我们打印输出转换后的结果。

请注意,DefaultConversionService是Spring Framework中默认的ConversionService实现,它提供了许多内置的转换器。还可以自定义和注册自己的转换器,以满足特定的转换需求。

配置ConversionService

一个ConversionService是一个无状态的对象,旨在在应用程序启动时实例化,然后在多个线程之间共享。在Spring应用程序中,通常为每个Spring容器(或ApplicationContext)配置一个ConversionService实例。Spring会识别这个ConversionService,并在框架需要执行类型转换时使用它。还可以将这个ConversionService注入到任何一个bean中,并直接调用它。

在Spring应用程序中配置和使用ConversionService的一般步骤如下:

  1. 创建一个实现了ConversionService接口的类,或使用Spring提供的默认实现DefaultConversionService。
  2. 配置该ConversionService实例,可以通过Java配置、XML配置或注解配置来完成。
  3. 将该ConversionService实例注册到Spring容器或ApplicationContext中。

以下是一个示例,演示如何在Spring应用程序中配置和使用ConversionService:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        return new DefaultConversionService();
    }

    // 可以在这里注册自定义的转换器或配置其他ConversionService相关的设置

}

在上述示例中,我们创建了一个DefaultConversionService实例,并将其作为一个Bean注册到Spring容器中。通过@Bean注解,Spring会自动将该ConversionService实例纳入管理,并在需要时进行注入。

可以在AppConfig类中注册自定义的转换器或配置其他相关的设置。例如,使用addConverter()方法注册自定义的转换器,使用addFormatter()方法注册格式化器等。

在其他的Spring组件中,可以通过依赖注入的方式获取到这个ConversionService实例,并在需要时进行调用。例如:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

    private final ConversionService conversionService;

    @Autowired
    public MyBean(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    public void doConversion() {
        int intValue = conversionService.convert("123", Integer.class);
        // 执行其他类型转换操作...
    }

    // 其他方法...

}

在上述示例中,我们将ConversionService实例注入到MyBean组件中,并在doConversion()方法中使用它执行类型转换操作。通过依赖注入,Spring会自动将ConversionService的实例传递给MyBean的构造函数。

通过这种方式,可以在Spring应用程序中配置和使用ConversionService,以执行各种类型转换操作。这样可以使类型转换在整个应用程序中保持一致,并且可以方便地进行自定义和扩展。

总结

总之,ConversionService提供了一个通用的机制,用于处理对象之间的类型转换和数据格式化。它在Spring框架中广泛使用,特别是在数据绑定、数据转换以及表达式求值的场景中。通过使用ConversionService,可以简化类型转换的代码,提高应用程序的可维护性和灵活性。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas & Friends

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值