最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

简单易用的 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:554967
用户头像

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

关注

评论

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

《Spring实战》读书笔记-第4章 面向切面的Spring

Java 程序员 后端

【Java知识点详解 7】装箱和拆箱

Java 程序员 后端

【Java面经】阿里三面被挂!幸获内推,历经5轮终于拿到口碑offer(1)

Java 程序员 后端

《JVM系列》 第六章 -- 对象的实例化与内存布局

Java 程序员 后端

《吃透MQ系列》核心基础全在这里了,一文啃透!

Java 程序员 后端

《菜菜的机器学习sklearn课堂》降维算法PCA和SVD

Java 程序员 后端

【Java设计模式实战系列】好的单例模式是怎样的?

Java 程序员 后端

墨天轮国产数据库沙龙 | 张晓庆:GoldenDB分布式数据库的自动安装与备份恢复

墨天轮

数据库 技术沙龙

《Spring实战》读书笔记-第4章 面向切面的Spring(1)

Java 程序员 后端

【Java 多线程 1】CountDownLatch

Java 程序员 后端

【Java核心面试宝典】Day3、图解HashMap高频面试及底层实现架构!

Java 程序员 后端

【Java设计模式系列】装饰器模式(Decorator Pattern)

Java 程序员 后端

【Java面试题】常见Java面试知识点总结

Java 程序员 后端

【C 语言小游戏】手打贪吃蛇1

Java 程序员 后端

《代码重构》之方法到底多长算“长”

Java 程序员 后端

「并发原理专题」AQS的技术体系之CLH、MCS锁的原理及实现

Java 程序员 后端

【Java 基础语法】万字解析 Java 的多态、抽象类和接口

Java 程序员 后端

【Java 集合框架】Stack、Queue 和 Deque 的使用

Java 程序员 后端

【Java从0到架构师】Maven

Java 程序员 后端

【Java每日面试题】大厂是如何设计秒杀系统的?

Java 程序员 后端

大数据中必须要掌握的 Flink SQL 详细剖析

五分钟学大数据

flink 11月日更

[译] 微服务的设计模式

Java 程序员 后端

《重学Java高并发》Disruptor使用实战

Java 程序员 后端

公有云是什么意思?其存在的意义是什么?

行云管家

云计算 公有云 私有云 混合云

【Java面试题总结 4】Java Web、网络、设计模式综合篇

Java 程序员 后端

“抽象类”到底抽不抽象?实例对比一看便知!

Java 程序员 后端

Zookeeper(从7个方面来了解Zookeeper基础概念)

Java 程序员 后端

【Java从0到架构师】SQL 多表查询

Java 程序员 后端

过等保选择云堡垒机还是硬件堡垒机比较好?

行云管家

网络安全 云服务 堡垒机 等级保护

【Java面经】阿里三面被挂!幸获内推,历经5轮终于拿到口碑offer

Java 程序员 后端

《JVM系列》 第六章 -- 对象的实例化与内存布局(1)

Java 程序员 后端

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