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! =)
Top comments (2)
Ótimo conteúdo, excelente explicação.. Muito obrigado.. 😉
Essa explicação facilitou muito a minha vida! Escrita de fácil entendimento! Obrigado!