简单易用的Java MVC框架VRaptor使用教程
使用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<User>类型的对象,视图中的变量名就应该是${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的规格说明书。
来自:http://www.infoq.com/cn/articles/VRaptor-MVC-Framework-Version-4