论坛首页 Java版

一个Java的Restful服务框架,支持JPA、JAAS、分布式资源对象

浏览 5651 次
该帖已经被评为精华帖
作者 正文
时间:2008-06-07 关键字: rest
项目地址: http://code.google.com/p/jrest4guice/
Demo演示: http://cnoss.vicp.net/


当前版本:0.9.0 preview

特点:
  • 基于Google guice
  • 零配置,服务的自动扫描注册
  • 非侵入式,用户不需要实现特定的接口来实现Restful服务,只需要通过@RESTful来声明
  • 支持Post. Get. Put. Delete操作
  • 支持对Get操作的缓存机制,实现动态资源静态化(通过@Cache标注声明)
  • 灵活的注入(支持上下文环境request/response/session以及参数的自动注入)
  • 根据客户端要求返回不同类型的数据(xml/json/html)
  • 通过@PageFlow实现对MVC module2的支持,输出结果支持Velocity、Freemarker和Spry模板引擎(当返回类型是text/html时才有效)
  • 支持JPA,通过增强的BaseEntityManager实现实体的CRUD
  • 支持事务,通过@Transactional注解声明事务的类型
  • 支持JAAS,通过@RolesAllowed注解声明操作所需要的角色
  • 支持分布式资源对象,实现业务逻辑的分布式部署

下一步计划:
  • 数据验证的支持
  • OSGI的支持
  • 分布式事务的支持

代码示例:
/**
 * @author <a href="mailto:zhangyouqun@gmail.com">cnoss (QQ:86895156)</a>
 * 联系人的资源对象
 * 声明remoteable为真(可以通过@RemoteReference的注入到任一资源对象,通常用在跨应用的资源调用上)
 */
@RESTful(name = "ContactResource", remoteable = true)
@Path( { "/contact", "/contacts/{contactId}" })
public class ContactResource {
	@Inject
	private ContactServiceDomain domain;

	/**
	 * 创建新的联系人 
	 * @param contact 联系人实体
	 */
	@Post
	public String createContact(@ModelBean Contact contact) {
		return this.domain.createContact(contact);
	}

	/**
	 * 修改联系人信息 
	 * @param contact 联系人实体
	 */
	@Put
	public void putContact(@ModelBean Contact contact) {
		this.domain.updateContact(contact);
	}

	/**
	 * 显示联系人列表 
	 * PageFlow :当服务端返回类型是Text/Html类型时,重定向用户的请求到指定的页面,实现最基本功能的MVC。
	 * 		在这里,指明当操作成功时,重定向到列表人列表页面,并使用Velocity模板进行渲染,当操作失败时,
	 * 		将用户请求重定向到操作出错页面。
	 * @param pageIndex 页码 
	 * @param pageSize 每页记录数
	 */
	@Get
	@Path("/contacts")
	@PageFlow(
			success = @PageInfo(url = "/template/contacts.vm",render=ViewRenderType.VELOCITY), 
			error = @PageInfo(url = "/template/error.vm",render=ViewRenderType.VELOCITY))
	public Page<Contact> listContacts(int pageIndex, int pageSize) {
		return this.domain.listContacts(pageIndex, pageSize);
	}

	/**
	 * 显示单个联系人的信息 
	 * @param contactId 联系对象ID
	 */
	@Get
	@PageFlow(success = @PageInfo(url = "/template/contactDetail.vm"))
	@Cache //声明需要缓存结果,可以减少应用服务器及数据库的压力
	public Contact getContact(@Parameter("contactId") String contactId) {
		return this.domain.findContactById(contactId);
	}

	/**
	 * 删除指定ID的联系人 
	 * @param contactId 联系对象ID
	 */
	@Delete
	public void deleteContact(@Parameter("contactId") String contactId) {
		this.domain.deleteContact(contactId);
	}
}

/**
 * 业务领域对象
 * @author <a href="mailto:zhangyouqun@gmail.com">cnoss (QQ:86895156)</a>
 *
 */
@SuppressWarnings( { "unused" })
public class ContactServiceDomain{
	private BaseEntityManager<String, Contact> entityManager;

	@Inject
	private void init(EntityManager em) {
		this.entityManager = new BaseEntityManager<String, Contact>(
				Contact.class, em);
	}

	@Transactional
	public String createContact(Contact contact) {
		if (contact == null)
			throw new RuntimeException("联系人的内容不能为空");

		if (this.entityManager.loadByNamedQuery("byName", contact.getName()) != null) {
			throw new RuntimeException("联系人的姓名相同,请重新输入");
		}

		this.entityManager.create(contact);
		return contact.getId();
	}

	@Transactional //事务声明
	@RolesAllowed({"admin","user"}) //权限声明
	public void deleteContact(String contactId) {
		String[] ids = contactId.split(",");
		Contact contact;
		for (String id : ids) {
			contact = this.findContactById(id);
			if (contact == null)
				throw new RuntimeException("联系人不存在");
			this.entityManager.delete(contact);
		}
	}

	@Transactional(type=TransactionalType.READOLNY)
	public Contact findContactById(String contactId) {
		return this.entityManager.load(contactId);
	}

	@Transactional(type=TransactionalType.READOLNY)
	public Page<Contact> listContacts(int pageIndex, int pageSize)
			throws RuntimeException {
		return this.entityManager.pageByNamedQuery("list",
				new Pagination(pageIndex, pageSize));
	}

