Hidden class in java

Скрытые классы в Java 15

Java 15 представила множество функций . В этой статье мы обсудим одну из новых функций под названием «Скрытые классы» в JEP-371 . Эта функция представлена как альтернатива Unsafe API , который не рекомендуется использовать за пределами JDK.

Функция скрытых классов полезна для всех, кто работает с динамическим байт-кодом или языками JVM.

2. Что такое скрытый класс?​

Динамически генерируемые классы обеспечивают эффективность и гибкость для приложений с малой задержкой. Они нужны только в течение ограниченного времени. Сохранение их на время жизни статически сгенерированных классов увеличивает объем используемой памяти. Существующие решения для этой ситуации, такие как загрузчики для каждого класса, громоздки и неэффективны.

Начиная с Java 15 скрытые классы стали стандартным способом создания динамических классов.

Скрытые классы — это классы, которые не могут использоваться непосредственно байт-кодом или другими классами. Несмотря на то, что он упоминается как класс, его следует понимать как скрытый класс или интерфейс. Он также может быть определен как элемент гнезда управления доступом и может быть выгружен независимо от других классов.

3. Свойства скрытых классов​

Давайте посмотрим на свойства этих динамически генерируемых классов:

  • Необнаруживаемый — скрытый класс не может быть обнаружен ни JVM во время компоновки байт-кода, ни программами, явно использующими загрузчики классов. Рефлексивные методы Class::forName , ClassLoader::findLoadedClass и Lookup::findClass их не найдут.
  • Мы не можем использовать скрытый класс в качестве суперкласса, типа поля, типа возвращаемого значения или типа параметра.
  • Код в скрытом классе может использовать его напрямую, не полагаясь на объект класса.
  • Поля final , объявленные в скрытых классах, не могут быть изменены независимо от их доступных флагов.
  • Он расширяет гнездо управления доступом за счет необнаруживаемых классов.
  • Он может быть выгружен, даже если его условный определяющий загрузчик классов все еще доступен.
  • Трассировки стека по умолчанию не отображают методы или имена скрытых классов, однако их можно отобразить, настроив параметры JVM.
Читайте также:  От чего произошло название языка python

4. Создание скрытых классов​

Скрытый класс не создается никаким загрузчиком классов. Он имеет тот же определяющий загрузчик класса, пакет среды выполнения и домен защиты, что и класс поиска.

Во-первых, давайте создадим объект Lookup :

 MethodHandles.Lookup lookup = MethodHandles.lookup(); 

Метод Lookup::defineHiddenClass создает скрытый класс. Этот метод принимает массив байтов.

Для простоты мы определим простой класс с именем HiddenClass , у которого есть метод для преобразования заданной строки в верхний регистр:

 public class HiddenClass    public String convertToUpperCase(String s)    return s.toUpperCase();   >   > 

Давайте получим путь к классу и загрузим его во входной поток. После этого мы конвертируем этот класс в байты, используя IOUtils.toByteArray() :

 Class?> clazz = HiddenClass.class;   String className = clazz.getName();   String classAsPath = className.replace('.', '/') + ".class";   InputStream stream = clazz.getClassLoader()   .getResourceAsStream(classAsPath);   byte[] bytes = IOUtils.toByteArray(); 

Наконец, мы передаем эти сконструированные байты в Lookup::defineHiddenClass :

 Class?> hiddenClass = lookup.defineHiddenClass(IOUtils.toByteArray(stream),   true, ClassOption.NESTMATE).lookupClass(); 

Второй логический аргумент true инициализирует класс. Третий аргумент ClassOption.NESTMATE указывает, что созданный скрытый класс будет добавлен в качестве соседа к классу поиска, чтобы он имел доступ к закрытым членам всех классов и интерфейсов в том же гнезде.

Предположим, мы хотим жестко связать скрытый класс с его загрузчиком классов ClassOption.STRONG . Это означает, что скрытый класс может быть выгружен только в том случае, если определяющий его загрузчик недоступен.

5. Использование скрытых классов​

Скрытые классы используются фреймворками, которые генерируют классы во время выполнения и используют их косвенно через отражение.

В предыдущем разделе мы рассмотрели создание скрытого класса. В этом разделе мы увидим, как его использовать и создать экземпляр.

Поскольку приведение классов, полученных из Lookup.defineHiddenClass , невозможно ни с каким другим объектом класса, мы используем Object для хранения экземпляра скрытого класса. Если мы хотим привести скрытый класс, мы можем определить интерфейс и создать скрытый класс, реализующий интерфейс:

 Object hiddenClassObject = hiddenClass.getConstructor().newInstance(); 

Теперь давайте получим метод из скрытого класса. Получив метод, мы будем вызывать его как любой другой стандартный метод:

 Method method = hiddenClassObject.getClass()   .getDeclaredMethod("convertToUpperCase", String.class); 
 Assertions.assertEquals("HELLO", method.invoke(hiddenClassObject, "Hello")); 

Теперь мы можем проверить несколько свойств скрытого класса, вызвав некоторые из его методов:

Метод isHidden() вернет true для этого класса:

 Assertions.assertEquals(true, hiddenClass.isHidden()); 

