读书人

施用Spring Roo 感受ROR式的开发

发布时间: 2012-11-10 10:48:50 作者: rapoo

使用Spring Roo ,感受ROR式的开发
Roo是一种 Spring 开发的辅助工具,使用命令行操作来生成自动化项目,操作非常类似于rails

我这里使用spring tool suit来开发一个demo项目

首先新建个spring template project,然后选择template类型为Roo Wep App (based on Roo 1.0.0.RC1)
这样,便会通过roo的命令自动生成一个标准的maven web项目,里面含有最基本的Spring 配置

建好项目后,我们可以在project上运行roo shell
通过hint命令,我们可以很快的了解各种操作提示

首先,设置数据库

install jpa -provider HIBERNATE -database MYSQL

roo会帮你在配置文件applicationContext.xml中配置好了相应的数据源配置,并且在 pom.xml中已经添加jpa和数据库驱动等相关的依赖。

然后新建一个domain
new persistent class jpa -name ~.domain.Employee -testAutomatically


这时roo会帮我们生成Employee类,这时Employee类还没有任何字段,我们可以通过roo shell往里面加入一些字段

add field string -fieldName name -notNull -sizeMax 200add field date jpa -fieldName birth -type java.util.Date......


最终生成的Employee如下:
@Entity@RooEntity@RooJavaBean@RooToStringpublic class Employee {    @NotNull    @Size(max = 200)    private String name;    @Temporal(TemporalType.TIMESTAMP)    private Date birth;}


