0% acharam este documento útil (0 voto)
9 visualizações12 páginas

Java Reflection

O curso de Java Reflection ensina a usar a API de reflection para criar códigos genéricos que convertem objetos em DTOs, transformando objetos Java em JSON e garantindo a proteção de dados. Os alunos aprenderão a implementar testes robustos com JUnit e a manipular objetos em tempo de execução, utilizando boas práticas de programação. O curso é voltado para desenvolvedores que já possuem conhecimento básico em Java e desejam aprofundar suas habilidades em reflection.

Enviado por

Ricardo Alves
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
9 visualizações12 páginas

Java Reflection

O curso de Java Reflection ensina a usar a API de reflection para criar códigos genéricos que convertem objetos em DTOs, transformando objetos Java em JSON e garantindo a proteção de dados. Os alunos aprenderão a implementar testes robustos com JUnit e a manipular objetos em tempo de execução, utilizando boas práticas de programação. O curso é voltado para desenvolvedores que já possuem conhecimento básico em Java e desejam aprofundar suas habilidades em reflection.

Enviado por

Ricardo Alves
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 12

Java Reflection: simplifique a conversão de objetos

Faça esse curso de Java e:


• Escreva códigos genéricos e flexíveis com a API de reflection do Java
• Aprenda técnicas para converter objetos em DTOs sem repetição de código
• Transforme objetos Java em JSON de forma eficiente
• Domine as melhores práticas para proteção de dados e tráfego de informações entre camadas de
uma aplicação
• Construa cenários de testes robustos com a biblioteca JUnit
• Analise e manipule objetos e classes em tempo de execução
Aulas
• A classe Class
• 0 / 6 24min
• Apresentação
• Falando sobre Reflection
• Usando a classe Class
• Implementando reflection para DTOs
• Mão na massa: passando a classe Target como parâmetro
• O que aprendemos?
• getDeclaredConstructor e getDeclaredFields

0 / 8 13min

• Projeto da aula anterior


• Instanciando uma classe
• Instanciando dinamicamente uma classe
• Pegando campos de uma classe
• Transformando campos semelhantes
• Para saber mais: Java reflection e performance
• Mão na massa: como percorrer campos antes do Java 8
• O que aprendemos?
• Comparando campos

0 / 7 20min

• Projeto da aula anterior


• Criando o método validar
• Permitindo acesso a campos privados
• Acessando campos privados
• Métodos set e get
• Mão na massa: código sem setAcessible(true)
• O que aprendemos?
• Testando a aplicação

0 / 9 36min

• Projeto da aula anterior


• Usando JUnit para teste
• Teste shouldTransform
• Garantindo a integridade na conversão de objetos
• Teste shouldNotTransform
• Teste shouldTransformWhenSomeFieldIsNull
• Mão na massa: criando fixtures
• Projeto final do curso
• O que aprendemos?
• Transformando objeto para JSON

0 / 9 29min

• Usando a biblioteca jackson-databind


• Convertendo para JSON
AULA 1 A CLASSE CLASS

01 Apresentação

Olá! Meu nome é João Victor Martins e serei seu instrutor neste curso de Java Reflection! Se você
ainda não tem familiaridade com a API de Reflection, esta é uma ótima oportunidade para descobrir
esse poderoso recurso do Java.

Audiodescrição: João Victor é um homem branco, de cabelo liso curto preto, barba preta,
sobrancelhas pretas e olhos pretos. Ele veste uma camisa preta e está sentado em frente a uma
parede clara iluminada em gradiente azul.

Pré-requisitos
É recomendável que você tenha conhecimento prévio sobre o núcleo do Java, como orientação a
objetos, classes e interfaces, pois isso facilitará seu aprendizado durante o curso.

O que vamos aprender?


Se você já possui esses conhecimentos e deseja aprofundar-se em Reflection, agregando habilidades
específicas a seu repertório, este é o lugar certo. Neste curso, vamos simular um código que realiza a
transformação de objetos, como, por exemplo, um objeto Pessoa, com suas características e
métodos, para um objeto DTO. Lembrando que seguiremos boas práticas visando a proteção de
dados, e é vantajoso utilizar DTOs para o tráfego entre camadas da aplicação.

Nosso código será abstrato. Vamos capturar um input, nesse caso, um objeto Pessoa, e transformá-
lo em PessoaDTO. Utilizaremos um código genérico, empregando genéricos e a API de Reflection,
evitando a instanciação manual de objetos e suas transformações para cada caso. Evitaremos a
necessidade de instanciar um EnderecoDTO e atribuir todos os seus campos manualmente, por
exemplo. Com isso, teremos um código reutilizável.

