O Java 8 trouxe novidades na linguagem e na biblioteca e muitas delas já são suportadas pelo Spring 4.x. Algumas das novas funcionalidades do Java 8 não causam impacto no Spring e podem ser usadas como estão, enquanto outras precisam de um suporte específico do Spring para ser utilizadas. Esse artigo apresenta essas novas funcionalidades do Java 8 que são suportadas pelo Spring 4.x.
O Spring 4 oferece suporte ao Java 6, 7 e 8
O código compilado com o Java 8 gera um arquivo .class que requer ao menos uma Máquina Virtual (VM) do Java 8. Uma vez que o Spring utiliza muita reflection e manipulação de bytecode com bibliotecas como ASM, CGLIB, era importante garantir que essas ferramentas fossem capazes de entender esses novos arquivos de bytecode. Para isso, o Spring Framework agora mantém de forma embarcada as bibliotecas ASM, CGLIB e outras, utilizando o jarjar (https://code.google.com/p/jarjar/), de forma que a distribuição do Spring tenha sempre um conjunto previsível de bibliotecas que funcionem com bytecode do Java na versões 6, 7 e 8, garantindo que não acontecerão erros em tempo de execução.
O Spring Framework é compilado com o Java 8, com a opção de linha de comando para produzir bytecode do Java 6. Portanto, é possível compilar e executar aplicações utilizando o Spring 4.x com Java 6, 7 e 8.
Spring e Expressões Lambda do Java 8
Os projetistas do Java 8 garantiram a compatibilidade com versões anteriores, de forma que as expressões lambda do Java 8 possam ser usadas com o código compilado com versões anteriores. Essa compatibilidade foi conseguida através do conceito de interfaces funcionais.
Basicamente, os projetistas analisaram diversos trechos de código Java existentes e perceberam que a maioria dos desenvolvedores Java usavam interfaces com um único método representando a ideia de uma função. Por exemplo, as seguintes listas de interfaces do JDK e Spring que somente usam um único método - também conhecidas como "interfaces funcionais".
Interfaces funcionais do JDK:
public interface Runnable { public abstract void run(); } public interface Comparable{ public int compareTo(T o); }
Interfaces funcionais do Spring framework
public interface ConnectionCallback{ T doInConnection(Connection con) throws SQLException, DataAccessException; } public interface RowMapper { T mapRow(ResultSet rs, int rowNum) throws SQLException; }
Expressões lambda podem ser utilizadas no Java 8 em qualquer lugar que uma interface funcional é passada como parâmetro ou devolvida por um método. Por exemplo, a classe JdbcTemplate do Spring contém um método com a seguinte assinatura:
publicList query(String sql, RowMapper rowMapper) throws DataAccessException
Observe que o segundo argumento do método é uma interface do tipo RowMapper. Com o Java 8 pode ser utilizada uma expressão lambda como valor para esse argumento.
Ao invés de escrever um código desse tipo:
jdbcTemplate.query("SELECT * from products", new RowMapper(){ @Override public Product mapRow(ResultSet rs, int rowNum) throws SQLException { Integer id = rs.getInt("id"); String description = rs.getString("description"); Integer quantity = rs.getInt("quantity"); BigDecimal price = rs.getBigDecimal("price"); Date availability = rs.getDate("available_date"); Product product = new Product(); product.setId(id); product.setDescription(description); product.setQuantity(quantity); product.setPrice(price); product.setAvailability(availability); return product; } });
pode ser escrito o seguinte código:
jdbcTemplate.query("SELECT * from queries.products", (rs, rowNum) -> { Integer id = rs.getInt("id"); String description = rs.getString("description"); Integer quantity = rs.getInt("quantity"); BigDecimal price = rs.getBigDecimal("price"); Date availability = rs.getDate("available_date"); Product product = new Product(); product.setId(id); product.setDescription(description); product.setQuantity(quantity); product.setPrice(price); product.setAvailability(availability); return product; });
Essa segunda versão do código utilizando expressões lambda é muito mais compacta e clara que a primeira versão usando classes anônimas.
Apresentar todos os detalhes de interfaces funcionais do Java 8 está além desse artigo e é altamente recomendado que o leitor entenda os detalhes de interfaces funcionais em uma referência mais apropriada. O ponto chave é que as expressões lambda do Java 8 podem ser passadas para métodos compilados com Java 7 ou anteriores se os métodos aceitarem uma interface funcional.
O código do Spring contém várias interfaces funcionais e, dessa forma, as expressões lambda podem ser facilmente usadas com o Spring. Mesmo que o Spring seja compilado com o formato de classes do Java 6 ainda é possível escrever aplicações usando expressões lambda do Java 8, compilá-las com o Java 8 e executá-las em uma VM do Java 8 que tudo funcionará.
Concluindo, pelo fato do Spring Framework usar interfaces funcionais antes d o Java 8 formalizar esse conceito é fácil usar expressões lambda com o Spring.
Spring 4 e a API Date e Time do Java 8
Os desenvolvedores Java lamentam há muito tempo as deficiências na classe java.util.Date e, finalmente, com o Java 8 há uma nova API de data e hora que resolve esses problemas. Essa nova API por sí só já valeria ter o seu próprio artigo, portanto ela não será abordada em detalhes aqui.
O Spring traz um framework de conversão é capaz de converter strings em datas e vice-versa. O Spring 4 atualiza esse framework de conversão para dar suporte às classes que fazem parte da nova API de Data e Hora do Java 8. Portanto, é possível escrever o seguinte código:
@RestController public class ExampleController { @RequestMapping("/date/{localDate}") public String get(@DateTimeFormat(iso = ISO.DATE) LocalDate localDate) { return localDate.toString(); } }
Observe que no exemplo acima o parâmetro do método get é do tipo LocalDate do Java 8 e o Spring 4 é capaz de pegar uma string como 2014-02-01 e convertê-la para uma instância apropriada.
É importante citar que o Spring é frequentemente usado com outras bibliotecas como o Hibernate para persistir dados e o Jackson para converter objetos em JSON e vice-versa.
Enquanto o Spring 4 suporta objetos da biblioteca de data e hora do Java 8, isso não significa que os frameworks de terceiros também são capazes de dar fazer esse tratamento.
Spring 4 e Anotações Repetidas
O Java 8 adicionou o suporte para anotações repetidas e o Spring 4 também reconhece essa funcionalidade. Em particular, o Spring 4 aceita as anotações repetidas @Scheduled e @PropertySource. Veja um exemplo de uso da @PropertySource no código abaixo:
@Configuration @ComponentScan @EnableAutoConfiguration @PropertySource("classpath:/example1.properties") @PropertySource("classpath:/example2.properties") public class Application { @Autowired private Environment env; @Bean public JdbcTemplate template(DataSource datasource) { System.out.println(env.getProperty("test.prop1")); System.out.println(env.getProperty("test.prop2")); return new JdbcTemplate(datasource); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Java 8 Optional<> e Spring 4.1
A falta de verificação de referências nulas é um problema comum no código das aplicações. Uma forma de eliminar o NullPointException é garantir que os métodos sempre devolvam um valor não nulo. Por exemplo, considere a seguinte definição de interface:
public interface CustomerRepository extends CrudRepository{ /** * retorna o customer para um id específico ou * null se o valor não for encontrado */ public Customer findCustomerById(String id); }
Um desenvolvedor pode implementar o CustomerRepository com uma possível falha através do seguinte código:
Customer customer = customerRepository.findCustomerById(“123”); customer.getName(); // causa NullPointerException
ao invés de escrever o seguinte código mais seguro:
Customer customer = customerRepository.findCustomerById(“123”); if(customer != null) { customer.getName(); // evita NullPointerException }
O ideal nesse caso é que o compilador informe do possível problema se nós não verificarmos um valor que pode ser nulo. A classe java.util.Optional torna possível escrever a interface da seguinte forma:
public interface CustomerRepository extends CrudRepository{ public Optional findCustomerById(String id); }
Portanto, a versão do código contendo possíveis falhas não compilará e o desenvolvedor deve verificar explicitamente se um Optional contém um valor ou não da seguinte forma:
Optionaloptional = customerRepository.findCustomerById(“123”); if(optional.isPresent()) { Customer customer = optional.get(); customer.getName(); }
A ideia principal do Optional é garantir que os desenvolvedores saibam que um método pode devolver um valor nulo, ou que eles podem passar um valor nulo para um método sem ter que ler o Javadoc. A assinatura do método e o compilador ajudarão a deixar claro que um valor é Optional. Uma descrição mais detalhada da ideia da classe Optional pode ser encontrada aqui.
O Spring 4.1 permite o uso de Option de duas formas. A anotação @Autowired contém um atributo 'required' que pode ser usado da seguinte forma:
@Service public class MyService { @Autowired(required=false) OtherService otherService; public doSomething() { if(otherService != null) { // usa outro serviço } } }
e ele pode ser substituído por:
public class MyService { @Autowired OptionalotherService; public doSomething() { otherService.ifPresent( s -> { // usa "s" para fazer algo }); } }
Outro ponto onde o Optional pode ser usado é com o Spring MVC no qual ele pode indicar que um parâmetro de um método que trata requisições é opcional. Por exemplo:
@RequestMapping(“/accounts/{accountId}”,requestMethod=RequestMethod.POST) void update(OptionalaccountId, @RequestBody Account account)
informará ao Spring que accountId é opcional.
Em resumo, a classe Optional do Java 8 torna fácil escrever código com menos NullPointException e o Spring funciona perfeitamente com essa classe.
Descoberta de nome de parâmetros
O Java 8 introduz suporte para manter em tempo de execução o nome dos argumentos de métodos. Isso significa que o Spring 4 pode extrair esses nomes dos métodos, tornando o código do SpringMVC mais compacto. Por exemplo:
@RequestMapping("/accounts/{id}") public Account getAccount(@PathVariable("id") String id)
pode ser escrito como:
@RequestMapping("/accounts/{id}") public Account getAccount(@PathVariable String id)
Perceba que a anotação @PathVariable("id") foi substituída por @PathVariable porque o Spring 4 pode obter o nome do argumento "id" a partir do código compilado. O compilador do Java 8 salva o nome dos argumentos no arquivos .class se ele é executado com o parâmetro "-parameters". Antes do Java 8, o Spring era capaz de extrair os nomes dos parâmetros do código compilado se o código fosse compilado com a opção "-debug".
Até o Java 7 a opção "-debug" não preserva os nomes do parâmetros em métodos abstratos. Portanto, para um projeto como o Spring Data que gera automaticamente as implementações de repositórios baseada em interfaces isso causava problemas. Considere a seguinte interface:
interface CustomerRepository extends CrudRepository{ @Query("select c from Customer c where c.lastname = :lastname") List findByLastname(@Param("lastname") String lastname); }
A necessidade do @Param("lastname") na assinatura do findByLastname é necessária mesmo com a opção "-debug" até o Java 7 porque os nomes de parâmetros não serão preservados pois findByLastname é um método abstrato. Com o Java 8 a opção "-parameters" permite ao Spring Data descobrir os nomes dos parâmetros em métodos abstratos de forma que a interface possa ser reescrita da seguinte forma:
interface CustomerRepository extends CrudRepository{ @Query("select c from Customer c where c.lastname = :lastname") List findByLastname(String lastname); }
Perceba que não é mais necessário o @Param("lastname"), tornando o código menos verboso e mais fácil de ler. Ao usar o Java 8 é uma boa ideia compilar o seu código com a opção "-parameters"
Conclusão
O Spring 4 funciona com o Java 6, 7 e 8 e como desenvolvedor você pode escrever sua aplicação usando qualquer uma dessas versões de Java. Se você estiver usando o Java 8 então poderá fazer uso de expressões lambda para escrever um código mais limpo e mais compacto nos lugares que existe uma interface funcional. Uma vez que o Spring usa várias interfaces funcionais então há várias oportunidades de usar expressões lambda.
O Java 8 traz uma versão aperfeiçoada de algumas bibliotecas, como é o caso do novo pacote java.time e a classe Optional que o Spring é capaz de usá-las para escrever um código mais fácil de escrever e mais simples.
Finalmente, compilar o código com o Java 8 com a opção "-parameters" preserva o nome dos argumentos nos métodos e torna possível escrever um código mais compacto nos métodos que atendem requisições no Spring MVC e métodos de consulta do Spring Data.
Se você já estiver pronto para usar o Java 8 nos seus projetos, você pode encontrar no Spring 4 uma boa opção que faz uso das funcionalidades do Java 8.
Sobre o autor
Adib Saikali é um Engenheiro Sênior na Pivotal, apaixonado por tecnologia e empreendorismo. Adib tem construído soluções com Spring e Java há mais de 10 anos e está atualmente interessado em ajudar clientes com o poder de Big Data, PaaS e metodologias ágeis para construir grandes produtos e serviços. Você pode contactar Adib pelo twitter @asaikali.