Conversão de tipo MVC Spring: PropertyEditor ou Converter?

129

Estou procurando a maneira mais fácil e simples de vincular e converter dados no Spring MVC. Se possível, sem fazer nenhuma configuração xml.

Até agora eu tenho usado PropertyEditors assim:

public class CategoryEditor extends PropertyEditorSupport {

    // Converts a String to a Category (when submitting form)
    @Override
    public void setAsText(String text) {
        Category c = new Category(text);
        this.setValue(c);
    }

    // Converts a Category to a String (when displaying form)
    @Override
    public String getAsText() {
        Category c = (Category) this.getValue();
        return c.getName();
    }

}

e

...
public class MyController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Category.class, new CategoryEditor());
    }

    ...

}

É simples: ambas as conversões são definidas na mesma classe e a ligação é direta. Se eu quisesse fazer uma ligação geral em todos os meus controladores, ainda podia adicionar 3 linhas na minha configuração xml .


Mas o Spring 3.x introduziu uma nova maneira de fazer isso, usando os Conversores :

Dentro de um contêiner Spring, esse sistema pode ser usado como uma alternativa aos PropertyEditors

Então, digamos que eu queira usar conversores, porque é "a última alternativa". Eu teria que criar dois conversores:

public class StringToCategory implements Converter<String, Category> {

    @Override
    public Category convert(String source) {
        Category c = new Category(source);
        return c;
    }

}

public class CategoryToString implements Converter<Category, String> {

    @Override
    public String convert(Category source) {
        return source.getName();
    }

}

Primeira desvantagem: eu tenho que fazer duas aulas. Benefício: não há necessidade de lançar graças à genéricos.

Então, como eu simplesmente vinculo os dados aos conversores?

Segunda desvantagem: não encontrei nenhuma maneira simples (anotações ou outras facilidades programáticas) de fazer isso em um controlador: nada parecido someSpringObject.registerCustomConverter(...);.

As únicas maneiras que eu encontrei seriam tediosas, não simples e apenas sobre a ligação geral entre controladores:

  • Configuração XML :

    <bean id="conversionService"
      class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="somepackage.StringToCategory"/>
                <bean class="somepackage.CategoryToString"/>
            </set>
        </property>
    </bean>
  • Configuração Java ( apenas no Spring 3.1+ ):

    @EnableWebMvc
    @Configuration
    public class WebConfig extends WebMvcConfigurerAdapter {
    
        @Override
        protected void addFormatters(FormatterRegistry registry) {
            registry.addConverter(new StringToCategory());
            registry.addConverter(new CategoryToString());
        }
    
    }

Com todas essas desvantagens, por que usar conversores? Estou esquecendo de algo ? Existem outros truques que eu não conheço?

Estou tentado a continuar usando PropertyEditors ... A ligação é muito mais fácil e rápida.

Jerome Dalbert
fonte
Nota (tropecei também, usando o Spring 3.2.17): ao usar <mvc: acionado por anotação />, é realmente necessário fazer referência a esse bean conversionService: <mvc: serviço de conversão acionado por anotação = "conversionService" />
21316
addFormatters (...) deve ser público. Também desde o 5.0 WebMvcConfigurerAdapter está obsoleto.
Paco Abato

Respostas:

55

Com todas essas desvantagens, por que usar conversores? Estou esquecendo de algo ? Existem outros truques que eu não conheço?

Não, acho que você descreveu muito bem o PropertyEditor e o Converter, como cada um é declarado e registrado.

Na minha opinião, os PropertyEditors são limitados em escopo - eles ajudam a converter String em um tipo, e essa string geralmente vem da interface do usuário; portanto, registrar um PropertyEditor usando @InitBinder e WebDataBinder faz sentido.

O conversor, por outro lado, é mais genérico, destina-se a QUALQUER conversão no sistema - não apenas para conversões relacionadas à interface do usuário (String para o tipo de destino). Por exemplo, o Spring Integration usa um conversor extensivamente para converter uma carga útil da mensagem em um tipo desejado.

Eu acho que, para os fluxos relacionados à interface do usuário, os PropertyEditors ainda são apropriados, especialmente para o caso em que você precisa fazer algo personalizado para uma propriedade de comando específica. Para outros casos, eu aceitaria a recomendação da referência do Spring e escreveria um conversor (por exemplo, para converter de um ID longo para uma entidade, como exemplo).

