9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

Hola, mi nombre es Hermano A (YourBatman).

Spring ha diseñado la org.springframework.format.Formatterabstracción de la interfaz del formateador y unifica el formateador para que solo tenga que preocuparse por la API unificada en lugar de la implementación específica. Los temas relacionados se describen en detalle en el artículo anterior .

Hay muchas implementaciones de formateadores creadas en Spring, y hay componentes especiales responsables de su administración, programación y uso, que pueden describirse como responsabilidades distintas y claras. Este artículo se centrará en el centro de registro de Formatter FormatterRegistryy le presentará cómo Spring implementa la gestión de registro de manera elegante e inteligente.

Aprender a codificar es un proceso de imitación , la mayoría de las veces no es necesario crear algo. Por supuesto, la imitación a la que se hace referencia aquí no es un modelo de CV ordinario , sino para tomar la esencia para su propio uso. El ingenioso diseño descrito en este artículo es la esencia, y puede extraerlo.

En los últimos días, ha entrado en un pequeño clima frío . Beijing tiene una temperatura de congelación de -20 ℃ y -11 ℃. Manténgase caliente cuando salga.

Esquema de este artículo

9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

Convención de versión

  • Spring Framework: 5.3.x
  • Spring Boot: 2.4.x

✍Texto

Después de leer y analizar tanto el código fuente de Spring, encontrará que la idea general de la administración de componentes es la misma, y ​​estos componentes son inseparables: registro (registrador) + distribuidor .

Un dragón da a luz a nueve hijos, cada uno de los cuales es diferente. Aunque la idea general sigue siendo la misma, cada realización tiene su propio espacio en su escenario, que es digno de nuestro anhelo.

FormatterRegistry: Registro de formateadores

El registro (centro de registro) del formateador de atributos de campo . Tenga en cuenta: Aquí se enfatiza la existencia del campo. Primero se familiarizará con él y luego tendrá una comprensión más profunda.

public interface FormatterRegistry extends ConverterRegistry {

    void addPrinter(Printer<?> printer);
    void addParser(Parser<?> parser);
    void addFormatter(Formatter<?> formatter);
    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}

Esta interfaz se hereda del registro del convertidor de tipos ConverterRegistry, por lo que el registro de formato es una versión mejorada del registro del convertidor , un superconjunto del mismo y cada vez más potente.

Sobre el tipo de convertidor, los ConverterRegistrydetalles del registro se pueden leer en esta serie de este artículo , lea la puerta trasera clara

Aunque FormatterRegistryse proporcionan muchos métodos de adición, básicamente describen lo mismo: fieldTypeagregar un formateador (impresora o analizador) al tipo especificado , y el dibujo es el siguiente:

9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

Nota: A excepción del último método de interfaz, addFormatterForFieldAnnotation()está relacionado con el formato de anotaciones, porque es muy importante, por lo que escribiré un artículo para explicarlo a continuación.

El árbol de herencia de la interfaz FormatterRegistry es el siguiente:

9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

Con la experiencia de aprender ConverterRegistry, esta rutina de diseño es fácil de entender. Estas dos clases de implementación están divididas jerárquicamente:

  • FormattingConversionService: Implementar todos los métodos de interfaz
  • DefaultFormattingConversionService: Heredado del FormattingConversionService anterior, y registre el formateador predeterminado en base a él

De hecho, la clasificación funcional es efectivamente el caso. Este artículo se centra en FormattingConversionServiceel diseño e implementación de esta clase, hay muchos trucos, mientras vengas, quieres que luzcas bien.

FormattingConversionService

Es FormatterRegistryla clase de implementación de la interfaz, que implementa todos sus métodos de interfaz.

FormatterRegistryConverterRegistry, todos los métodos de la interfaz ConverterRegistry se han GenericConversionServiceimplementado por completo, por lo que puede completar indirectamente la implementación de los métodos de la interfaz ConverterRegistry heredando , por lo que la estructura de herencia de esta clase es así (canta esta estructura):

9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

FormattingConversionService obtiene la "mitad izquierda" (interfaz principal ConverterRegistry) heredando GenericConversionService ; solo queda la "mitad derecha" para ser procesada, que es el nuevo método de interfaz de FormatterRegistry.

