1. 概述

Jersey 是一个开源框架,用于开发 RESTful Web 服务

它不仅是 JAX-RS 规范的官方参考实现,还提供了一系列扩展功能,简化 Web 应用开发。

本文将通过一个小型示例,演示如何使用 Jersey 提供的 Model-View-Controller(MVC)扩展

如果你更关注纯 API 开发,可以参考我们另一篇 Jersey + Spring 构建 REST API 的教程

✅ 提示:本文重点在 MVC 渲染,不是写 REST 接口,别跑偏。


2. Jersey 中的 MVC

Jersey 提供了对 MVC 设计模式的支持扩展,允许你在服务端渲染页面,而不仅仅是返回 JSON。

在 Jersey 的上下文中,MVC 各组件对应如下:

  • Controller(控制器):即带有 @Path 注解的资源类或方法
  • View(视图):绑定到资源的模板文件(如 .ftl
  • Model(模型):资源方法返回的 Java 对象,用于填充模板

要启用 MVC 功能,必须注册对应的 MVC 模块。

本文选用主流模板引擎 Freemarker。Jersey 原生支持多种渲染引擎,包括:

  • Freemarker
  • Mustache
  • JSP(Java Server Pages)

⚠️ 踩坑提醒:JSP 在嵌入式容器中支持较差,生产环境建议用 Freemarker 或 Mustache。

更多 MVC 原理可参考:Servlet + JSP 的 MVC 实现


3. 项目搭建

本节将配置 Maven 依赖,并使用嵌入式 Grizzly 服务器快速启动应用。

3.1. Maven 依赖

首先引入 Jersey MVC 的 Freemarker 扩展:

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-mvc-freemarker</artifactId>
    <version>3.1.1</version>
</dependency>

接着添加嵌入式容器 Grizzly 的支持:

<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-grizzly2-servlet</artifactId>
    <version>3.1.1</version>
</dependency>

✅ 版本一致很重要,避免兼容问题。


3.2. 服务器配置

要启用 MVC 模板功能,需注册对应的 JAX-RS 特性。

我们创建一个自定义配置类:

public class ViewApplicationConfig extends ResourceConfig {    
    public ViewApplicationConfig() {
        packages("com.baeldung.jersey.server");
        property(FreemarkerMvcFeature.TEMPLATE_BASE_PATH, "templates/freemarker");
        register(FreemarkerMvcFeature.class);
    }
}

关键配置说明:

配置项 作用
packages() 扫描指定包下所有 @Path 注解的资源类
TEMPLATE_BASE_PATH 模板文件根路径,对应 src/main/resources/templates/freemarker
register(FreemarkerMvcFeature.class) 启用 Freemarker 渲染支持

✅ 约定优于配置:模板默认放在 resources 下,别放错目录。


3.3. 启动应用

使用 exec-maven-plugin 快速启动嵌入式服务器:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <configuration>                
        <mainClass>com.baeldung.jersey.server.http.EmbeddedHttpServer</mainClass>
    </configuration>
</plugin>

编译并运行:

mvn clean compile exec:java

输出日志:

Jul 28, 2018 6:21:08 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Application started.
Try out http://localhost:8082/fruit
Stop the application using CTRL+C

访问 http://localhost:8082/fruit,看到 “Welcome Fruit Index Page!” 即表示成功。

✅ 端口是 8082 不是 8080,别惯性思维。


4. MVC 模板使用方式

Jersey 的 MVC 核心是两个类:**Viewable** 和 @Template 注解

下面介绍三种绑定模板的姿势。

4.1. 使用 Viewable

最直接的方式:在资源方法中返回 Viewable 实例。

@Path("/fruit")
public class FruitResource {
    @GET
    public Viewable get() {
        return new Viewable("/index.ftl", "Fruit Index Page");
    }
}

解析:

  • FruitResource 是 Controller
  • "Fruit Index Page" 是 Model(字符串)
  • /index.ftl 是 View 模板路径

简单粗暴,适合快速原型。


4.2. 使用 @Template 注解

不想每次都 new Viewable?用 @Template 更清爽。

@GET
@Template(name = "/all.ftl")
@Path("/all")
@Produces(MediaType.TEXT_HTML)
public Map<String, Object> getAllFruit() {
    List<Fruit> fruits = new ArrayList<>();
    fruits.add(new Fruit("banana", "yellow"));
    fruits.add(new Fruit("apple", "red"));
    fruits.add(new Fruit("kiwi", "green"));

    Map<String, Object> model = new HashMap<>();
    model.put("items", fruits);
    return model;
}

优势:

  • ✅ 方法返回值直接作为 Model
  • ✅ 模板路径通过注解声明,代码更清晰
  • ✅ 返回 Map<String, Object> 是常见做法,便于模板访问

对应的 all.ftl 模板会接收到 items 变量并渲染列表。


4.3. 错误处理:@ErrorTemplate

出错了怎么返回友好页面?用 @ErrorTemplate

@GET
@ErrorTemplate(name = "/error.ftl")
@Template(name = "/named.ftl")
@Path("{name}")
@Produces(MediaType.TEXT_HTML)
public String getFruitByName(@PathParam("name") String name) {
    if (!"banana".equalsIgnoreCase(name)) {
        throw new IllegalArgumentException("Fruit not found: " + name);
    }
    return name;
}

行为逻辑:

  • ✅ 正常情况:使用 /named.ftl 渲染
  • ❌ 异常情况:自动跳转到 /error.ftl,Model 是异常对象本身

error.ftl 示例:

<body>
    <h1>Error - ${model.message}!</h1>
</body>

${model.message} 直接调用异常的 getMessage() 方法。

✅ 单元测试验证错误流:

@Test
public void givenGetFruitByName_whenFruitUnknown_thenErrorTemplateInvoked() {
    String response = target("/fruit/orange").request()
      .get(String.class);
    assertThat(response, containsString("Error -  Fruit not found: orange!"));
}

测试访问 /fruit/orange,确认返回错误页面且包含异常信息。


5. 总结

本文带你实战了 Jersey 的 MVC 扩展功能:

  • ✅ 如何集成 Freemarker 模板引擎
  • ✅ 三种模板绑定方式:Viewable@Template@ErrorTemplate
  • ✅ 错误页面统一处理,提升用户体验

虽然现在前后端分离是主流,但在某些内部系统、管理后台或需要服务端渲染的场景下,Jersey MVC 依然能帮你快速出活。

💡 项目源码已托管:GitHub - eugenp/tutorials


原始标题:Jersey MVC Support