使用 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
@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 )关注我们,并与我们的编辑和其他读者朋友交流。
评论