Para verificar o funcionamento, realizaremos diversos cenários de teste com o JUnit, uma biblioteca
para testes de unidade em Java. Exploraremos situações onde a transformação deve acontecer,
quando não deve, ou quando um campo está nulo.

Além disso, teremos uma aula adicional sobre a conversão de objetos Java para JSON, uma demanda
comum atualmente. Utilizaremos uma biblioteca externa, mas ainda aproveitando a API de
Reflection, para testar a transformação completa e verificar o resultado em formato JSON.

Conclusão
Se você tem interesse em enriquecer seus conhecimentos sobre Reflection e APIs Java, nos
acompanhe neste curso. Aguardamos você no próximo vídeo!
02 Falando sobre Reflection

A grande maioria das pessoas desenvolvedoras de software já tiveram que trabalhar com sistemas
que possuem múltiplas camadas. Quando falamos de múltiplas camadas, nos referimos a camadas
como controllers, services e repositories.

Estas camadas servem para separar e atribuir responsabilidades únicas às nossas classes, tornando o
código mais legível. Portanto, é muito comum realizarmos essa separação. Existem vários tipos de
arquitetura que promovem essa divisão, mas, ao trabalharmos com código, sempre lidamos com
camadas.

Um aspecto comum é o objeto que trafegamos entre essas camadas. Vamos considerar uma
arquitetura mais simples, que inclui o controller, o service e o repository. A camada controller é a
que recebe a requisição da pessoa usuária, que pode ser um insert ou a solicitação de uma lista, por
exemplo. Essa requisição chega ao Controller.

Temos uma camada intermediária, geralmente chamada de classe de serviço, onde se encontram as
regras de negócio. Por fim, temos o repository, onde recebemos informações e salvamos detalhes
no banco de dados. Assim, o repository é utilizado para armazenar essas informações.

Falando sobre Reflection


Na IDE, começaremos com a classe PessoaRepository aberta, que simula o comportamento da
pessoa. Há um método list() que retorna uma Pessoa que instanciamos manualmente.

A classe Pessoa possui atributos privados, como id, nome e cpf, para promover o encapsulamento.
Usamos um construtor para criar a classe Pessoa, e métodos getters são disponibilizados para
acessar as informações da pessoa.

Quando trafegamos esses dados entre camadas, por exemplo, a partir de PessoaService,
chamamos PessoaRepository. O método list() retorna uma instância de Pessoa, que seria
encaminhada para o controller e, em seguida, retornada para a pessoa usuária. No entanto, não é
uma boa prática retornar objetos diretos do banco de dados, que podem conter informações
sensíveis como o ID.

Para proteger nosso domínio e os dados, criamos classes DTO (Data Transfer Object, ou Objeto de
Transferência de Dados), que contêm apenas os campos necessários para o cliente. Assim,
protegemos nosso objeto, no caso, a pessoa.

Geralmente, fazemos uma transformação de forma explícita em alguns projetos. Por exemplo: na
classe PessoaService, teríamos um método list() que retorna PessoaDTO. Este método chama
o PessoaRepository(), obtém uma Pessoa e a transforma em PessoaDTO.

Para isso, usamos o construtor PessoaDTO(), passando os campos necessários, e retornamos esta
nova instância sem o ID, que é uma informação do banco de dados irrelevante para a pessoa usuária.

PessoaService.java:

package br.com.alura;

public class PessoaService {


public PessoaDTO list() {
Pessoa pessoa = new PessoaRepository().list();
PessoaDTO pessoaDTO = new PessoaDTO(pessoa.getNome(),
pessoa.getCpf());
return pessoaDTO;
}
}

Embora seja possível realizar essa transformação no arquivo PessoaRepository.java, preferimos


deixar essa responsabilidade para o serviço, pois consideramos que o repositório deve se concentrar
mais no banco de dados.

O processo de transformar objetos em DTOs pode se tornar trabalhoso, principalmente quando


temos vários domínios e precisamos realizar essas conversões frequentemente. Seria ideal ter uma
função ou biblioteca capaz de realizar a conversão de uma classe para uma classe DTO de maneira
abstrata.

