При использовании универсального контроллера, как вернуть представление, присущее определенному контроллеру?

В результате этого ответа: qaru.site/info/7540/..., мне интересно, как вернуть представление, присущее определенному контроллеру при использовании общего контроллера.

Ответ 1

Когда вы визуализируете представление в действии контроллера, вы просто вызываете простую функцию, которая была сгенерирована механизмом шаблона:

public Application extends Controller {
  public static Result index() {
    return ok(views.html.index.render(42));
  }
}

Здесь render - это метод объекта index, который имеет тип Template1<Integer, Html>.

Теперь возникает вопрос: как написать общий контроллер, способный вызвать вид, специфичный для другого контроллера? Или просто: как абстрагироваться от представлений?

Я вижу два решения: инверсия управления и отражение.

Давайте посмотрим, как реализовать оба варианта в простом случае. Предположим, что у вас есть следующий общий Shower<T> класс, способный вычислять HTTP-ответ, содержащий HTML-представление любого значения типа T:

public class Shower<T> {
  public Result show(T value) {
    // TODO return an HTML representation of `value`
  }
}

Инверсия управления

Чтобы реализовать Shower<T> с помощью инверсии элемента управления, нам просто нужно ввести значение Template1<T, Html>, используемое для выполнения рендеринга:

public class Shower<T> {

  public final Template1<T, Html> template;

  public Shower(Template1<T, Html> template) {
    this.template = template;
  }

  public Result show(T value) {
    return ok(template.render(value));
  }

}

Чтобы использовать его в контроллере, создайте статический экземпляр Shower<T> и введите ему шаблон:

public class Application extends Controller {
  public static Shower<Foo> foo = new Shower<Foo>(views.html.Foo.show.ref());
}

Отражение

Вы можете обнаружить, что это слишком шаблонный шаблон, чтобы явно вводить шаблон для использования для каждого экземпляра Shower<T>, поэтому у вас может возникнуть соблазн получить его путем отражения на основе соглашения об именах, например. чтобы показать значение типа Foo, просто найдите объект с именем show в пакете views.html.Foo:

public class Shower<T> {

  private final Class<T> clazz;

  public Shower(Class<T> clazz) {
    this.clazz = clazz;
  }

  public Result show(T value) throws Exception {
    Class<?> object = Play.application().classLoader().loadClass("views.html." + clazz.getSimpleName() + ".show$");
    Template1<T, Html> template = (Template1<T, Html>)object.getField("MODULE$").get(null);
    return ok(template.render(value));
  }
}

(это способ доступа к объектам Scala с использованием отражения)

Вы можете использовать его в контроллере следующим образом:

public class Application extends Controller {
  public static Shower<Foo> foo = new Shower<Foo>(Foo.class);
}

Плюсы и минусы

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