Metodi sintetici di Java

In questo post del blog, esamino il concetto di metodi sintetici Java. Il post riassume cos'è un metodo sintetico Java, come è possibile crearne e identificarlo e le implicazioni dei metodi sintetici Java sullo sviluppo Java.

La specifica del linguaggio Java (sezione 13.1) afferma "Tutti i costrutti introdotti dal compilatore che non hanno un costrutto corrispondente nel codice sorgente devono essere contrassegnati come sintetici, ad eccezione dei costruttori predefiniti e del metodo di inizializzazione della classe." Ulteriori indizi sul significato di sintetico in Java possono essere trovati nella documentazione Javadoc per Member.isSynthetic (). La documentazione di tale metodo afferma che restituisce "vero se e solo se questo membro è stato introdotto dal compilatore". Mi piace quella definizione molto breve di "sintetico": un costrutto Java introdotto dal compilatore.

Il compilatore Java deve creare metodi sintetici su classi nidificate quando i loro attributi specificati con il modificatore private sono accessibili dalla classe che lo racchiude. L'esempio di codice successivo indica questa situazione.

DemonstrateSyntheticMethods.java (la classe di inclusione richiama un attributo privato di classe annidata)

package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } } 

Il codice sopra viene compilato senza incidenti. Quando javap viene eseguito sul .classfile compilato , l'output è come mostrato nell'istantanea della schermata seguente.

Come indica l'istantanea della schermata sopra, un metodo sintetico con il nome access$100è stato creato sulla classe annidata NestedClassper fornire la sua stringa privata alla classe che la racchiude. Si noti che il metodo sintetico viene aggiunto solo per il singolo attributo privato della NestedClass a cui accede la classe che lo racchiude. Se cambio la classe inclusa per accedere a tutti gli attributi privati ​​di NestedClass, verranno generati metodi sintetici aggiuntivi. Il prossimo esempio di codice mostra come fare proprio questo e l'istantanea della schermata che segue dimostra che in quel caso vengono generati quattro metodi sintetici.

DemonstrateSyntheticMethods.java (la classe acclusa richiama quattro attributi privati ​​della classe annidata)

package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } } 

Come mostrano i due frammenti di codice precedenti e le immagini associate, il compilatore Java introduce metodi sintetici in base alle necessità. Quando solo uno degli attributi privati ​​della classe annidata è stato accessibile dalla classe che lo racchiude, solo un metodo sintetico ( access$100) è stato creato dal compilatore. Tuttavia, quando tutti e quattro gli attributi privati della classe nidificata erano accessibili dalla classe contenitrice, quattro corrispondenti metodi di sintesi sono stati generati dal compilatore ( access$100, access$200, access$300e access$400).

In tutti i casi in cui una classe di inclusione accede ai dati privati ​​della sua classe annidata, è stato creato un metodo sintetico per consentire che l'accesso avvenga. Cosa succede quando la classe annidata fornisce una funzione di accesso per i suoi dati privati ​​che la classe che la racchiude può utilizzare? Ciò è dimostrato nel listato di codice successivo e nel suo output come mostrato nell'istantanea della schermata successiva.

DemonstrateSyntheticMethods.java con la funzione di accesso pubblico della classe annidata per i dati privati

package dustin.examples; import java.util.Calendar; import java.util.Date; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); out.println("Date: " + nested.getDate()); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; private Date date = new Date(); public Date getDate() { return this.date; } } } 

L'istantanea della schermata sopra mostra che il compilatore non aveva bisogno di generare un metodo sintetico per accedere all'attributo Date privato nella classe nidificata perché la classe che lo racchiudeva accedeva a quell'attributo tramite il getDate()metodo fornito . Anche con getDate()fornito, il compilatore avrebbe generato un metodo sintetico per accedere al datecodice che lo racchiudeva per accedere datedirettamente all'attributo (come proprietà) piuttosto che tramite il metodo di accesso.

L'ultima istantanea dello schermo fa apparire un'altra osservazione. Come getDate()mostra il metodo appena aggiunto publicnell'istantanea della schermata, i modificatori come sono inclusi nell'output di javap. Poiché non viene mostrato alcun modificatore per i metodi sintetici creati dal compilatore, sappiamo che sono a livello di pacchetto (o pacchetto privato). In breve, il compilatore ha creato metodi privati ​​del pacchetto per accedere agli attributi privati.

Le API di riflessione Java forniscono un altro approccio per determinare i metodi sintetici. L'elenco di codice successivo è per uno script Groovy che utilizzerà le API di riflessione Java per fornire convenientemente dettagli sui metodi della classe nidificata mostrata sopra.

ReflectOnMethods.groovy