你会发现,怎么没有get set呢?我们反编译看下编译后的class代码
// Decompiled by DJ v3.5.5.77 Copyright 2003 Atanas Neshkov  Date: 2009-8-8 21:37:43// Home Page : http://members.fortunecity.com/neshkov/dj.html  - Check often for new version!// Decompiler options: packimports(3) // Source File Name:   Employee.javapackage com.javaeye.domain;import java.util.Date;import java.util.List;import javax.persistence.EntityManager;import org.aspectj.runtime.reflect.Factory;import org.springframework.beans.factory.annotation.Configurable;import org.springframework.beans.factory.aspectj.*;// Referenced classes of package com.javaeye.domain://            Employee_Roo_ToString, Employee_Roo_Entity, Employee_Roo_JavaBeanpublic class Employee    implements ConfigurableObject{    public Employee()    {        org.aspectj.lang.JoinPoint joinpoint1 = Factory.makeJP(ajc$tjp_1, this, this);        org.aspectj.lang.JoinPoint joinpoint = Factory.makeJP(ajc$tjp_0, this, this);        if(this != null && getClass().isAnnotationPresent(org/springframework/beans/factory/annotation/Configurable) && AnnotationBeanConfigurerAspect.ajc$if_1((Configurable)getClass().getAnnotation(org/springframework/beans/factory/annotation/Configurable)))            AnnotationBeanConfigurerAspect.aspectOf().ajc$before$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$1$e854fa65(this);        if(this != null && getClass().isAnnotationPresent(org/springframework/beans/factory/annotation/Configurable) && (this == null || !getClass().isAnnotationPresent(org/springframework/beans/factory/annotation/Configurable) || !AnnotationBeanConfigurerAspect.ajc$if_1((Configurable)getClass().getAnnotation(org/springframework/beans/factory/annotation/Configurable))) && AbstractDependencyInjectionAspect.ajc$if_0(joinpoint))            AnnotationBeanConfigurerAspect.aspectOf().ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$2$1ea6722c(this);        Employee_Roo_Entity.ajc$interFieldInit$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$entityManager(this);        Employee_Roo_Entity.ajc$interFieldInit$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$id(this);        Employee_Roo_Entity.ajc$interFieldInit$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$version(this);        if(!AnnotationBeanConfigurerAspect.ajc$if_1((Configurable)getClass().getAnnotation(org/springframework/beans/factory/annotation/Configurable)) && AbstractDependencyInjectionAspect.ajc$if_0(joinpoint1))            AnnotationBeanConfigurerAspect.aspectOf().ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$2$1ea6722c(this);    }    public static String ajc$privFieldGet$com_javaeye_domain_Employee_Roo_JavaBean$com_javaeye_domain_Employee$name(Employee employee)    {        return employee.name;    }    public static void ajc$privFieldSet$com_javaeye_domain_Employee_Roo_JavaBean$com_javaeye_domain_Employee$name(Employee employee, String s)    {        employee.name = s;    }    public static Date ajc$privFieldGet$com_javaeye_domain_Employee_Roo_JavaBean$com_javaeye_domain_Employee$birth(Employee employee)    {        return employee.birth;    }    public static void ajc$privFieldSet$com_javaeye_domain_Employee_Roo_JavaBean$com_javaeye_domain_Employee$birth(Employee employee, Date date)    {        employee.birth = date;    }    public static long countEmployees()    {        return Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$countEmployees();    }    public static EntityManager entityManager()    {        return Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$entityManager();    }    public static List findAllEmployees()    {        return Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$findAllEmployees();    }    public static Employee findEmployee(Long long1)    {        return Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$findEmployee(long1);    }    public static List findEmployeeEntries(int i, int j)    {        return Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$findEmployeeEntries(i, j);    }    public void flush()    {Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$flush(this);    }    public Date getBirth()    {        return Employee_Roo_JavaBean.ajc$interMethod$com_javaeye_domain_Employee_Roo_JavaBean$com_javaeye_domain_Employee$getBirth(this);    }    public Long getId()    {        return Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$getId(this);    }    public String getName()    {        return Employee_Roo_JavaBean.ajc$interMethod$com_javaeye_domain_Employee_Roo_JavaBean$com_javaeye_domain_Employee$getName(this);    }    public Integer getVersion()    {        return Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$getVersion(this);    }    public void merge()    {   Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$merge(this);    }    public void persist()    {     Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$persist(this);    }    public void remove()    {        Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$remove(this);    }    public void setBirth(Date date)    {        Employee_Roo_JavaBean.ajc$interMethod$com_javaeye_domain_Employee_Roo_JavaBean$com_javaeye_domain_Employee$setBirth(this, date);    }    public void setId(Long long1)    {        Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$setId(this, long1);    }    public void setName(String s)    {        Employee_Roo_JavaBean.ajc$interMethod$com_javaeye_domain_Employee_Roo_JavaBean$com_javaeye_domain_Employee$setName(this, s);    }    public void setVersion(Integer integer)    {        Employee_Roo_Entity.ajc$interMethod$com_javaeye_domain_Employee_Roo_Entity$com_javaeye_domain_Employee$setVersion(this, integer);    }    public String toString()    {        return Employee_Roo_ToString.ajc$interMethod$com_javaeye_domain_Employee_Roo_ToString$com_javaeye_domain_Employee$toString(this);    }    private String name;    private Date birth;    public transient EntityManager ajc$interField$com_javaeye_domain$entityManager;    public Long ajc$interField$com_javaeye_domain_Employee_Roo_Entity$id;    public Integer ajc$interField$com_javaeye_domain_Employee_Roo_Entity$version;    private static final org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_0; /* synthetic field */    private static final org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_1; /* synthetic field */    static     {        Factory factory = new Factory("Employee.java", Class.forName("com.javaeye.domain.Employee"));        ajc$tjp_0 = factory.makeSJP("initialization", factory.makeConstructorSig("1", "org.springframework.beans.factory.aspectj.ConfigurableObject", "", "", ""), 17);        ajc$tjp_1 = factory.makeSJP("initialization", factory.makeConstructorSig("1", "com.javaeye.domain.Employee", "", "", ""), 17);    }}


经过反编译生成的class发现,通过domain类上面的roo注释,自动在类编译时加入get set,甚至我们发现了findAllEmployees,persist,remove,哈,这不是我们梦寐以求的充血模型吗?

原来,roo在为我们创建domain的时候自动为同一个domain创建了4个aspect(.aj files),并在编译期动态为domain切入代码.
4个aspect分别是:
domainName_Roo_Configurable.aj(配置)
domainName_Roo_Entity.aj(加入orm持久)
domainName_Roo_JavaBean.aj(为字段生成get set)
domainName_Roo_ToString.aj(重写toString)


可见,使用roo我们彻底摆脱了在daomain里对各个属性写get set,并且使domain摇身一变为充血。

下一步,就是建Controller了,同样是一条语句
new controller automatic -formBackingObject ~.domain.Employee -name ~.web.CommentController


你会发现,EmployeeController,spring mvc的配置以及curd的jsp页面全部生成了

EmployeeController代码如下:
@RooWebScaffold(automaticallyMaintainView = true, formBackingObject = Employee.class)@RequestMapping("/employee/**")@Controllerpublic class EmployeeController {}


在这里出现了一行@RooWebScaffold注解,做过rails的人会想,这不是rails里面的scaffold吗?没错,通过@RooWebScaffold注解,EmployeeController自动获得了curd的所有功能。

