BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

简单易用的MVC框架:VRaptor

| 作者 Rodrigo Turini 关注 0 他的粉丝 ,译者 丛一 关注 2 他的粉丝 发布于 2014年8月5日. 估计阅读时间: 20 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

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

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

赞一下 by 贾 珣

最近看到很多Java的新MVC框架,可能是孤陋寡闻,但这些框架的改进相当有想法。

和nutz很像啊。 by fei li

这个处理方式和国人开发的nutz框架的很像啊

性能稳定性怎样? by lazey liu

单纯的rest 相比resteasy jersey restlet 性能和稳定性怎样?

也许是个不错的选择 by 李 鹏

值得一看

也许是个不错的选择 by 李 鹏

值得一看

希望不错 by 李 鹏

没仔细看呢,名字取得相当霸气啊!F-22 Raptor吗?嘎嘎

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

6 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT