[PT-BR] Reflection com JPMS

Posted on

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 {

}
Enter fullscreen mode

Exit fullscreen mode

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;
}
Enter fullscreen mode

Exit fullscreen mode

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;
}
Enter fullscreen mode

Exit fullscreen mode

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);
    }
}
Enter fullscreen mode

Exit fullscreen mode

A classe Pessoa possui apenas um atributo privado.

package br.com.modelo;

public class Pessoa {
    private Double peso;
} 
Enter fullscreen mode

Exit fullscreen mode

O módulo br.com.modelo exporta o pacote com a classe Pessoa

module br.com.modelo {
     exports br.com.modelo; 
}
Enter fullscreen mode

Exit fullscreen mode

O módulo br.com.principal, faz o requires no módulo br.com.modelo

module br.com.principal {
     requires br.com.modelo;
} 
Enter fullscreen mode

Exit fullscreen mode

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)
Enter fullscreen mode

Exit fullscreen mode

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;
}
Enter fullscreen mode

Exit fullscreen mode

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! =)

Leave a Reply

Your email address will not be published.