写点什么

简单易用的 MVC 框架:VRaptor

  • 2014-08-04
  • 本文字数:4412 字

    阅读完需:约 14 分钟

使用 Java 进行 Web 开发时,有很多基于 MVC 的框架可供选择。 VRaptor 就是其中之一。最新的 VRaptor 第四版基于 CDI1.1 。本文将带你逐步了解这一框架的原理及新版本的新增特性。

在 VRaptor 框架中创建一个控制器,只需要在 Java 类中添加 @Controller 注解即可,框架将根据其约定的 URL 和 JSP 规范完成剩余的工作,这样可以尽量减少配置文件的使用。例如:

复制代码
@Controller
public class UserController {
public void list() { //... }
}

规范简介

VRaptor 的 URL 格式规范为 controllerName/methodName,因此可以通过 user/list 访问到 list 方法。需要注意的是,后缀 Controller 并没有包含在路径中。

JSP 调度器遵循另外一个有用的规范。与 URL 的规范类似,在控制器方法执行完毕后,VRaptor 会在 WEB-INF/jsp/controllerName/methodName.jsp 这一路径下查找 JSP 文件。

在我们的例子中就是 WEB-INF/jsp/user/list.jsp

根据这些规范,Controller 中所有的公有方法会被逐一映射用于应答 HTTP 请求,不限制请求动作的类型。

根据所选择的请求动作,也可以通过使用 @Get,@Post,@Put 或 @Delete 注解限制对控制器方法的访问。

如果不想使用默认的 URL 规范,也可以在注解中添加一个 String 类型的参数,修改控制器方法的访问路径。如果不希望限制 HTTP 请求动作的类型,可以使用 @Path 注解。

复制代码
@Get("any/other/url")
public void list() { //... }

获取参数

控制器方法可以接收参数,VRaptor 会尝试为这些参数填入值。这对于简单的值类型通常是可行的,例如下面 search 方法的 long 类型值。

复制代码
@Get
public void search(long id) { //... }

如果请求中带有名为 id 的参数,也就是与方法的参数名具有相同的名字,VRaptor 会尝试将这个参数转化为控制器方法所期望的类型。通过 URL:user/search?id=1 可以访问到这个方法,或者也可以使用参数化的 URL:

复制代码
@Get("user/search/{id}")
public void search(long id) { //... }

在这种情况下,URL 路径就是 _user/search/1__。_

不仅如此,我们还可以从视图中得到更加复杂的对象,如下所示,这个 add 方法能够得到一个 User 类型的对象:

复制代码
@Post
public void add(User user) { //... }

调用 add 方法的 form.jsp 的代码如下:

复制代码
<form action="user/add" method="post">
<input type="text" name="user.name"/>
<input type="text" name="user.email"/>
<input type="submit" value="Create user">
</form>

表单的请求参数样例如下:

复制代码
user.name = Rodrigo Turini
user.email = rodrigo.turini@caelum.com.br
...

自定义结果集

如果想在 JSP 页面中获取对象列表,我们只需让控制器方法将对象列表作为参数返回。

复制代码
@Get
public List<User> list(){
return userDao.list();
}

因为返回值是一个 List类型的对象,视图中的变量名就应该是 ${userList}。如果返回类型是一个简单的 User 对象,视图变量名就是 ${user}。另外一种将对象传送给视图的方式是使用专门的 Result 接口。我们可以将这个 Bean 注入到我们的控制器中,然后调用它的 include 方法。

复制代码
@Controller
public class UserController {
@Inject private Result result;
@Inject private UserDao userDao;
@Get
public void list(){
List<User> list = userDao.list();
result.include(list);
result.include("users", list);
}
}

上述代码将同一个 list 变量作为参数传给 include 方法两次,主要是为了说明这两种方式的用法和它们之间的区别。第一个 include 方法将会生成一个在 JSP 页面中可用的 ${userList}的变量,与方法的返回值一样。第二个 include 方法显式地提供了对象的名字,因此可以通过 ${users}访问到它。

Result 类中还有很多其他的方法可以帮助我们与视图进行交互。从下面的例子可以看到,很容易就返回用 JSON 格式序列化后的对象列表。

复制代码
@Get
public void jsonList(){
List<User> users = userDao.list();
result.use(json()).from(users).serialize();
}

Result 类为我们提供了包括 json 方法在内的多个方法,用于处理最为通用的一些结果类型,例如 xml,html,jsonp 等。Result 类中还有一个 representation 方法,可以根据请求所能接受的格式序列化对象。

简单的配置

目前为止,我们所看见的配置都非常简单。因为 VRaptor 中所有的类都是由 CDI 管理的 Bean,我们可以特化(specialize)任何一个 VRaptor 组件——而且这些定制能够被完美地封装。例如,如果想更改默认的渲染视图或 VRaptor 用于查找视图的文件夹,只需要特化 DefaultPathResolver 类即可。

复制代码
@Specializes
public class CustomPathResolver extends DefaultPathResolver {
@Override
protected String getPrefix() {
return "/root/folder/";
}
}

我们也可以通过重写 PathAnnotationRoutesParser 类来修改 URL 的默认规范。

VRaptor 与 JPA 的集成

CDI 集成框架比较有趣的另一个方面就是它能够让你很方便地管理项目中的外部类。例如,VRaptor 与 JPA 的整合就相当简单。首先创建一个 EntityManager 的生产类:

复制代码
public class EntityManagerCreator {
@Inject private EntityManagerFactory factory;
@Produces @RequestScoped
public EntityManager getEntityManager() {
return factory.createEntityManager();
}
public void destroy(@Disposes EntityManager em) {
if (em.isOpen()) {
em.close();
}
}
}

然后就可以在应用程序中的任何一个 Bean 中注入这个对象的一个实例。

复制代码
public class UserDao {
@Inject private EntityManager em;
public void add(User user) {
em.getTransaction().begin();
em.persist(user);
em.getTransaction().commit()
}
// ...
}

我们也可以创建一个简单的拦截器,将视图的渲染包含在事务中。示例如下:

复制代码
@Intercepts
public class JPATransactionInterceptor {
@Inject private EntityManager manager;
@AroundCall
public void intercept(SimpleInterceptorStack stack) {
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
stack.next();
transaction.commit();
}
}

从上面的代码中可以看到,使用 @AroundCall 注解的拦截器方法接收一个 SimpleInterceptorStack 对象作为参数。调用 next() 方法就会将请求分派给控制器,因此我们需要在调用 next() 方法之前打开事务,然后在调用之后关闭事务。

另外一种将视图的渲染包含在事务中的方法就是支持 @Transactional 注解,然后仅在包含这个注解的方法上应用拦截器。这种方法和之前的方法一样很简单,只需要在拦截器中添加一个 accepts 方法即可。

复制代码
@Accepts
public boolean accepts() {
return method.containsAnnotation(Transactional.class);
}

在 UserController 类的 add 方法上添加 @Transactional 注解:

复制代码
@Controller
public class UserController {
@Inject private Result result;
@Inject private UserDao userDao;
@Get
public void list() {
List<User> list = userDao.list();
result.include(list);
result.include("users", list);
}
@Post @Transactional
public void add(User user) {
userDao.add(user);
}
}

然后 UserDao 类的方法只需要将持久化的工作代理给 EntityManager.persist 即可:

复制代码
public void add(User user) {
em.persist(user);
}

添加插件

与许多其他的功能一样,上述功能已经以 VRaptor4 插件的形式实现并发布。上文所提及的拦截器和生产者分别位于 vraptor-jpa vraptor-hibernate 插件中,只需要将相关的 jar 包添加到项目中(或通过配置自己熟悉的依赖管理工具)就可以正常使用它们,而无需更多的配置。

只要插件中包含 beans.xml 文件,CDI 框架就可以管理插件中的类并且能够让这些类在 VRaptor 上被注入。由于创建扩展非常简单,插件开发的社区参与非常活跃并且已经创建了很多插件。在 VRaptor 的框架文档中罗列了其中一部分插件。

深入挖掘 VRaptor 在 JavaEE 中的使用

在应用服务器中集成 VRaptor 与 JPA 和事务控制更容易。可以使用注解 @PersistenceContext 替代 CDI 的 @Inject 注入 EntityManager。这样就可以使用注解 javax.transaction.Transactional 来控制方法中的事务。

复制代码
@Controller
public class UserController {
@Inject private Result result;
@Inject private UserDao userDao;
@Get
public void list(){
List<User> list = userDao.list();
result.include("users", list);
}
@Post @Transactional
public void add(User user){
userDao.add(user);
}
@Post @Transactional
public void remove(User user){
userDao.delete(user);
}
}

或者也可以像下面这段代码一样将 @Transactional 注解直接添加到 VRaptor 控制器上,而不是对方法做注解:

复制代码
@Controller @Transactional
public class UserController {
@Inject private Result result;
@Inject private UserDao userDao;
// remaining code
}

之所以可以这样,是因为 VRaptor 控制器和其他的类一样,都是由 CDI 管理的 Bean。

VRaptor 示例

通过基于 VRaptor 框架的开源项目,可以在实践中了解更多关于 VRaptor 框架的功能。 Mamute 问答引擎就是一个很好地利用 Vraptor4 新特性的例子,具体内容可以参见项目代码库。也可以通过VRaptor 框架的示例应用 vraptor-music-jungle 和已经配置好的基础项目 blank-project 快速展开学习。

关于 VRaptor4 的更多信息

VRaptor 官方文档中可以了解到更多关于 VRaptor4 框架的更多信息:从一分钟指南十分钟指南开始,然后学习从老版本向新版本迁移的教程,再之后可以浏览由社区编写的详细说明文档。如果想更加深入地学习,可以阅读与VRaptor 内核无缝集成的 CDI1.1 的规格说明书

关于作者

Rodrigo Turini毕业于计算机科学专业目前在巴西的 Caelum 公司工作,担任开发者和讲师的角色。他是 Vraptor4 的领导者之一并在许多其他开源项目中有所贡献。

查看英文原文: VRaptor MVC Framework; Powerful Simplicity


感谢崔康对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014-08-04 10:555512
用户头像

发布了 75 篇内容, 共 65.9 次阅读, 收获喜欢 6 次。

关注

评论

发布
暂无评论
发现更多内容

FinClip 与 uniapp:轻应用平台与前端开发框架

王字 Wannz

小程序 uniapp 移动开发 finclip

有奖调查| 2022 Apache Pulsar 怎么过,你们说了算

Apache Pulsar

开源 云原生 中间件 Apache Pulsar Apache Pulsar 社区

我与音视频的故事 | 社区征文

Changing Lin

音视频

低代码OR零代码,企业如何选择自身所需的软件开发平台?

BeeWorks

恒源云(GPUSHARE)_替代MLM的预训练任务,真的超简单吗?

恒源云

人工智能 自然语言处理 深度学习

圆桌会议:如何避免踩到移动研发中,效能提升那些坑

王字 Wannz

移动开发 迭代

2022年低代码的变化与趋势

BeeWorks

OCR技术用于在线身份认证的运营效果分析

OCR

大模型应用新范式:统一特征表示优化(UFO)

百度开发者中心

“热搜”中的分布式数据库

OceanBase 数据库

oceanbase 客户案例 四川农信

各项结果排名第一!百度内容技术架构团队在国际向量检索大赛BigANN中斩获佳绩

百度Geek说

百度 内容 前端 后端

在线YAML转Properties工具

入门小站

工具

新版上线|能力中心打造全新视角

钉钉开发者

钉钉能力中心 钉钉开放平台能力中心 钉钉开放平台 能力中心

Java如何实现消费数据隔离?

CRMEB

阿里云EMAS 1月产品动态

移动研发平台EMAS

阿里云 程序人生 移动开发 #EMAS

MySQL 是如何实现RC事务隔离级别的

华为云开发者联盟

MySQL ReadView 事务隔离 RC事务隔离 Read Committed

延迟任务场景,该如何提高吞吐量和时效性

华为云开发者联盟

redis 延迟任务 低延迟 Redis 消费队列

利用鸿蒙JavaUI 框架的 WebView 加载本地冰墩墩网页

宇宙之一粟

鸿蒙开发 2月月更

【连接平台」企业告警信息通过机器人同步至钉钉群

钉钉开发者

连接器 钉钉应用开发 钉群

小程序框架与平台编译对比

王字 Wannz

小程序 百度智能小程序 头条小程序 finclip 小程序框架

IT人的笔记本——全面了解 Jupyter

dongge

jupyterlab

2021盘点 | 云主机年度榜单出炉,Top5花落谁家?

博睿数据

开源商业模式促进金融业科技生态的发展

王字 Wannz

小程序 开源 IT 金融

【网络安全】一款针对Flutter的逆向工程分析工具

H

网络安全 逆向分析

Linux之at命令

入门小站

Linux

手把手教你使用HarmonyOS本地模拟器

HarmonyOS开发者

HarmonyOS DevEco Studio

分布式进阶(二十三):Nginx 服务器应用详解

No Silver Bullet

nginx https 正向代理与反向代理 SSL证书 2月月更

FinClip 与 mPaaS:轻应用平台与移动应用开发平台

王字 Wannz

小程序 移动开发 mPaaS finclip 小程序容器

远程办公团队如何沟通?

王字 Wannz

远程办公 wrh 居家办公 线下办公 soho

架构实战营:模块七作业

Geek_93ffb0

「架构实战营」

FinClip 的 2021 与 2022

王字 Wannz

finclip 小程序容器 小程序开发 小程序管理平台

简单易用的MVC框架:VRaptor_Java_Rodrigo Turini_InfoQ精选文章