同样是生成controllerName_Roo_Controller.aj在编译期织入代码

我们来看看生成的EmployeeController_Roo_Controller.aj:

package com.javaeye.web;privileged aspect EmployeeController_Roo_Controller {        @org.springframework.web.bind.annotation.RequestMapping(value = "/employee", method = org.springframework.web.bind.annotation.RequestMethod.POST)        public java.lang.String EmployeeController.create(@org.springframework.web.bind.annotation.ModelAttribute("employee") com.javaeye.domain.Employee employee, org.springframework.validation.BindingResult result) {            if (employee == null) throw new IllegalArgumentException("A employee is required");                for(javax.validation.ConstraintViolation<com.javaeye.domain.Employee> constraint : javax.validation.Validation.buildDefaultValidatorFactory().getValidator().validate(employee)) {                    result.rejectValue(constraint.getPropertyPath(), null, constraint.getMessage());                    }                if (result.hasErrors()) {                    return "employee/create";                    }                employee.persist();                return "redirect:/employee/" + employee.getId();            }            @org.springframework.web.bind.annotation.RequestMapping(value = "/employee/form", method = org.springframework.web.bind.annotation.RequestMethod.GET)        public java.lang.String EmployeeController.createForm(org.springframework.ui.ModelMap modelMap) {            modelMap.addAttribute("employee", new com.javaeye.domain.Employee());                return "employee/create";            }            @org.springframework.web.bind.annotation.RequestMapping(value = "/employee/{id}", method = org.springframework.web.bind.annotation.RequestMethod.GET)        public java.lang.String EmployeeController.show(@org.springframework.web.bind.annotation.PathVariable("id") java.lang.Long id, org.springframework.ui.ModelMap modelMap) {            if (id == null) throw new IllegalArgumentException("An Identifier is required");                modelMap.addAttribute("employee", com.javaeye.domain.Employee.findEmployee(id));                return "employee/show";            }            @org.springframework.web.bind.annotation.RequestMapping(value = "/employee", method = org.springframework.web.bind.annotation.RequestMethod.GET)        public java.lang.String EmployeeController.list(org.springframework.ui.ModelMap modelMap) {            modelMap.addAttribute("employees", com.javaeye.domain.Employee.findAllEmployees());                return "employee/list";            }            @org.springframework.web.bind.annotation.RequestMapping(method = org.springframework.web.bind.annotation.RequestMethod.PUT)        public java.lang.String EmployeeController.update(@org.springframework.web.bind.annotation.ModelAttribute("employee") com.javaeye.domain.Employee employee, org.springframework.validation.BindingResult result) {            if (employee == null) throw new IllegalArgumentException("A employee is required");                for(javax.validation.ConstraintViolation<com.javaeye.domain.Employee> constraint : javax.validation.Validation.buildDefaultValidatorFactory().getValidator().validate(employee)) {                    result.rejectValue(constraint.getPropertyPath(), null, constraint.getMessage());                    }                if (result.hasErrors()) {                    return "employee/update";                    }                employee.merge();                return "redirect:/employee/" + employee.getId();            }            @org.springframework.web.bind.annotation.RequestMapping(value = "/employee/{id}/form", method = org.springframework.web.bind.annotation.RequestMethod.GET)        public java.lang.String EmployeeController.updateForm(@org.springframework.web.bind.annotation.PathVariable("id") java.lang.Long id, org.springframework.ui.ModelMap modelMap) {            if (id == null) throw new IllegalArgumentException("An Identifier is required");                modelMap.addAttribute("employee", com.javaeye.domain.Employee.findEmployee(id));                return "employee/update";            }            @org.springframework.web.bind.annotation.RequestMapping(value = "/employee/{id}", method = org.springframework.web.bind.annotation.RequestMethod.DELETE)        public java.lang.String EmployeeController.delete(@org.springframework.web.bind.annotation.PathVariable("id") java.lang.Long id) {            if (id == null) throw new IllegalArgumentException("An Identifier is required");                com.javaeye.domain.Employee.findEmployee(id).remove();                return "redirect:/employee";            }            @org.springframework.web.bind.annotation.InitBinder        public void EmployeeController.initBinder(org.springframework.web.bind.WebDataBinder binder) {            binder.registerCustomEditor(java.util.Date.class, new org.springframework.beans.propertyeditors.CustomDateEditor(new java.text.SimpleDateFormat("yyyy-M-d"), false));            }        }