Biju Kunjummen
fonte
5
Outra coisa boa é que os conversores são sem estado, enquanto os editores de propriedades são organizados e criados muitas vezes e implementados com muitas chamadas de API, não acho que isso tenha um grande impacto no desempenho, mas os conversores são mais limpos e mais simples.
Boris Treukhov 22/09/12
1
@Boris cleaner sim, mas não é mais simples, especialmente para iniciantes: você precisa escrever 2 classes de conversor + adicionar várias linhas na configuração xml ou java config. Estou falando sobre o envio / exibição de formulários do Spring MVC, com conversões gerais (não apenas entidades).
Jerome Dalbert 23/09/12
16
  1. Para conversões de / para String, use formatadores (implemente org.springframework.format.Formatter ) em vez de conversores. Ele possui os métodos print (...) e parse (...) , portanto, você precisa de apenas uma classe em vez de duas. Para registrá-los, use FormattingConversionServiceFactoryBean , que pode registrar conversores e formatadores, em vez de ConversionServiceFactoryBean .
  2. O novo material do Formatador possui alguns benefícios adicionais:
    • A interface do formatador fornece o objeto Locale em seus métodos de impressão (...) e análise (...) , para que sua conversão de string possa ser sensível à localidade
    • Além dos formatadores pré-registrados, o FormattingConversionServiceFactoryBean vem com alguns objetos AnnotationFormatterFactory pré-registrados , que permitem especificar parâmetros de formatação adicionais por meio de anotação. Por exemplo: @RequestParam@DateTimeFormat (pattern = "MM-DD-AA")LocalDate baseDate ... Não é muito difícil criar suas próprias classes AnnotationFormatterFactory , consulte NumberFormatAnnotationFormatterFactory do Spring para um exemplo simples. Eu acho que isso elimina a necessidade de formatadores / editores específicos do controlador. Use um ConversionService para todos os controladores e personalize a formatação por meio de anotações.
  3. Concordo que, se você ainda precisar de alguma conversão de string específica do controlador, a maneira mais simples ainda será usar o editor de propriedades personalizadas. (Eu tentei ligar ' binder.setConversionService (...) ' no meu método @InitBinder , mas falha, pois o objeto fichário vem com o serviço de conversão 'global' já definido. Parece que as classes de conversão por controlador são desencorajadas em Primavera 3).
Alexander
fonte
7

A maneira mais simples (supondo que você esteja usando uma estrutura de persistência), mas não a maneira perfeita, é implementar um conversor de entidade genérico via ConditionalGenericConverter interface que converterá entidades usando seus metadados.

Por exemplo, se você estiver usando JPA, esse conversor poderá verificar se a classe especificada possui @Entityanotação e usar o @Idcampo anotado para extrair informações e executar a pesquisa automaticamente usando o valor String fornecido como um ID para pesquisa.

public interface ConditionalGenericConverter extends GenericConverter {
    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

ConditionalGenericConverter é uma "arma definitiva" da API de conversão Spring, mas está sendo implementada uma vez que será capaz de processar a maioria das conversões de entidade, economizando tempo do desenvolvedor - é um grande alívio quando você apenas especifica as classes de entidade como parâmetros do seu controlador e nunca pensa em implementar um novo conversor (exceto para tipos personalizados e não-entidades, é claro).

Boris Treukhov
fonte
Ótima solução para lidar apenas com a conversão de entidades, obrigado pelo truque. Não é simples no começo, pois você precisa escrever mais uma aula, mas é simples e economiza tempo a longo prazo.
Jerome Dalbert 23/09/12
Entre esse conversor pode ser implementado para qualquer tipo que cumpra algum contrato genérico - outro exemplo: se suas enumerações implementarem alguma interface de pesquisa inversa comum - você também poderá implementar um conversor genérico (será semelhante ao stackoverflow.com / questions / 5178622 /… )
Boris Treukhov 23/09
@JeromeDalbert sim, é um pouco difícil para um iniciante fazer coisas pesadas, mas se você tiver uma equipe de desenvolvedores, será mais simples) PS E será chato registrar os mesmos editores de propriedades sempre que necessário)
Boris Treukhov 23/09/12
1

Você pode resolver a necessidade de ter duas classes de conversor separadas, implementando os dois conversores como classes internas estáticas.

public class FooConverter {
    public static class BarToBaz implements Converter<Bar, Baz> {
        @Override public Baz convert(Bar bar) { ... }
    }
    public static class BazToBar implements Converter<Baz, Bar> {
        @Override public Bar convert(Baz baz) { ... }
    }
}

Você ainda precisará registrar os dois separadamente, mas pelo menos isso reduz o número de arquivos que você precisa modificar se fizer alguma alteração.

ntm
fonte