Skip to content

Latest commit

 

History

History
97 lines (71 loc) · 6.5 KB

reflection.md

File metadata and controls

97 lines (71 loc) · 6.5 KB

Reflection

Тема рефлексии может показаться сложной, но общее представление о ней иметь нужно, т.к. на этом строится большая часть фреймворков (включая JUnit).

Рефлексия - API, позволяющих анализировать приложение в момент исполнения.

Рефлексия начинается с того, что у нас есть класс, который называется Class.

Объекты этого класса описывают классы, загруженные в память JVM.

Например, у вас есть класс Product. Когда JVM загружает этот класс из байт-кода, то она создаёт объект класса Class, который и описывает класс Product.

Что значит описывает? Это значит смотрит на него примерно как мы с вами:

  1. Название
  2. Набор полей
  3. Набор методов
  4. и т.д.

Каждый объект знает, к какому классу он принадлежит. Когда IDEA генерирует equals вот эта строка и проверяет, что объект относятся к одному и тому же классу: if (o == null || getClass() != o.getClass()) return false;.

Зачем это нужно? Рефлексия позволяет нам буквально в цикле перебирать поля и методы, выполняя необходимые действия:

  • JUnit смотрит, написано ли над методом @Test
  • Mockito смотрит, написано ли над полем @Mock, @InjectMocks
  • и т.д.

И самое главное - благодаря рефлексии JUnit, Mockito и другие инструменты могут создавать объекты прямо во время исполнения программы.

Пародия на JUnit

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

Итак задача: мы хотим написать некоторую пародию на JUnit, которая:

  1. Берёт класс
  2. Анализирует все его методы
  3. Находит все, над которыми стоит аннотация @Test
  4. Создаёт для каждого такого метода новый объект и запускает на нём этот метод (тот, над которым стояло @Test)

Естественно, мы для простоты изложения опустим кучу нюансов и продемонстрируем основную идею. Если вам будут интересны детали, пишите в Slack-чат вопросы, мы обязательно ответим.

Итак, поехали (наш подопытный)*:

package ru.netology;

import org.junit.jupiter.api.Test;

public class DemoTest {
  @Test
  public void shouldBeCalled() {
    System.out.println("Test method called");
  }

  public void shouldNotBeCalled() {
    System.out.println("Invalid method called");
  }
}

Примечание*: целиком код проекта вы можете найти в репозитории с кодом к лекциям в проекте reflection-sample.

Создаём "запускалку" наших тестов:

package ru.netology;

import org.junit.jupiter.api.Test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Launcher {
  // что такое исключения мы разберём на следующей лекции
  public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
    // создаём объект типа `Class` (generic'и мы пока не знаем, поэтому и так "сойдёт")
    // clazz или cls - общепринятое имя, т.к. class - зарезервировано
    Class clazz = DemoTest.class;
    // берём все методы класса
    Method[] methods = clazz.getDeclaredMethods();
    // перебираем методы
    for (Method method : methods) {
      // смотрим, есть ли над методом аннотация @Test
      if (method.isAnnotationPresent(Test.class)) {
        // создаём объект класса (newInstance помечен аннотацией @Deprecated, но для простоты мы будем использовать его, в противном случае, нужно выбирать конструкторы)
        Object object = clazz.newInstance();
        // вызываем метод на объекте
        method.invoke(object);
      }
    }
  }
}

Как вы видите, код "не особо приятный", и, в большинстве случаев, вы такой код писать не будете, если не станете сами разрабатывать библиотеки и инструменты (т.к. инструменты тестирования уже это делают за вас).

Но свою работу он выполняет, вы можете запустить, подебажить, посмотреть, как он работает.

Ключевое: вы должны понимать, что Java предоставляет нам инструменты манипулирования кодом, которые и позволяют создавать такие мощные вещи как JUnit и Mockito*.

Примечание*: на самом деле мы немного лукавим, т.к. Mockito использует ещё и библиотеку ByteBuddy, которая генерирует байт-код на лету (но это уже совсем другая история).