FormattingConversionService:

    @Override
    public void addPrinter(Printer<?> printer) {
        Class<?> fieldType = getFieldType(printer, Printer.class);
        addConverter(new PrinterConverter(fieldType, printer, this));
    }
    @Override
    public void addParser(Parser<?> parser) {
        Class<?> fieldType = getFieldType(parser, Parser.class);
        addConverter(new ParserConverter(fieldType, parser, this));
    }
    @Override
    public void addFormatter(Formatter<?> formatter) {
        addFormatterForFieldType(getFieldType(formatter), formatter);
    }
    @Override
    public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
        addConverter(new PrinterConverter(fieldType, formatter, this));
        addConverter(new ParserConverter(fieldType, formatter, this));
    }
    @Override
    public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) {
        addConverter(new PrinterConverter(fieldType, printer, this));
        addConverter(new ParserConverter(fieldType, parser, this));
    }

Desde la implementación de la interfaz, puede ver este "secreto impactante": todos los formateadores (incluidos la impresora, el analizador, el formateador) se consideran Converterregistrados, lo que significa que solo hay un registro real, es decir ConverterRegistry.

9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

La gestión del registro del formateador es mucho menos complicada que la del convertidor, porque se basa en la idea de adaptación de nivel superior y finalmente se adapta al convertidor para completar el registro. Así que vaya en el registro final real se adapta al convertidor a través del formateador, reutilización perfecta del complejo conjunto de lógica de gestión de convertidores.

Esta idea de diseño puede ser "CV" en nuestro propio pensamiento de programación.

Independientemente de si es Printer o Parser, ambos se adaptarán a GenericConverter para ser agregados ConverterRegistryy administrados como convertidor. Ahora debe saber por qué la FormatterRegistryinterfaz solo necesita proporcionar un método de adición pero no un método de eliminación.

Por supuesto, la implementación de la adaptación de la impresora / analizador también es el tema central de este artículo, hay muchos artículos en él, ¡vamos!

PrinterConverter: adaptador de interfaz de impresora

La Printer&lt;?&gt;adaptación es un convertidor y el objetivo de conversión es fieldType -&gt; String.

private static class PrinterConverter implements GenericConverter {

    private final Class<?> fieldType;
    // 从Printer<?>泛型里解析出来的类型,有可能和fieldType一样,有可能不一样
    private final TypeDescriptor printerObjectType;
    // 实际执行“转换”动作的组件
    private final Printer printer;
    private final ConversionService conversionService;

    public PrinterConverter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) {
        ...
        // 从类上解析出泛型类型,但不一定是实际类型
        this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer));
        ...
    }

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

}

Dado que es un convertidor, el punto clave es, por supuesto, su método de conversión:

PrinterConverter:

    @Override
    @SuppressWarnings("unchecked")
    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        // 若sourceType不是printerObjectType的子类型
        // 就尝试用conversionService转一下类型试试
        // (也就是说:若是子类型是可直接处理的,无需转换一趟)
        if (!sourceType.isAssignableTo(this.printerObjectType)) {
            source = this.conversionService.convert(source, sourceType, this.printerObjectType);
        }
        if (source == null) {
            return "";
        }

        // 执行实际转换逻辑
        return this.printer.print(source, LocaleContextHolder.getLocale());
    }

El paso de conversión se divide en dos pasos:

  1. Si el tipo de origen (tipo real) no es un subtipo del tipo genérico del tipo de impresora, intente utilizar conversionService para transferir
    1. Por ejemplo: la impresora maneja el tipo de Número, pero usted pasa el tipo de Persona, esta vez el servicio de conversión entrará en juego
  2. Deje que la impresora del formateador de destino realice la lógica de conversión real

9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

La impresora puede decir que se puede transferir directamente, se puede construir conversionService en un convertidor: siempre que la fuente sea de un tipo con el que pueda tratar, o después de conversionService puede ser mi tipo de energía de tratamiento, se puede convertir. Hay una reutilización perfecta de la habilidad .

Hablando de esto, supongo que algunos amigos aún no pueden entender lo que significa y qué problemas se pueden resolver, así que les daré ejemplos de códigos para profundizar su comprensión.

Prepare un Java Bean:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {

    private Integer id;
    private String name;
}

Prepare una impresora: agregue 10 al tipo Integer y luego conviértalo al tipo String

private static class IntegerPrinter implements Printer<Integer> {