Essa lógica de um código mais abstrato conseguiria identificar que recebemos, por exemplo, uma
classe Pessoa. Existe uma classe PessoaDTO para transformar? Quais são os campos entre eles que
precisamos usar de Pessoa para PessoaDTO? A lógica faz isso: compara campos, tudo isso sem a
necessidade do trabalho manual de instanciar.

É nesse momento que entra a API Java Reflection. Com a documentação Java Reflection API aberta,
temos a informação de que a Reflection, basicamente, habilita o código Java a descobrir informações
sobre campos, métodos e construtores de classes carregadas, permitindo que um código Java
manipule objetos de uma maneira mais flexível e abstrata.

A documentação da Oracle nos mostra o que é a Java Reflection API e como ela permite que um
código descubra informações sobre as classes, sem necessidade de conhecer os detalhes
previamente. Com isso, podemos transformar classes em DTOs de forma mais abstrata e eficiente.

Pode parecer confuso a princípio, mas conseguiremos usar a Reflection para fazer o trabalho que
antes fazíamos manualmente, passaremos por todos os detalhes e você vai perceber que não é tão
trivial, mas conseguiremos fazer um acompanhamento interessante e deixar nosso código muito
elegante.

Ainda na documentação, temos a categoria "API Specification", que são as especificações da API Java
Reflection, onde encontramos o link java.lang.reflect, que é de fato o pacote com as classes e
interfaces da Reflection que usaremos no nosso código.

Acessando este link, encontramos um sumário de interfaces do pacote java.lang.reflect que


iremos usar para fazer a transformação das classes em classes DTO de forma muito mais abstrata.

Conclusão
No próximo vídeo, vamos começar a melhorar o nosso código e explorar mais a fundo o
desenvolvimento com a API de Reflection. Te encontramos lá!
03 Usando a classe Class

Falamos anteriormente que vamos criar um código bem abstrato e elegante, que vai fazer a
transformação da classe em uma classe DTO usando generics. Este será nosso objetivo a partir de
agora!

Usando a classe Class


Começaremos na IDE sem nada aberto, para começar agora de fato o desenvolvimento da nova
estrutura. Com a aba do projeto aberta na lateral esquerda da IDE, vamos criar uma nova classe que
será responsável por fazer essa transformação genérica.

Temos uma estrutura com as pastas "src", "main", "java", e dentro de "java" temos o pacote
"br.com.alura", onde estão as classes usamos para simular o comportamento da transformação:
Pessoa, PessoaDTO, PessoaRepository, e PessoaService.

Não precisamos nos preocupar com essas classes agora, então basta criar um novo pacote, clicando
no botão direito em "br.com.alura". Esse pacote se chamará "br.com.alura.refl", referente a reflexão.

Não usaremos "reflection", porque já temos a API Reflection e podemos confundir o pacote
"reflection" com o pacote Reflection do Java.

Criando a classe Transformator


Uma vez criado o novo pacote, criaremos uma nova classe dentro dele, chamada Transformator.

Transformator.java:

package br.com.alura.refl;

