当您在控制器操作中渲染视图时,您只需调用模板引擎生成的普通函数:
public Application extends Controller {
public static Result index() {
return ok(views.html.index.render(42));
}
}
Here, render
是对象的方法index
其中有类型Template1<Integer, Html>
.
现在的问题是:如何编写一个能够调用特定于另一个控制器的视图的通用控制器?或者简单地:如何抽象视图?
我看到两个解决方案:控制反转 http://en.wikipedia.org/wiki/Inversion_of_control and 反射.
让我们看看如何在一个简单的用例上实现这两者。假设你有以下通用的Shower<T>
能够计算包含任意类型值的 HTML 表示形式的 HTTP 响应的类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);
}
优点和缺点
基于反射的解决方案需要更少的样板文件在调用站点上,但事实上它依赖于命名约定,这使得它更脆弱。此外,这个解决方案只会失败在运行时当它失败时,第一个解决方案将向您显示丢失的模板在编译时。最后但并非最不重要的一点是,基于反射的解决方案可能会添加一些性能开销由于反射。