    @Override
    public String print(Integer object, Locale locale) {
        object += 10;
        return object.toString();
    }
}

Ejemplo 1: uso de impresora, sin conversión intermedia

Caso de prueba:

@Test
public void test2() {
    FormattingConversionService formattingConversionService = new FormattingConversionService();
    FormatterRegistry formatterRegistry = formattingConversionService;
    // 说明:这里不使用DefaultConversionService是为了避免默认注册的那些转换器对结果的“干扰”,不方便看效果
    // ConversionService conversionService = new DefaultConversionService();
    ConversionService conversionService = formattingConversionService;

    // 注册格式化器
    formatterRegistry.addPrinter(new IntegerPrinter());

    // 最终均使用ConversionService统一提供服务转换
    System.out.println(conversionService.canConvert(Integer.class, String.class));
    System.out.println(conversionService.canConvert(Person.class, String.class));

    System.out.println(conversionService.convert(1, String.class));
    // 报错:No converter found capable of converting from type [cn.yourbatman.bean.Person] to type [java.lang.String]
    // System.out.println(conversionService.convert(new Person(1, "YourBatman"), String.class));
}

Ejecute el programa, salida:

true
false
11

Perfecto.

Sin embargo, no puede completar la Person -&gt; Stringconversión de tipos. En términos generales, tenemos dos formas de lograr este objetivo:

  1. Manera directa: escriba un convertidor de persona a cadena, dedicado
    1. Las desventajas son obvias: escribe más código
  2. Combinaciones ( recomendadas ): Si ya las hay Person -&gt; Integer, y luego las combinamos con una muy conveniente, el siguiente ejemplo te dirá que uses este método para completar la "demanda"
    1. Las deficiencias no son obvias: los convertidores generalmente no requieren nada que ver con los datos comerciales, por lo que son versátiles y deben reutilizarse tanto como sea posible.

El siguiente ejemplo dos lo ayudará a resolver el propósito logrado al reutilizar las capacidades existentes Person -&gt; String.

Ejemplo 2: uso de impresora, con conversión intermedia

Según el ejemplo 1, si desea lograrlo Person -&gt; String, solo necesita escribir otro Person -&gt; Integerconvertidor en ConversionServiceél.

Nota: En términos generales, ConversionService ya tiene muchas "capacidades", solo utilícelo. Para ayudarlo a explicar los principios subyacentes, este ejemplo utiliza una instancia de ConversionService " limpia "

@Test
public void test2() {
    FormattingConversionService formattingConversionService = new FormattingConversionService();
    FormatterRegistry formatterRegistry = formattingConversionService;
    // 说明:这里不使用DefaultConversionService是为了避免默认注册的那些转换器对结果的“干扰”,不方便看效果
    // ConversionService conversionService = new DefaultConversionService();
    ConversionService conversionService = formattingConversionService;

    // 注册格式化器
    formatterRegistry.addFormatterForFieldType(Person.class, new IntegerPrinter(), null);
    // 强调:此处绝不能使用lambda表达式代替,否则泛型类型丢失,结果将出错
    formatterRegistry.addConverter(new Converter<Person, Integer>() {
        @Override
        public Integer convert(Person source) {
            return source.getId();
        }
    });

    // 最终均使用ConversionService统一提供服务转换
    System.out.println(conversionService.canConvert(Person.class, String.class));
    System.out.println(conversionService.convert(new Person(1, "YourBatman"), String.class));
}

Ejecute el programa, salida:

true
11

Perfecto.

Para este ejemplo, existen las siguientes preocupaciones:

  1. Utilice el addFormatterForFieldType()método para registrar IntegerPrinter y especifique claramente el tipo de procesamiento: solo se procesa el tipo de Persona
    1. Nota: IntegerPrinter se puede registrar varias veces para manejar diferentes tipos. Por ejemplo, aún puede mantenerlo formatterRegistry.addPrinter(new IntegerPrinter());para lidiar con Integer -> String es un problema
  2. Dado que IntegerPrinter en realidad solo puede convertir Integer -&gt; String, es necesario registrar un convertidor para Person -&gt; Integerpuente, de modo que pueda conectarse Person -&gt; Integer -&gt; String. Parece que estos están hechos por IntegerPrinter desde el exterior, lo cual es muy ordenado
  3. Enfatice: al registrar un convertidor con addConverter (), asegúrese de no usar expresiones lambda en lugar de entrada, de lo contrario, el tipo genérico se perderá y se producirán errores
    1. Si desea utilizar expresiones lambda, utilice el método de sobrecarga addConverter (Class, Class, Converter) para completar el registro

ParserConverter: adaptador de interfaz de analizador

La Parser&lt;?&gt;adaptación es un convertidor y el objetivo de conversión es String -&gt; fieldType.

private static class ParserConverter implements GenericConverter {

    private final Class<?> fieldType;
    private final Parser<?> parser;
    private final ConversionService conversionService;

    ... // 省略构造器

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

}

Dado que es un convertidor, el punto clave es, por supuesto, su método de conversión:

ParserConverter:

    @Override
    @Nullable
    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        // 空串当null处理
        String text = (String) source;
        if (!StringUtils.hasText(text)) {
            return null;
        }

        ...
        Object result = this.parser.parse(text, LocaleContextHolder.getLocale());
        ...

        // 解读/转换结果
        TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass());
        if (!resultType.isAssignableTo(targetType)) {
            result = this.conversionService.convert(result, resultType, targetType);
        }
        return result;
    }

El paso de conversión se divide en dos pasos:

  1. Convierta la cadena al tipo especificado a través del analizador y el resultado (si falla, se lanzará una excepción)
  2. Determine si el resultado es un subtipo del tipo de destino , regrese directamente; de ​​lo contrario, llame a ConversionService para convertir

9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

Puede ver que es lo opuesto al "orden" de Printer, y hace un escándalo por el valor de retorno. Del mismo modo, los dos ejemplos siguientes se utilizarán para profundizar la comprensión.

private static class IntegerParser implements Parser<Integer> {

    @Override
    public Integer parse(String text, Locale locale) throws ParseException {
        return NumberUtils.parseNumber(text, Integer.class);
    }
}

Ejemplo 1: uso del analizador, sin conversión intermedia

Escribe casos de prueba:

@Test
public void test3() {
    FormattingConversionService formattingConversionService = new FormattingConversionService();
    FormatterRegistry formatterRegistry = formattingConversionService;
    ConversionService conversionService = formattingConversionService;

    // 注册格式化器
    formatterRegistry.addParser(new IntegerParser());

    System.out.println(conversionService.canConvert(String.class, Integer.class));
    System.out.println(conversionService.convert("1", Integer.class));
}

Ejecute el programa, salida:

true
1

Perfecto.

Ejemplo 2: usar analizador con conversión intermedia

El siguiente ejemplo ingresa una cadena de "1" y sale un objeto Persona (debido al presagio del ejemplo anterior, aquí es "sencillo").

@Test
public void test4() {
    FormattingConversionService formattingConversionService = new FormattingConversionService();
    FormatterRegistry formatterRegistry = formattingConversionService;
    ConversionService conversionService = formattingConversionService;

    // 注册格式化器
    formatterRegistry.addFormatterForFieldType(Person.class, null, new IntegerParser());
    formatterRegistry.addConverter(new Converter<Integer, Person>() {
        @Override
        public Person convert(Integer source) {
            return new Person(source, "YourBatman");
        }
    });

    System.out.println(conversionService.canConvert(String.class, Person.class));
    System.out.println(conversionService.convert("1", Person.class));
}

Ejecute el programa, pop, puntero nulo:

java.lang.NullPointerException
    at org.springframework.format.support.FormattingConversionService$PrinterConverter.resolvePrinterObjectType(FormattingConversionService.java:179)
    at org.springframework.format.support.FormattingConversionService$PrinterConverter.<init>(FormattingConversionService.java:155)
    at org.springframework.format.support.FormattingConversionService.addFormatterForFieldType(FormattingConversionService.java:95)
    at cn.yourbatman.formatter.Demo.test4(Demo.java:86)
    ...

Según la información de la pila de excepciones, se puede aclarar la razón: addFormatterForFieldType()el segundo parámetro del método no puede pasar nulo, de lo contrario un puntero nulo. Esto es en realidad Spring Frameworkun error. Presenté un problema a la comunidad y espero que se pueda resolver:

9. Vea el capítulo real para más detalles, el diseño del registro Formatter es muy lindo

Para ejecutar este ejemplo correctamente, cámbielo así:

// 第二个参数不传null,用IntegerPrinter占位
formatterRegistry.addFormatterForFieldType(Person.class, new IntegerPrinter(), new IntegerParser());

Ejecute el programa nuevamente y genere:

true
Person(id=1, name=YourBatman)

Perfecto.

Para este ejemplo, existen las siguientes preocupaciones:

  1. Utilice el addFormatterForFieldType()método para registrar IntegerParser y especifique claramente el tipo de procesamiento que se utiliza para procesar el tipo de Persona
    1. Es decir, este IntegerParser se usa específicamente para convertir atributos cuyo tipo de destino es Person
  2. Debido a que IntegerParser en realidad solo puede convertir String -&gt; Integer, es necesario registrar un convertidor para Integer -&gt; Personpuente, de modo que pueda unirse String -&gt; Integer -&gt; Person. Se ve como éstos son hechos por IntegerParser, muy ordenado
  3. También enfatice: cuando registre el convertidor con addConverter (), asegúrese de no usar expresiones lambda en lugar de entrada, de lo contrario se perderá el tipo genérico, lo que resultará en errores

¿Qué mejoras aportan ambos con ConversionService?

Nota: Usted conoce un Servicio de Conversión tan importante, si lo olvida, puede tomar el ascensor a esta revisión.

Para PrinterConverter y ParserConverter, su propósito de origen es lograr String &lt;-&gt; Object, y las características son:

  • PrinterConverter: La salida debe ser de tipo String, y también se ha determinado el tipo de entrada, es decir Printer&lt;T&gt;, el tipo genérico, que solo se puede procesarT(或T的子类型) -&gt; String
  • ParserConverter: La entrada debe ser de tipo String, y también se ha determinado el tipo de salida, es decir Parser&lt;T&gt;, el tipo genérico, que solo se puede procesarString -&gt; T(或T的子类型)

Según las "reglas" establecidas, el alcance de sus habilidades es todavía bastante limitado. Aquí es donde Spring es tan poderoso que puede ampliar inteligentemente las capacidades de los componentes existentes mediante la combinación. Por ejemplo, pone la referencia ConversionService en PrinterConverter / ParserConverter en la ganancia, para lograr este efecto:

ConversionService

A través de la combinación de habilidades y colaboración, juega un papel en serie, ampliando así el "rango" de entrada / salida, se siente como jugar un efecto de lupa, este diseño es muy lindo.

✍Resumen

Este artículo se FormatterRegistrycentra en la introducción de la interfaz y se centra en la implementación de esta interfaz, y encontró que incluso un pequeño centro de registro tiene importantes aspectos destacados para el aprendizaje y el CV.

En términos generales, ConversionService nace con capacidades de conversión muy poderosas, por lo que la situación real es que si necesita personalizar una impresora / analizador, existe una alta probabilidad de que no necesite agregar un convertidor Converter adicional usted mismo. Es decir, el mecanismo subyacente lo hace estar parado Sobre los hombros de "gigantes".

♨Este artículo pensando en preguntas♨

Después de leerlo, es posible que no lo entienda y que no lo entienda. Aquí, 3 preguntas de pensamiento al final del artículo lo ayudarán a revisar:

  1. FormatterRegistry como centro de registro solo tiene una forma de agregar, ¿por qué?
  2. ¿Por qué se enfatiza en el ejemplo? Cuando registre un convertidor con addConverter (), asegúrese de no usar expresiones lambda en lugar de entrada. ¿Cuál es el problema?
  3. ¿Puede pensar en otros casos de este ingenioso diseño de combinación de funciones / puente?

☀Lectura recomendada☀

♚Declaración♚

Este artículo pertenece a la columna: Conversión de tipo Spring , la respuesta entre bastidores de la cuenta pública al nombre de la columna puede obtener todo el contenido.

Comparte, crece, rehúsa esconderte y detenerte. Preste atención a [BAT Utopia] y responda a la columna de palabras clave con columnas originales pequeñas y hermosas, como la pila de tecnología Spring y middleware para el aprendizaje gratuito. Este artículo ha sidohttps://www.yourbatman.cn incluido.

Este artículo es un artículo original de Brother A (YourBatman) y no se puede reproducir sin el permiso / apertura del autor. Gracias por su cooperación.

Supongo que te gusta

Origin blog.51cto.com/3631118/2592729
Recomendado
Clasificación