public class Transformator {

A ideia é ter uma função que vamos poder chamar. Essa função vai receber qualquer tipo e vai
retornar o tipo dessa entrada DTO. Mas como fazemos para informar qualquer tipo para a classe?

Antes, estávamos trabalhando com Pessoa, transformando em PessoaDTO; agora, falamos de algo
que não sabemos e temos que transformar. Como passamos esse parâmetro?

Vamos começar criando na classe Transformator o primeiro método, que será público (public).
Por enquanto, deixaremos ele como void, e definiremos o nome do método como transform().

package br.com.alura.refl;

public class Transformator {

public void transform() {

}
Temos uma função básica. Agora precisamos receber qualquer tipo. Para receber qualquer tipo,
temos outra API, outro recurso no Java, que são os generics.

Usando Generics
Os generics nos permitem deixar o código genérico para que possamos receber qualquer coisa.
Então, em vez de trabalhar com tipos específicos, trabalhamos com os generics, que podem receber
qualquer coisa. Na documentação, encontramos a seção "Generic Types".

Basicamente, podemos trabalhar com as convenções presentes na documentação, como o "The


Diamond", que vamos criar na sequência, que seria o sinal de menor e maior, onde vamos passar
letras e valores que vão representar o parâmetro que será informado ainda no momento em
execução.

No nosso caso, analisando a interface de exemplo da documentação, temos uma interface Pair que
tem o K e o V dentro do "Diamond". Com isso, falamos que o K e o V são retornos das funções
getKey() e getValue(), respectivamente.

public interface Pair<K, V> {


public K getKey();
public V getValue();
}

Faremos algo semelhante no nosso código para conseguirmos receber qualquer parâmetro.

De volta à IDE, em vez de o método transform() ser um void, ele será um <I, O>, sendo o I
referente à entrada (input), e o O referente à saída (output).

Transformator.java:

package br.com.alura.refl;

public class Transformator {

public <I, O> transform() {

Dessa forma, já usamos a convenção do "Diamond" do generics. Agora, o método transform() vai
receber esse parâmetro qualquer, representado pelo I.

Quando digitamos I entre os parênteses, o IntelliJ sugere a opção "type parameter of transform".
Esse é o tipo do parâmetro do método transform(). Chamaremos o I de input.

public <I, O> transform(I input) {

Temos um método que pode receber qualquer tipo que será representado pelo input declarado no
generics. Ainda não vamos trabalhar com o O, porque nossa intenção é, primeiro, pegar a classe de
entrada e fazer o tratamento para depois transformar para o output, que seria o data class, mas isso
será feito mais adiante. O que queremos agora é pegar os detalhes da classe que recebemos no
input.

Para isso, chamamos a variável input no escopo do método e digitamos um ponto (.). Como
sugestão, temos o getClass(). Com o getClass(), conseguimos ter acesso a algumas
informações da classe.

Se analisarmos, o retorno fornecido é um tipo Class<?> que chamaremos de source. Com a


interrogação em Class<?>, falamos para o código que pegamos do input o getClass(),
retornamos uma Class<?>, mas não sabemos o tipo ainda. Para nós, ainda está muito genérico e
abstrato.

public <I, O> transform(I input) {


Class<?> source = input.getClass();
}

Nosso código agora está mais funcional. O método transform() ainda reclama um erro, porque
não há um return type e precisamos retornar algo, mas trabalharemos isso ao longo do
desenvolvimento.

Se abrirmos o input.getClass(), observaremos que o Class<?> é do objeto Object.java, que


é o pai de todos os objetos, o objeto de mais alta hierarquia no modelo do Java.

Por outro lado, se abrirmos Class<?>, ele será uma classe Java como qualquer outra, no arquivo
Class.java. Porém, agora começamos a encontrar algumas coisas referentes à reflexão.

Temos várias implementações do que podemos fazer com a classe Class<?>. É dela que vamos
pegar, por exemplo, os campos, os construtores, tudo através da classe Class<?>, para
conseguirmos instanciar o objeto e transformar.

Já estamos usando a reflexão da Class<?>, e agora a ideia é realmente conseguir usar esses
recursos a nosso favor. Temos uma source, e agora precisamos transformar essa classe que é o
nosso input, na classe input com o final DTO. O padrão da nomenclatura será, por exemplo, ao
entrar com a classe Pessoa, ter um PessoaDTO para conseguir fazer essa transformação.

Nesse caso, podemos usar source.forName. Entre os parênteses de forName(), vamos chamar
source.getClass() novamente, mas agora vamos concatenar o nome DTO.

public <I, O> transform(I input) {


Class<?> source = input.getClass();
Class<?> target = sourceforName(source.getClass() + "DTO")
}

Com isso, falamos que o nosso objetivo, isto é, o nosso target será ter uma classe que será o nome
da source, que pegamos com getClass(), mais o final DTO.

Se o input recebe a classe Pessoa, pegamos com getClass() essa classe, e agora o objetivo é
instanciar uma classe PessoaDTO, que pegamos com source.getClass() + "DTO", para então
podermos fazer a transformação.
O código é um pouco complicado, mas conforme desenvolvemos, encontramos os detalhes e as
coisas começam a fazer sentido.

Você pode estar se perguntando: por que temos um erro de compilação no forName()? Porque
precisamos adicionar na assinatura do método transform() a exceção
ClassNotFoundException.

public <I, O> transform(I input) throws ClassNotFoundException {


Class<?> source = input.getClass();
Class<?> target = source.forName(source.getClass() + "DTO");
}

Em forName(), se, por exemplo, ele tentar buscar uma classe PessoaDTO e ela não existir, teremos
ClassNotFoundException, que é uma exceção checada. Precisamos deixar explícito que o
método transform() pode dar esse tipo de exceção.

Feito isso, o nosso código passa a compilar, exceto o método transform(), devido ao retorno que
ainda não declaramos.

Conclusão
O nosso objetivo era pegar a classe de alguma forma, para começarmos a trabalhar com os seus
parâmetros e construtores. O resto de pegar campos, construtores e instanciar, deixaremos para os
próximos vídeos. Nossa ideia é realmente dar um passo de cada vez para a concepção da nossa
função.

Vamos continuar o trabalho? Para isso, te esperamos no próximo vídeo!

04 Implementando reflection para DTOs

Você está trabalhando em um projeto e precisa criar um método chamado transformToDTO que
deve receber uma classe de qualquer tipo e retornar a sua versão DTO.

public <I, O> O transformToDTO(I input) throws ClassNotFoundException {


// Implementação do método
}
Considerando as práticas e conteúdos abordados no curso, como devemos proceder para que a
implementação seja capaz de receber um tipo genérico e retornar a classe DTO correspondente?

Resposta:

Class.forName(input.getClass().getName() + "DTO");
A concatenação do nome da classe com "DTO" e o uso do método forName são consistentes com o
conteúdo do curso e permitem carregar dinamicamente a classe DTO correspondente, seguindo a
nomenclatura padrão estabelecida.
05 Mão na massa: passando a classe Target como parâmetro

Durante o desenvolvimento do nosso projeto, tomamos a decisão de sempre transformar o objeto


passado por parâmetro para a sua versão com o sufixo DTO. Isso traz algumas vantagens, pois temos
mais controle sobre a transformação, mas caso esse código seja uma lib para terceiros, precisaríamos
de uma documentação muito bem desenvolvida para evitar exceções nos códigos dos nossos
clientes.

Uma maneira diferente de desenvolver o nosso projeto e dar um pouco mais de liberdade para os
clientes seria permitir que o usuário passasse o objeto a ser transformado e qual classe vai ter o
resultado da transformação. Assim, os clientes ficam livres para chamar a classe com o sufixo DTO, se
quiserem, ou com qualquer outro nome que faça sentido. Abaixo na Opinião você pode conferir
como fica o código.

Opinião do instrutor


A primeira mudança seria passar um novo parâmetro no método transform:

public <T, U> U transform(T source, Class target) throws


IllegalAccessException, NoSuchMethodException, InvocationTargetException,
InstantiationException

Em vez de usarmos o método forName para pegar o source.getName e adicionar o sufixo DTO,
usamos apenas o parâmetro target que já é a class:

Class<?> sourceClass = source.getClass();


Class<?> targetClass = target;
Durante o desenvolvimento do código no curso, usamos o nome do objeto a ser retornado como
target, porém, neste caso recebemos um objeto chamado target como parâmetro. Por isso,
vamos precisar alterar o nome da variável resultante da chamada
targetClass.getDeclaredConstructor().newInstance();.

U instanceOfTarget = (U)
targetClass.getDeclaredConstructor().newInstance();

Após essa alteração, todo o código será o mesmo de antes, porém, precisamos alterar a forma que
nossos testes fazem a chamada ao método transform. Vamos ver como fica, por exemplo, o teste
shoudTransform.

@Test
public void shouldTransform() throws InvocationTargetException,
IllegalAccessException, NoSuchMethodException, InstantiationException {
Transformator transformator = new Transformator();
PessoaDTO transformated = transformator.transform(pessoa,
PessoaDTO.class);

Assertions.assertInstanceOf(PessoaDTO.class, transformated);

Assertions.assertEquals("João", transformated.getNome());
Assertions.assertEquals(32, transformated.getIdade());
Assertions.assertEquals("04333958210", transformated.getCpf());
}
Como podemos perceber, agora passamos a classe que queremos transformar como segundo
parâmetro do método transform. No nosso caso continua sendo o PessoaDTO, porém, chamando
o .class. Poderíamos passar qualquer classe e isso nos traria mais flexibilidade no projeto.

06 O que aprendemos?

Nessa aula, você aprendeu:


• Que a API de reflection é um conjunto de classes e interfaces que permite que um programa Java
examine e manipule elementos internos de um programa em tempo de execução. Com a reflexão, é
possível obter informações sobre classes, interfaces, métodos, campos, construtores e outros
componentes de um programa Java;
• Que a classe Class é uma parte essencial da reflexão em Java, permitindo que as pessoas
desenvolvedoras acessem e manipulem informações sobre tipos em tempo de execução,
proporcionando flexibilidade e capacidade de adaptação em várias situações de programação;
• Utilizar o método forName, que é usado para carregar dinamicamente uma classe durante a
execução do programa.

Você também pode gostar