Статьи

Метаданные параметров конструктора / метода, доступные через отражение в JDK 8

Одна из наименее объявленных новых функций JDK 8 — это дополнительная возможность включать метаданные параметров в скомпилированные классы Java [JDK Enhancement Proposal ( JEP ) 118 ]. Эта функция позволяет приложениям Java получать доступ к информации метаданных этого параметра во время выполнения посредством отражения .

След API Reflection API Учебников Java включает урок под названием « Получение имен параметров метода», в котором обсуждается и демонстрируется, как применять эту новую функцию в Java 8. Урок включает в себя пример Java-класса MethodParameterSpy, который можно запускать с предоставленным Java-классом для указания характеристик. метода и параметров конструктора. В этом уроке также подчеркивается, что это необязательная функция, поскольку хранение метаданных дополнительных параметров в файлах .class увеличивает размер этих файлов. Урок также указывает на то, что в некоторых случаях имена параметров содержат конфиденциальную информацию, которую разработчик не хочет, чтобы она была доступна в скомпилированных файлах .class .

Метаданные дополнительных параметров могут быть включены в .class скомпилированные в Java 8, передав опцию -parameters компилятору javac . Эта опция -parameters также отображается, когда один из типов javac -help как показано на следующем снимке экрана.

jdk8JavacHelp-parametersHighlighted

На странице Oracle TechNotes в javac показано, как можно получить доступ к этим дополнительным данным параметров метода / конструктора во время выполнения: «Сохраняет формальные имена параметров конструкторов и методов в созданном файле класса, чтобы метод java.lang.reflect.Executable.getParameters из Reflection API может их получить ». Следующий фрагмент кода (класс с именем ParameterDisplayer ) демонстрирует это (акцент делается на метод displayParametersMetadata(String[]) ).

ParameterDisplayer.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package dustin.examples.jdk8;
 
import static java.lang.System.out;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
 
/**
 * Uses JDK 8 Parameter class to demonstrate metadata related to the parameters
 * of the methods and constructors of the provided class (includes private,
 * protected, and public methods, but does not include methods inherited from
 * parent classes; those classes should be individually submitted).
 *
 * @author Dustin
 */
public class ParameterDisplayer
{
   private static void displayParametersMetadata(final String[] classesNames)
   {
      for (final String className : classesNames)
      {
         try
         {
            final Class clazz = Class.forName(className);
 
            // Get all class's declared methods (does not get inherited methods)
            final Method[] declaredMethods = clazz.getDeclaredMethods();
            for (final Method method : declaredMethods)
            {
               writeHeader(
                    "Method " + method.toGenericString()
                  + " has " + method.getParameterCount() + " Parameters:");
               int parameterCount = 0;
               final Parameter[] parameters = method.getParameters();
               for (final Parameter parameter : parameters)
               {
                  out.println(
                       "\targ" + parameterCount++ + ": "
                     + (parameter.isNamePresent() ? parameter.getName() : "Parameter Name not provided,")
                     + (isParameterFinal(parameter) ? " IS " : " is NOT ")
                     + "final, type " + parameter.getType().getCanonicalName()
                     + ", and parameterized type of " + parameter.getParameterizedType()
                     + " and " + (parameter.isVarArgs() ? "IS " : "is NOT ")
                     + "variable." );
               }
            }
         }
         catch (ClassNotFoundException cnfEx)
         {
            out.println("Unable to find class " + className);
         }
      }
   }
 
   private static void writeHeader(final String headerText)
   {
      out.println("\n==========================================================");
      out.println("= " + headerText);
      out.println("==========================================================");
   }
 
   /**
    * Indicate whether provided Parameter is final.
    *
    * @param parameter Parameter to be tested for 'final' modifier.
    * @return {@code true} if provided Parameter is 'final'.
    */
   private static boolean isParameterFinal(final Parameter parameter)
   {
      return Modifier.isFinal(parameter.getModifiers());
   }
 
   public static void main(final String[] arguments)
   {
      if (arguments.length < 1)
      {
         out.println("You must provide the fully qualified name of at least one class.");
         System.exit(-1);
      }
 
      displayParametersMetadata(arguments);
   }
}

Сначала я думал о запуске этого класса в хорошо известном классе JDK, но понял, что это не слишком полезно, потому что эти классы вряд ли были созданы с опцией -parameters . Поэтому я создал простой пример класса, чтобы помочь с демонстрацией. Он называется ManyMethods и показан далее.

ManyMethods.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package dustin.examples.jdk8;
 
import java.util.List;
 
/**
 * Class with numerous methods intended to be used in demonstrating JDK 8's new
 * Parameter class.
 *
 * @author Dustin
 */
public class ManyMethods
{
   public ManyMethods() {}
 
   private void addArrayOfStrings(String[] strings) {}
 
   private void addManyStrings(final String ... strings) {}
 
   private void addListOfStrings(final List<String> strings) {}
 
   @Override
   public String toString()
   {
      return "ManyMethods";
   }
}

На следующих двух снимках экрана показано, как запустить ParameterDisplayer экземпляров ManyMethods скомпилированных без и с параметром ManyMethods . Наиболее заметные различия заключаются в том, что имена параметров не предоставляются при компиляции без параметра -parameters . Кроме того, нет достоверной информации о том, является ли параметр final при компиляции без параметра -parameters . Метод Parameter.getModifiers () не включает final при компиляции без -parameters независимо от того, является ли параметр фактически final .

parameterDisplayerOnClassWithoutParametersCompilation

parameterDisplayerOnClassWITHParametersCompilation

Класс ParameterDisplayer использует Parameter.isNamePresent () для программной идентификации того, что имя параметра отсутствует (если оно не скомпилировано с параметром -parameters ). Если бы эта проверка не была сделана, имя параметра, возвращаемое Parameter.getName (), было бы «arg» плюс номер параметра (arg0 для первого параметра, arg1 для второго параметра и т. Д.).

Два из трех методов в классе ManyMethods , у которых был параметр, имели модификатор final для этого параметра. Эти случаи были правильно определены отражением с помощью Parameter.getModifiers () только тогда, когда класс был скомпилирован с параметром -parameters .

Слегка связанная сторона Примечание: документация по инструментам Sun / Oracle всегда состояла из страницы «windows» и страницы «solaris», причем последняя обычно используется для описания того, как конкретный инструмент работает во всех разновидностях в Linux и Unix. Я отметил, что это изменилось в документации по Java 8. Эта документация все еще имеет версию «windows», но версия Unix / Linux теперь имеет «unix» в своем URL. Чтобы проиллюстрировать это, вот URL-адреса для страниц инструментов Java SE 7 и Java SE 8 javac :

Возвращаясь к новому (с Java 8) классу Parameter , стоит отметить, что наблюдается увеличение количества скомпилированных файлов .class , в которых хранятся метаданные этого дополнительного параметра. Для моего класса ManyMethods показанного выше, файл .class был увеличен с 909 байт до 961 байт.

Конструктор , как и метод , расширяет исполняемый файл , поэтому класс Constructor тот же метод getParameters, что и Method . Java 8 предоставляет более подробную информацию о параметрах метода и конструктора, когда код явно скомпилирован с этой дополнительной информацией.