Кроме того, поскольку для скрытого класса нет фактического имени, его каноническое имя будет нулевым :

 Assertions.assertEquals(null, hiddenClass.getCanonicalName()); 

Скрытый класс будет иметь тот же определяющий загрузчик, что и класс, выполняющий поиск. Поскольку поиск происходит в том же классе, следующее утверждение будет успешным:

 Assertions.assertEquals(this.getClass()   .getClassLoader(), hiddenClass.getClassLoader()); 

Если мы попытаемся получить доступ к скрытому классу с помощью любых методов, они вызовут исключение ClassNotFoundException . Это очевидно, так как имя скрытого класса достаточно необычное и неквалифицированное, чтобы быть видимым для других классов. Давайте проверим пару утверждений, чтобы доказать, что скрытый класс невозможно обнаружить:

 Assertions.assertThrows(ClassNotFoundException.class, () -> Class.forName(hiddenClass.getName())); 
 Assertions.assertThrows(ClassNotFoundException.class, () -> lookup.findClass(hiddenClass.getName())); 

Обратите внимание, что единственным способом, которым другие классы могут использовать скрытый класс, является его объект класса .

6. Анонимный класс против скрытого класса​

В предыдущих разделах мы создали скрытый класс и поэкспериментировали с некоторыми его свойствами. Теперь давайте остановимся на различиях между анонимными классами — внутренними классами без явных имен — и скрытыми классами:

  • Анонимный класс имеет динамически сгенерированное имя с символом $ между ними, в то время как скрытый класс, производный от com.foreach.reflection.hiddenclass.HiddenClass , будет com.foreach.reflection.hiddenclass.HiddenClass/1234.
  • Анонимный класс создается с использованием Unsafe::defineAnonymousClass , который устарел, тогда как Lookup::defineHiddenClass создает экземпляр скрытого класса .
  • Скрытые классы не поддерживают исправление константного пула. Это помогает определить анонимные классы с их постоянными записями пула, уже преобразованными в конкретные значения.
  • В отличие от скрытого класса, анонимный класс может получить доступ к защищенным членам основного класса, даже если он находится в другом пакете, а не в подклассе.
  • Анонимный класс может заключать в себе другие классы для доступа к своим членам, но скрытый класс не может заключать в себе другие классы.

Хотя скрытый класс не является заменой анонимного класса , он заменяет некоторые способы использования анонимных классов в JDK. Начиная с Java 15, лямбда-выражения используют скрытые классы .

7. Заключение​

В этой статье мы подробно обсудили новую языковую функцию под названием «Скрытые классы». Как всегда, код доступен на GitHub .

Источник

What is the Hidden Classes in java 15?

What exactly is the Hidden Classes feature in Java 15 ? As i read in Java 15 documentation , Spring & Hibernate frameworks used Hidden Classes ? I need real example to Spring and/or Hibernate used Hidden Classes .

I’ve downloaded Spring framework source code from GitHub, searched for «defineHiddenClass» and got no hits.

The purpose of this site is not giving tutorials, especially not for solving non-existing problems. Besides that, when you use lambda expressions or method references, you are already using hidden classes.

Hidden classes are not a Java language feature; you don’t write one in Java code. They are an implementation technique for low-level libraries that dynamically generate classes, such as LambdaMetafactory , which is used in the translation of lambda expressions by the Java compiler.

@BrianGoetz there’s one thing I don’t get. What is the purpose of the STRONG option? Why would you ever want to prevent the garbage collection of an entirely unreachable class that is impossible to ever get re-used? As far as I know, the hidden classes created via Unsafe did not have that option and lambda expressions worked well without it.

1 Answer 1

To answer this question, it’s important to first distinguish the Java language from the Java runtime. The former is the «Java code» a programmer writes, while the latter is the «Java program» people use to run code written in that language (among other languages, like Kotlin and Clojure).

In the Java language, there are many ways to define a class. Most classes are like ArrayList, which are top-level and have a programmer-defined name. But other ways of defining a class may not have a simple name. An anonymous inner class, for example, does not provide a way for a programmer to give it a name. The same goes for lambda expressions introduced in Java 8.

In the past, the Java runtime has had the limitation that all classes must have a name and must be publicly addressable by the runtime. This has meant that the Java compiler gives an anonymous inner class a name unlikely to conflict with any other class’s name, usually with dollar signs, which are legal class names to the Java runtime, but illegal class names in the Java language. This is an implementation detail. But because classes all have names and are all addressable through the class loader, abstraction is «leaky»; there are ways these classes which may be meant to be hidden can be addressed through access to the class loader.

«Hidden classes» are a new (to Java 15) feature of the Java runtime, a way for programs to define classes that cannot be addressed by other classes running on the class loader. They still have names, but access is scoped in a way that their existence cannot be «leaked» to other parts of the program. It is not a new language feature, but a tool that a compiler (or runtime framework) may use to implement certain pre-existing language features.

To the typical Java programmer, this process is transparent. Unless your program compiles code or manipulates bytecode, you do not need to worry about this implementation detail. But for those who do, this is a valuable tool in their software toolbox.

Источник

Оцените статью