[PT-BR] Reflection com JPMS
Tags: java
Published: 2020-08-14 20:53:47

Desde que surgiu o JPMS (Java Platform Module System), alguns comportamentos da plataforma sofreram alterações. Podemos citar como exemplo o uso de reflexão. Antes dos módulos, com a Reflection API, era possível quebrar o encapsulamento dos objetos e acessar seus atributos privados. Com o JPMS este comportamento mudou e agora o desenvolvedor precisa configurar o módulo para permitir reflexão, caso contrário uma exceção será lançada. Existem alguns detalhes por trás dessa configuração e a ideia é explorá-los no decorrer do post.

_Vale destacar que, mesmo nas versões em que existe o JPMS (9+), a mudança citada acima só ocorrera se estiver compilando com **module-path**_. 

Existem duas formas para permitir reflexão. A primeira delas é usando a palavra reservada `open` na declaração do módulo, assim será permitido o uso de reflection em todos os pacotes. 

 ```
open module br.com.exemplo {

}
```

A outra opção é permitindo a reflexão por pacote, para isso usamos a palavra reservada `opens` antes da declaração do pacote. 

```
module br.com.exemplo {
     opens br.com.exemplo.modelo;
}
```
Qualquer tentativa de usar reflexão em um pacote que não seja o `br.com.exemplo.modelo` resultará em uma exceção. 
Esta última opção ainda permite usar um `opens` declarativo, ou seja, informando qual módulo específico poderá realizar reflexão no pacote. Podemos fazer isso usando a palavra `to`

```
module br.com.exemplo {
     opens br.com.exemplo.modelo to br.com.outroexemplo.dao;
}
```
Dessa maneira estamos dizendo que apenas o módulo `br.com.outroexemplo.dao` poderá fazer o uso de reflexão no pacote `br.com.exemplo.modelo`.

# Vamos para a prática!!

Vamos usar a classe Principal do módulo `br.com.principal` para fazer um acesso reflexivo na classe Pessoa do módulo `br.com.modelo`. 

```
package br.com.principal;

import java.lang.reflect.Field;
import br.com.modelo.Pessoa;

class Principal { 
    public static void main(String[] args) throws NoSuchFieldException {
        Field f = Pessoa.class.getDeclaredField("peso");
        f.setAccessible(true);
    }
}
```

A classe Pessoa possui apenas um atributo privado. 

```
package br.com.modelo;

public class Pessoa {
    private Double peso;
} 
```
O módulo `br.com.modelo` exporta o pacote com a classe Pessoa

```
module br.com.modelo {
     exports br.com.modelo; 
}
```
O módulo `br.com.principal`, faz o requires no módulo `br.com.modelo`

```
module br.com.principal {
     requires br.com.modelo;
} 
```
Podemos compilar o projeto com `javac -d mods --module-source-path src -m br.com.principal` e se não deu nenhum problema, usamos o `java --module-path mods -m br.com.principal/br.com.principal.Principal` para executar o programa. Como não configuramos nossos módulos para permitir reflexão, a saída é a seguinte: 

```
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.Double br.com.modelo.Pessoa.peso accessible: module br.com.modelo does not "opens br.com.modelo" to module br.com.principal
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:340)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:280)
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:176)
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:170)
	at br.com.principal/br.com.principal.Principal.main(Principal.java:9)
```
Para mudar este comportamento, basta que alteremos o `module-info.java` do módulo `br.com.modelo`, permitindo que o módulo `br.com.principal` possa fazer um acesso reflexivo em seu pacote. 

_**Aqui valem todas as regras citadas na introdução do post.**_

```
module br.com.modelo {
    exports br.com.modelo; 
    
    opens br.com.modelo to br.com.principal;
}
```
Se compilar e executar novamente, vamos ver que não aparece mais a exceção e conseguimos usar a reflexão. 

# Por debaixo dos panos

A verificação, se permite reflexão, é feito  pelo método `setAccessible`. Ele é um método _caller sensitive_ e sua característica é ter comportamentos diferentes dependendo de quem o chama. Quando a classe ou módulo deseja utilizar reflexão em um outro módulo, é verificado se existe a palavra reservada `open` ou `opens` no módulo de destino. Caso tenha, o acesso é feito com sucesso, caso contrário o resultado é uma exceção. Para saber quando um método é _caller sensitive_, basta procurar pela anotação `@CallerSensitive`.  

# Concluindo ... 

Nós vimos que, com o JPMS, as nossas classes ficam mais protegidas, garantindo um melhor encapsulamento. Caso seja necessário o uso de reflection, cabe a nós realizar as configurações nos módulos. Todo este comportamento é garantido por um método _caller sensitive_, que verificará se o módulo ou classe "chamadora" possui permissão para um acesso reflexivo no módulo de destino. A ideia era mostrar um pouco mais desses detalhes e espero ter alcançado, mas se ficou alguma dúvida, deixe seu comentário por aqui ou nas minhas redes sociais, que estarei a disposição. Valeu! =)