这样,Controller里不用写一行代码,就自动拥有了curd,而且,最新的roo使用了spring3.0,还支持了rest

好了,就这么简单,一个domain,一个Controller,我们就可以发布到tomcat中运行了。
不到5分钟,我们就可以生成了一个可运行的项目,是不是很有ror式的感觉啊?赶快来试试吧!







3 楼 zspzlxn 2009-11-06 太强了。。。领教了。。这种手法还是第一次看到 4 楼 zspzlxn 2009-11-06 一切皆有可能
5 楼 grandboy 2010-01-14 我昨天晚上试一下这个工具,如果能支持数据库就好了,要不然一条一条命令来加Field,还是挺麻烦的。希望它下一版本能加上这个功能。 6 楼 Arden 2010-01-14 关键是开发的时候修改东西要不要重启Web服务器!! 7 楼 liujunsong 2010-01-14 批评两句.雕虫小极而已.

也许这么做开始的时候能省点力气.可是等到一年以后你来维护这些代码的时候,是不是还能象现在这么高兴呢?想想这个问题吧,如果答案是否定的,那么就趁早放弃吧. 8 楼 herowzz 2010-01-15 liujunsong 写道批评两句.雕虫小极而已.

也许这么做开始的时候能省点力气.可是等到一年以后你来维护这些代码的时候,是不是还能象现在这么高兴呢?想想这个问题吧,如果答案是否定的,那么就趁早放弃吧.

那请赐教一下,跟您自己手写的代码相比,哪里不好维护了? 9 楼 grandboy 2010-01-15 liujunsong 写道批评两句.雕虫小极而已.

也许这么做开始的时候能省点力气.可是等到一年以后你来维护这些代码的时候,是不是还能象现在这么高兴呢?想想这个问题吧,如果答案是否定的,那么就趁早放弃吧.

这个东西现在还不成熟,只是1.0 release. 我不知道ror的开发过程是什么样的,请有这方面经验的讲一下。我是想用它先做一个雏形,把一些基础代码都生成,在此基础上进行修改,增加功能。可能我用得不好,好像不太适合我这种需求。 10 楼 魔力猫咪 2010-01-15 感觉是Grails把Groovy的外壳剥了就是roo了。 11 楼 herowzz 2010-01-15 在雏形的基础上进行修改,增加完全没有问题
用AspectJ插件可以很简单的将aj文件push in到类文件里
还可以直接对aj文件进行修改 12 楼 herowzz 2010-01-15 魔力猫咪 写道感觉是Grails把Groovy的外壳剥了就是roo了。
恩,确实,第一次使用roo的时候就有种grails的感觉,不过roo基于spring之上,使用java原生语言,与动态语言相比的性能优势,必然会获得很多怀旧玩家的支持。。。 13 楼 魔力猫咪 2010-01-15 “怀旧玩家的支持”?又不是老游戏换个引擎出XX版。
14 楼 herowzz 2010-01-15 其实东西还是spring的那套东西,引擎就是aspect
不过使用这个引擎之后,立马就变为了充血模型,开发方式都改变了
可见spring的这招很妙,可能有人会觉得小儿科,但是为什么都是人家发明了新东西,新思想,而我们却在这里除了会说“雕虫小技”,还会什么? 15 楼 herowzz 2010-01-15 Ben Alex 写道
ROO框架使用Aspect来描述像@Configurable这样的注解。在Roo框架中使用AOP背后的理由是什么?

至于为什么我们使用aspect作为Java代码生成的基础,背后有许多动机,我在我的系列博文的第三部分谈到了这一内容。但是实质上我提供了一些不同的备选技术原型,包括JSR 269、构建时生成源代码、IDE插件、开发时产生字节码、运行时产生字节码以及高级反射方法如扩展Spring Framework AOP、DSL等等。

