BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Spring Framework 4 e Java 8

Spring Framework 4 e Java 8

Favoritos

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:

public  List 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:

Optional optional = 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
    Optional otherService;

    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(Optional accountId, @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.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT