Controle de Acesso Baseado no Java 11 Nest
1. Introdução
Neste tutorial, exploraremosnests, o novo contexto de controle de acesso introduzido no Java 11.
2. Antes do Java 11
2.1. Tipos aninhados
Java permite que classes e interfaces sejamnested within each other. Essesnested types have unrestricted access entre si, incluindo campos, métodos e construtores privados.
Considere o seguinte exemplo de classe aninhada:
public class Outer {
public void outerPublic() {
}
private void outerPrivate() {
}
class Inner {
public void innerPublic() {
outerPrivate();
}
}
}
Aqui, embora o métodoouterPrivate() sejaprivate, ele é acessível a partir do métodoinnerPublic().
Podemos descrever um tipo de nível superior, além de todos os tipos aninhados nele, como formando um ninho. Dois membros de um ninho são descritos como companheiros de ninho.
Assim, no exemplo acima,Outer eInner juntos formam um ninho e são companheiros de ninho um do outro.
2.2. Método de ponte
As regras de acesso da JVM não permitem acesso privado entre companheiros de ninho. Idealmente, devemos obter um erro de compilação para o exemplo acima. No entanto, o compilador de código-fonte Java permite o acesso introduzindo um nível de indireção.
Por exemplo,an invocation of a private member is compiled into an invocation of a compiler-generated, package-private, bridging method in the target class, which in turn invokes the intended private method.
Isso acontece nos bastidores. Esse método de ponte aumenta um pouco o tamanho de um aplicativo implantado e pode confundir usuários e ferramentas.
2.3. Usando Reflexão
Uma conseqüência adicional disso é que a reflexão central também nega o acesso. Isso é surpreendente, dado que as invocações reflexivas devem se comportar da mesma forma que as invocações no nível da fonte.
Por exemplo, se tentarmos chamarouterPrivate() reflexivamente da classeInner:
public void innerPublicReflection(Outer ob) throws Exception {
Method method = ob.getClass().getDeclaredMethod("outerPrivate");
method.invoke(ob);
}
Teríamos uma exceção:
java.lang.IllegalAccessException:
Class com.example.Outer$Inner can not access a member of class com.example.Outer with modifiers "private"
O Java 11 tenta abordar essas preocupações.
3. Controle de acesso baseado em Nest
Java 11 brings the notion of nestmates and the associated access rules within the JVM. Isso simplifica o trabalho dos compiladores de código-fonte Java.
Para conseguir isso, o formato do arquivo de classe agora contém dois novos atributos:
-
Um membro de ninho (normalmente a classe de nível superior) é designado como host do ninho. Ele contém um atributo (NestMembers) para identificar os outros membros de ninho conhecidos estaticamente.
-
Cada um dos outros membros do ninho possui um atributo (NestHost) para identificar seu host do ninho.
Portanto, para os tiposCeD serem companheiros de ninho, eles devem ter o mesmo hospedeiro de ninho. Um tipoC afirma ser um membro do ninho hospedado porD, se listarD em seu atributo NestHost. A associação é validada seD também listaC em seu atributo NestMembers. Além disso, o tipoD é implicitamente um membro do ninho que hospeda.
Agora existeno need for the compiler to generate the bridge methods.
Finalmente, o controle de acesso baseado em ninho remove o comportamento surpreendente da reflexão principal. Portanto, o métodoinnerPublicReflection() mostrado na seção anterior será executado sem nenhuma exceção.
4. API Nestmate Reflection
Java 11 provides means to query the new class file attributes using core reflection. A classejava.lang.Class contém os três novos métodos a seguir.
4.1. getNestHost()
Isso retorna o host do ninho ao qual este objetoClass pertence:
@Test
public void whenGetNestHostFromOuter_thenGetNestHost() {
is(Outer.class.getNestHost().getName()).equals("com.example.Outer");
}
@Test
public void whenGetNestHostFromInner_thenGetNestHost() {
is(Outer.Inner.class.getNestHost().getName()).equals("com.example.Outer");
}
Ambas as classesOutereInner pertencem ao host de ninhocom.example.Outer.
4.2. isNestmateOf()
Isso determina se oClass fornecido é um companheiro deste objetoClass:
@Test
public void whenCheckNestmatesForNestedClasses_thenGetTrue() {
is(Outer.Inner.class.isNestmateOf(Outer.class)).equals(true);
}
4.3. getNestMembers()
Isso retorna uma matriz contendo objetosClass representando todos os membros do ninho ao qual este objetoClass pertence:
@Test
public void whenGetNestMembersForNestedClasses_thenGetAllNestedClasses() {
Set nestMembers = Arrays.stream(Outer.Inner.class.getNestMembers())
.map(Class::getName)
.collect(Collectors.toSet());
is(nestMembers.size()).equals(2);
assertTrue(nestMembers.contains("com.example.Outer"));
assertTrue(nestMembers.contains("com.example.Outer$Inner"));
}
5. Detalhes de compilação
5.1. Método de ponte antes do Java 11
Vamos nos aprofundar nos detalhes do método de ponte gerado pelo compilador. Podemos ver isso desmontando o arquivo de classe resultante:
$ javap -c Outer
Compiled from "Outer.java"
public class com.example.Outer {
public com.example.Outer();
Code:
0: aload_0
1: invokespecial #2 // Method java/lang/Object."":()V
4: return
public void outerPublic();
Code:
0: return
static void access$000(com.example.Outer);
Code:
0: aload_0
1: invokespecial #1 // Method outerPrivate:()V
4: return
}
Aqui, além do construtor padrão e do método públicoouterPublic(),notice the method access$000(). The compiler generates this as a bridging method.
OinnerPublic() passa por este método para chamarouterPrivate():
$ javap -c Outer\$Inner
Compiled from "Outer.java"
class com.example.Outer$Inner {
final com.example.Outer this$0;
com.example.Outer$Inner(com.example.Outer);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/example/Outer;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."":()V
9: return
public void innerPublic();
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/example/Outer;
4: invokestatic #3 // Method com/example/Outer.access$000:(Lcom/example/Outer;)V
7: return
}
Observe o comentário na linha # 19. Aqui,innerPublic() chama o método de ponteaccess$000().
5.2. Companheiros de aninhamento com Java 11
O compilador Java 11 geraria o seguinte arquivo de classeOuter desmontado:
$ javap -c Outer
Compiled from "Outer.java"
public class com.example.Outer {
public com.example.Outer();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public void outerPublic();
Code:
0: return
}
Observe que não há um método de ponte gerado pelo compilador. Além disso, a classeInner agora pode fazer uma chamada direta para o métodoouterPrivate():
$ javap -c Outer\$Inner.class
Compiled from "Outer.java"
class com.example.Outer$Inner {
final com.example.Outer this$0;
com.example.Outer$Inner(com.example.Outer);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/example/Outer;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."":()V
9: return
public void innerPublic();
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/example/Outer;
4: invokevirtual #3 // Method com/example/Outer.outerPrivate:()V
7: return
}
6. Conclusão
Neste artigo, exploramos o controle de acesso baseado em ninho introduzido no Java 11.
Como de costume, trechos de código podem ser encontradosover on GitHub.