我优先要考虑的事情是确保:
就算用户停止使用这一工具,他们的项目照样能够进行;这直接就排除了采取任何运行时动作的可能性。
在运行时,用户的项目必须不以牺牲性能为代价;这直接排除了大多数反射方法的可能性。
开发者要能够使用他们已有的Java知识、技巧和经历。于是工具必须支持常规Java编程体验、支持开发者的普通编程形式、使他们可以使用自己常用的IDE,访问熟悉的工具如debugger和代码助手。该工具必须能使开发者运用已知、已理解及所期望的东西。这样就排除了任何特定的字节码方法以及大多数运行时方法。
该工具必须能自动工作,并且不需要明确调用,也不需要系统集成或建造特定IDE。它必须绝对支持透明的、即时的round-tripping。这就排除了JSR 269以及构创建于系统等方法,比如类似于XDoclet的工具。
其次要考虑:
用户的项目必须工作于Java 5及更高版本上。这就排除了JSR 269,将其排除的原因还有很多(比如原型时期脆弱的IDE支持)。
该工具应该容易让最终用户进行扩展。当然,由于我们使用了AspectJ技术,这是件非常容易的事。它还不鼓励使用任何特定IDE的技术,这可能比Roo附加组件需要更复杂的开发和部署过程。
该工具对增加附加组件的支持应该是长期的。使用AspectJ ITD提供的分离概念可以很容易的实现这一点。
该工具应该非常轻量级:下载、学习、运转都应非常迅速。这对一个拥有最少依赖的基于shell的方法非常有利。Roo 下载文件小于3M!
实质上AspectJ ITDs增加了一个自动维护的元数据模型,它是我们能找到的唯一解决方案,可以满足所有需求。当然,如果你愿意牺牲一些表达方面的要求的话,自然会有其他方法解决这一问题。


16 楼 tanbamboo 2010-01-15 通过Aspect-J来做这些,我觉得相当不错的,起码是预编织的,比动态的性能肯定要好。
另外通过annotation方式做scaffold的话,也比rails的方式方便,起码后期改动要方便。
不知道如果要override CRUD中某个方法,是不是很方便。
有空尝试一下。

Grails小小的试用了一下,个人感觉不好,与rails的差距比较大,尤其是命令脚步执行那个慢啊。 17 楼 Kymair 2010-01-19 liujunsong 写道批评两句.雕虫小极而已.

也许这么做开始的时候能省点力气.可是等到一年以后你来维护这些代码的时候,是不是还能象现在这么高兴呢?想想这个问题吧,如果答案是否定的,那么就趁早放弃吧.

其实这是一种程序员常有的误区

问题是,你如果比较好的掌握了它,你根本就不需要再去看生成后的代码,除非是库本身有BUG。这种可能性不排除,也许你会说,到时候调试查错困难,但是相对于它给你减少的代码和工作量从而提高的效率,从而减少的潜在BUG,比因它自己可能的BUG给你带来的阻碍,应该更多吧?
软件开发就是这种方向,复杂性越来越高,只能抽象等级也越来越高。 18 楼 herowzz 2010-01-19 Kymair 写道liujunsong 写道批评两句.雕虫小极而已.

也许这么做开始的时候能省点力气.可是等到一年以后你来维护这些代码的时候,是不是还能象现在这么高兴呢?想想这个问题吧,如果答案是否定的,那么就趁早放弃吧.

其实这是一种程序员常有的误区

问题是,你如果比较好的掌握了它,你根本就不需要再去看生成后的代码,除非是库本身有BUG。这种可能性不排除,也许你会说,到时候调试查错困难,但是相对于它给你减少的代码和工作量从而提高的效率,从而减少的潜在BUG,比因它自己可能的BUG给你带来的阻碍,应该更多吧?
软件开发就是这种方向,复杂性越来越高,只能抽象等级也越来越高。

调试查错也不可能困难,生成的代码都在你手上,你想调试就调试,想改就改
除非一种可能,就是那位仁兄根本就看不懂,不会。如果连spring都不懂,不会的话,那讨论的重点就不在roo上了,而是spring适不适合他了... 19 楼 xuyangcn 2010-04-29 刚刚试了一下,发现对于基本的CRUD确实不错,但是我试图整合JQuery的Plugin不成功,没有出错信息。无语啊。 20 楼 binarier 2010-05-12 这里有一点刚接触时都会忽略的,就是加字段不需要一个个地add field,只要修改Entity,在ROO保持打开的情况下,那些aspectJ 的 ITD会实时生成的。 21 楼 Angel_Night 2010-07-06 herowzz 写道其实东西还是spring的那套东西,引擎就是aspect
不过使用这个引擎之后,立马就变为了充血模型,开发方式都改变了
可见spring的这招很妙,可能有人会觉得小儿科,但是为什么都是人家发明了新东西,新思想,而我们却在这里除了会说“雕虫小技”,还会什么?

ajax刚出来的时候很多人认为雕虫小技都不算吧。。
22 楼 fjjiaboming 2011-12-27 很强大.
思想啊.

读书人网 >Ruby Rails

热点推荐