	@Transactional
	public void updateContact(Contact contact) {
		if (contact == null)
			throw new RuntimeException("联系人的内容不能为空");
		
		Contact tmpContact = this.entityManager.loadByNamedQuery("byName", contact.getName());
		if(tmpContact != null && !contact.getId().equals(tmpContact.getId()))
			throw new RuntimeException("联系人的姓名相同,请重新输入");

		this.entityManager.update(contact);
	}
}


/**
 * 远程资源引用的示例
 * @author <a href="mailto:zhangyouqun@gmail.com">cnoss (QQ:86895156)</a>
 * 
 */
@Path({"/testCallRemote"})
public class TestRemoteResource {
	@Inject
	@RemoteReference //注入远程资源对象的引用
	private ContactResource service;

	@Get
	@ProduceMime( {MimeType.MIME_OF_JSON,MimeType.MIME_OF_JAVABEAN})
	public Page<Contact> listContacts(int pageIndex, int pageSize) {
		return this.service.listContacts(pageIndex, pageSize);
	}
}


请大家直接从SVN中获取JRest4Guice、JRest4Guice-sample两个工程即可(使用Maven)

真诚希望大家提出宝贵意见,联系方式:
  • Email:zhangyouqun@gmail.com
  • QQ: 86895156
  • MSN: zhangyouqun@hotmail.com
   
时间:2008-06-07
不知开发的效率如何?
   
0 请登录后投票
时间:2008-06-07
  • 1、将程序员从XML与JavaBean之间的来回切换中解放了出来。保持了思维的连贯性。
  • 2、弱化了DAO层,将其替换成BaseEntityManager,即使现在很多人用的动态DAO,虽然不用写实现,但是还是要写一堆看上去很怪的只有接口没有实现的DAO,加重了执行过程中的反射压力。
  • 3、BaseEntityManager增强并集成了我们常用的DAO功能,(查询、分页、动态参数构造等)减少了重复代码。
  • 4、注解式的权限声明为我们实现RBAC的权限系统提供了底层的支持。
   
0 请登录后投票
时间:2008-06-07
支持事务,通过@Transactional注解声明事务的类型
支持JAAS,通过@RolesAllowed注解声明操作所需要的角色
支持分布式资源对象,实现业务逻辑的分布式部署

这几点非常不错,很诱人,我先下载代码研究一下,期待对OSGI的支持
   
0 请登录后投票
时间:2008-06-12
上手起来真是方便,没想到还能通过这么简单代码实现如此强大的功能!楼主,能不能加入你的团队?谢谢!
   
0 请登录后投票
时间:2008-06-12
我的Email:nieyunf@gmail.com
   
0 请登录后投票
时间:2008-06-13
根据我使用Guice的经验,应该尽量使用Constructor injection,用field injection测试会非常困难。

你的annotation有些像JAX-RS,是否有支持JAX-RS的想法?
   
0 请登录后投票
时间:2008-06-13
yizhuo 写道
根据我使用Guice的经验,应该尽量使用Constructor injection,用field injection测试会非常困难。

你的annotation有些像JAX-RS,是否有支持JAX-RS的想法?

  • 1、先发一段引用来看一下三种不同方式注入的区别
  • 引用

    Guice best practices
    Field vs. method vs. constructor injection

    Field injection

    * + Most compact syntax (good for a trivial custom provider, e.g.)
    * - Can't take any special action upon injection
    * - Your class is not testable!

    Method injection

    * + Isn't field injection
    * + Only thing that works for some strange edge cases

    Constructor injection

    * + Fields can be final!
    * + Injection cannot possibly have been skipped, even if Guice is not in the picture
    * + Easy to see dependencies at a glance
    * + It's what the idea of construction is all about
    * - No optional injections
    * - Useless when Guice can't do instantiation itself, e.g. a servlet
    * - Subclasses need to "know about" the injections needed by their superclasses
    * - Less convenient for tests that only "care about" one of the parameters


    以上说明得很清楚,三种方式各有优缺点,我的理解是:对于非自定义提供者的使用构造器级注入,对于要带业务逻辑的使用方法级注入,而对于自定义提供者的采用属性级注入。我的案例中有以下的代码片段:

    1. 属性级的注入,因为使用了自定义提供者
    2.     @Inject
          @RemoteReference //注入远程资源对象的引用
          private ContactResource service;   
      

    3. 方法级的注入,因为要做额外的操作
    4. 	private BaseEntityManager<String, Contact> entityManager;
      
      	@Inject
      	private void init(EntityManager em) {
      		this.entityManager = new BaseEntityManager<String, Contact>(
      				Contact.class, em);
      	}
      

    5. 构造器级的注入,可以将我之前的代码改为
    6. 	private ContactService service;
      	
      	@Inject
      	public ContactResource(ContactService service){
      		this.service = service;
      	}
      

  • 2、因为JSR311还没有最终发布,所以只是借鉴其中的一些规范。我个人也不太喜欢JSR311中子资源(sub resource)的实现方式。
  •    
    0 请登录后投票
    时间:2008-06-13
    最新变动 !!!

    JRest4Guice已转成Maven方式的管理,请大家下载原代码时注意。

    本次变动采纳了
    agapple 写道
    小建议一下, 能否像webwork一样,使用maven进行管理依赖

    这样checkout就不需要下载额外的jar
    的建议,谢谢agapple提出的宝贵意见。
       
    0 请登录后投票
    时间:2008-06-13
    注解,又是注解。
    改动注解就要重新编译。
    已经没有了配置文件的有点,所以也就是说和硬编码没有什么区别。

    动态语言代码就是配置文件~但是Java不行。
       
    8 请登录后投票
    论坛首页 Java版

    跳转论坛:
    JavaEye推荐