#!/usr/bin/env groovy import java.lang.reflect.Method import java.lang.reflect.Modifier if (args == null || args.size() < 2) { println "Outer and nested class names must be provided." println "\nUsage #1: reflectOnMethods qualifiedOuterClassName nestedClassName\n" println "\nUsage #2: groovy -cp classpath reflectOnMethods.groovy qualifiedOuterClassName nestedClassName\n" println "\t1. Include outer and nested classes on classpath if necessary" println "\t2. Do NOT include \$ on front of nested class name.\n" System.exit(-1) } def enclosingClassName = args[0] def nestedClassName = args[1] def fullNestedClassName = enclosingClassName + '$' + nestedClassName def enclosingClass = Class.forName(enclosingClassName) Class nestedClass = null enclosingClass.declaredClasses.each { if (!nestedClass && fullNestedClassName.equals(it.name)) { nestedClass = it } } if (nestedClass == null) { println "Unable to find nested class ${fullNestedClassName}" System.exit(-2) } // Use declaredMethods because don't care about inherited methods nestedClass.declaredMethods.each { print "\nMethod '${it.name}' " print "is ${getScopeModifier(it)} scope, " print "${it.synthetic ? 'is synthetic' : 'is NOT synthetic'}, and " println "${it.bridge ? 'is bridge' : 'is NOT bridge'}." } def String getScopeModifier(Method method) { def modifiers = method.modifiers def isPrivate = Modifier.isPrivate(modifiers) def isPublic = Modifier.isPublic(modifiers) def isProtected = Modifier.isProtected(modifiers) String scopeString = "package-private" // default if (isPublic) { scopeString = "public" } else if (isProtected) { scopeString = "protected" } else if (isPrivate) { scopeString = "private" } return scopeString } 

Quando lo script Groovy sopra viene eseguito sulla classe e sulla classe nidificata mostrate sopra, l'output è quello mostrato nell'istantanea della schermata successiva.

I risultati dello script Groovy mostrato nell'immagine precedente verificano quanto ci aveva già detto javap: ci sono quattro metodi sintetici e un metodo non sintetico definiti sulla classe annidata NestedClass. Lo script ci dice anche che i metodi sintetici generati dal compilatore sono ambito privato del pacchetto.

L'aggiunta di metodi sintetici alla classe annidata a livello di ambito privato del pacchetto non è l'unica cosa che il compilatore ha fatto nell'esempio precedente. Ha anche cambiato l'ambito della stessa classe nidificata dall'impostazione privata nel codice a privata del pacchetto nel .classfile. Infatti, mentre i metodi sintetici sono stati aggiunti solo nel caso in cui la classe che la racchiude accedeva all'attributo private, il compilatore rende sempre la classe nidificata package-private anche se è specificata come privata nel codice. La buona notizia è che questo è un artefatto risultante del processo di compilazione, il che significa che il codice non può essere compilato così com'è rispetto al livello di ambito modificato della classe nidificata o dei suoi metodi sintetici. Il runtime è dove le cose possono diventare rischiose.

La classe, Rogue, tenta di accedere ad alcuni dei metodi sintetici NestedClass. Il suo codice sorgente viene mostrato di seguito, seguito dall'errore del compilatore visto durante il tentativo di compilare questo codice sorgente Rogue.

Rogue.java cerca di accedere a metodi sintetici in fase di compilazione

package dustin.examples; import static java.lang.System.out; public class Rogue { public static void main(final String[] arguments) { out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); } } 

Il codice precedente non verrà compilato, anche per il metodo non sintetico getDate(), e segnala questo errore:

Buildfile: C:\java\examples\synthetic\build.xml -init: compile: [javac] Compiling 1 source file to C:\java\examples\synthetic\classes [javac] C:\java\examples\synthetic\src\dustin\examples\Rogue.java:9: dustin.examples.DemonstrateSyntheticMethods.NestedClass has private access in dustin.examples.DemonstrateSyntheticMethods [javac] out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); [javac] ^ [javac] 1 error BUILD FAILED C:\java\examples\synthetic\build.xml:29: Compile failed; see the compiler error output for details. Total time: 1 second 

Come indica il messaggio di errore di compilazione sopra riportato, anche il metodo non sintetico sulla classe nidificata è inaccessibile in fase di compilazione perché la classe nidificata ha un ambito privato. Nel suo articolo Java Insecurities: Accounting for Subtleties That Can Compromise Code, Charlie Lai discute potenziali situazioni in cui queste modifiche introdotte dal compilatore sono vulnerabilità di sicurezza. Faisal Feroz va oltre e afferma, nel post How to Write Secure Java Code, "Don't use Inner Classes" (vedi Classi annidate, interne, membri e di primo livello per i dettagli sulle classi interne come sottoinsieme di classi annidate) .

Molti di noi possono dedicarsi a lungo allo sviluppo di Java senza bisogno di una comprensione significativa dei metodi sintetici. Tuttavia, ci sono situazioni in cui la consapevolezza di questi è importante. Oltre ai problemi di sicurezza relativi a questi, è anche necessario essere consapevoli di cosa sono durante la lettura delle tracce dello stack. Nomi dei metodi quali access$100, access$200, access$300, access$400, access$500, access$600, e access$1000nell'analisi di stack riflettono metodi sintetici generati dal compilatore.

Post originale disponibile su //marxsoftware.blogspot.com/

.

Questa storia, "Java's Synthetic Methods" è stata originariamente pubblicata da JavaWorld.