设计一个容易的service-oriented(面向服务)的J2EE应用

设计一个简单的service-oriented(面向服务)的J2EE应用
    这篇文档翻译自 http://www.javaworld.com/javaworld/jw-10-2004/jw-1004-soa_p.html?page=1 ,这个文档的时间是 04年的,所以从今天的角度来看,原文的观点未必全部正确,但是作者用一个如此简单的例子来阐述了SOA,我想对SOA的理解还是有一定帮助的。

yananay@126.com
2007年6月




现在大家都有一个共识,就是开源的项目会对大家的项目有所帮助:Structs,Spring,Hibernate,Tiles,Avalon,WebWorks,Tapestry,Oracle ADF,等等还有很多很多。但是许多人也发现这些东西并非解决问题的万灵药,因为“开放源代码”并非就意味着它们很容易被修改和改进。
当你面对一个某一个专业领域时,那么最好的办法是,基于某些framework来创建自己的framework。不过,如果要作一个像structs那样的framework,实在是不容易的事,但是如果要在structs或者其他framework上进行封装,那就容易得多了。
在这个文档里,我将演示如何创建一个framework,我给它起个名字叫x18p(xiangnong 18 palm,降龙十八掌)。这是一个简单的framework,通过这个framework,我们来看一看目前被许多j2ee framework 所忽视的2个方面:紧耦合与肿胀的DAO代码。
正如你所看到的,x18p这个framework会基于structs,spring,Axis,Hibernate等等其他开源framework。我们当然更希望您能通过这个文档,来提高自己搭建framework的能力,并且在更多的项目中来逐渐完善它。

我开发的过程遵循了RUP(IBM Rational Unified Process)。步骤如下:

1、 确定一个简单的目标
2、 分析现存的j2ee架构,并且找出问题的所在
3、 比较众多的framework,然后选择其中一个比较合适的而且简单的
4、 编写代码,并且持续重构
5、 多和使用framework的开发人员交流,并且获得反馈
6、 测试,测试,还是测试


确定一个简单的目标

我们要有一个伟大的目标,我们要设计一个framework来解决一切可能出现的问题。如果你有足够的资源,那这个目标可真是一个好想法。
不过,通常老板们认为,在你的项目开始之前去设计一个framework,这可是一个看不见商业价值的消耗。为了降低不可预见的风险,愉快地工作,降低学习曲线,保证项目的利益,根据我多年的j2ee的经验,这个我们可以分成2个部分来完成。
1、 降低j2ee Action 部分的偶合性
2、 降低j2ee中DAO层的重复代码

总的来说,我想提供优秀质量的代码,并且降低项目的成本和维护成本。所以,我会用对这个2个部分分别用上面提到的6个步骤来实现。


降低代码的耦合度
1. 分析以往的j2ee架构

如果我们手头有一个j2ee framework,我们首先必须知道如何才能改进它。光说是没用的,我们还是先来看看一个典型的j2ee架构 structs 吧!

设计一个容易的service-oriented(面向服务)的J2EE应用

Action 调用 xxxManager, xxxManager 又调用 xxxDAOs。可以看出,在一个典型的structs架构中,我们会涉及到如下的元素:

• HttpServlet 或者 Action,用来传递HttpRequest 或者 HttpResponse
• Business logic层
• 数据访问层(DAO)
• 领域层(Domain)

上面的架构有什么问题?问题就是:紧耦合。如果Action中的逻辑很简单,那么没有任何问题。那么如果你要访问一些EJB呢?如果你要通过不同的资源调用web服务呢?如果你需要访问JMX呢?
通过 structs-config.xml,Structs 有这样的能力去帮助你实现这些功能吗?答案是:不能。Structs 仅仅是一个web端的framework,不过如果你在action 里编写调用一些其他服务的代码也不是不可以,但那样做的话,就导致在Action的execute()方法里混合了2种类型的代码。哪2种代码呢?

第一种就是web段的,如 HttpRequest/ HttpResponse。对于一次调用来说,你可以从HttpRequest或者Actionform里获得提交的数据,你也可以把数据放到HttpResquest里或者Session里,然后在jsp里显示它。

第二种代码就是业务逻辑的。在Action,你也可以调用后台的代码,如EJB,JMX,甚至去操作JDBC的数据源。你也可以使用“定位服务”的模式去加载一些业务逻辑类,当然也可以生成一个POJO如xxxManager。虽然有很多方式,但是都有一个缺点,就是Action必须考虑后台对象的类型。

但这就是Action的工作方式,难道不是吗?Action其实就是servlet,它负责从request/response里得到数据,同时也负责把数据放到 request/response或者session里,用于在jsp里显示。它同时也是和业务逻辑层交互的一个桥梁 — 从业务逻辑层里得到数据或者更新数据。不过,Action并不考虑以什么方式或者协议来和业务逻辑层联系。

或许你可以想象,你可以结束Action和业务逻辑层这种紧耦合的现状。
这就是我们要解决的问题。在众多的开源framework中,spring 进入了我的视野。


2. 比较、选择framework

Spring 的核心被称作 BeanFactory。它不同于以往的服务定位模式,它有一个新名词,叫 IOC(Inversion-of-Control 控制反转),这个特性之前被称作“Injection Denpendency”(依赖注入)。其思想就是通过调用 ApplicationContext的getBean方法来得到一个对象,这个方法从Spring的配置文件里寻找对象的定义,然后创建这个对象,然后以 java.lang.Object 的类型返回这个对象。用“getBean”方法是一个查找对象的好办法。这意味着在 Action中,我们只需要引用一个ApplicationContext 就可以了。不过这并不是重点,因为我们还必须把得到的对象转换成正确的类型,如EJB,或者JMX。这么一来,Action仍然需要考虑后台对象的类型 — 这仍然是紧耦合。

如果我们要避免这一点,那么该如何做?很自然的,service(服务),这个词出现在我的脑海里。Service 是一个广泛的概念,任何东西都可以被称作service,而不仅仅是那个名字就叫作 web-service的service。Action 也可以把一个EJB当作service,也可以把一个JMX当作service。我想我们设计一个service才是正确的途径。

随着战略的清晰,我们通过分析进一步降低了风险,我们将要发挥我们的创造力,去创建一个service 层来演示这个“面向服务”的内容!

3. 代码和重构

为了把“面向服务”这个概念变成可以运行的代码,我们必须考虑以下这些事情:

• service层将会存在于web层和业务逻辑层之间。
• web层仅仅调用 service层的控制类,service层的控制类会根据x18p-config.xml 来调用不同的“服务提供者”。
• “服务提供者”可以去调用相应的服务,这里的服务可以是任何类型的,EJB,JMX,LDAP,等等。X18p-config.xml 必须提供足够的数据来让“服务器提供者”完成每次调用。
• 使用Spring来实现对象的查找和引用。
• 持续增加“服务提供者”。正如你所看到的,“服务提供者”类型越多,我们的x18p framework就越有威力!
• 使用现有的structs的知识,但是不要漏掉新出现的知识。

现在,我们比较一下在使用了x18p framework前后的Action代码:
public ActionForward execute(ActionMapping mapping, ActionForm form, 
		HttpServletRequest request, HttpServletResponse response)throws 
			IOException, ServletException {
      		...
      		UserManager userManager = new UserManager();
      		String userIDRetured = userManager.addUser("John Smith")
      		...
	}


应用了x18p framework后的Action:

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {
      ...
      ServiceRequest bsr = 
	this.getApplicationContext().getBean("businessServiceRequest");   
      
      bsr.setServiceName("User Services");
      bsr.setOperation("addUser");
      bsr.addRequestInput("param1", "addUser");
      String userIDRetured = (String) bsr.service();
      ...
}


Spring 可以去负责载入对象,看看下面的applicationContext.xml:
设计一个容易的service-oriented(面向服务)的J2EE应用

在ServiceRequest.java 里,“service” 方法很简单,只是让Spring去载入“serviceRouter”(我们叫它服务控制类),并且把自己作为参数传递进去:
public Object service() {
     	 	return ((ServiceRouter) this.serviceContext.getBean("service 
			router")).route(this);
	 }



“serviceRouter”(服务控制类)再通过 x18p-config.xml 去寻找“服务提供者”。重点就是Action 不必知道“服务提供者”是如何实现的,它仅仅知道如何使用一个服务,如何给服务传递参数,如何处理服务返回的类型就可以了。下面看看这个 x18p-config.xml。
设计一个容易的service-oriented(面向服务)的J2EE应用

对于“User Service”这个服务来说,“serviceRouter”(服务控制类)会创建一个POJO的服务提供者去处理服务的请求。这个POJO的springObjectId是“userServiceManager”,这个POJO服务提供者再通过spring和这个springObjectId来得到一个POJO。如果“userServiceManager”使用的类是X18p.framework.UserPOJOManager,那么这个UserPOJOManager就应该一个逻辑处理的类。

下面测试一下ServiceRouter.java。

public Object route(ServiceRequest serviceRequest) throws Exception {
      //        /1. Read all the mapping from XML file or retrieve it from Factory
      //         Config config = xxxx;
      //        2. Get service's type from config.
      String businessServiceType = Config.getBusinessServiceType(serviceRequest.getServiceName());
      //        3. Select the corresponding Router/Handler/Controller to deal with it.
      if (businessServiceType.equalsIgnoreCase("LOCAL-POJO")) {
         POJOController pojoController = (POJOController) Config.getBean("POJOController");
         pojoController.process(serviceRequest);
      }
      else if (businessServiceType.equalsIgnoreCase("WebServices")) {
         String endpoint = Config.getWebServiceEndpoint(serviceRequest.getServiceName());
         WebServicesController ws = (WebServicesController) Config.getBean("WebServicesController");
         ws.setEndpointUrl(endpoint);
         ws.process(serviceRequest);
      }
      else if (businessServiceType.equalsIgnoreCase("EJB")) {
         EJBController ejbController = (EJBController) Config.getBean("EJBController");
         ejbController.process(serviceRequest);
      }
      else {
         System.out.println("Unknown types, it's up to you how to handle it in the framework");
      }
      //        That's it, it is your framework, you can add any new ServiceProvider for your next project.
      return null;
   }



上面的代码中的“if-else”块,可以被重构成command模式。“Config”对象提供了 Spring 和 x18p 的配置文件信息,你可以自己决定如何去寻找配置信息的方法。

假设我们实现了一个POJO manager,TestPOJOBusinessManager,然后POJO服务提供者(POJOServiceController.java)会使用反射的方式从TestPOJOBusinessManager里调用addUser方法。

通过在xml配置文件中介绍这三个类(BusinessServiceRequester,ServiceRouter,ServiceProviderController),我们对service-oriented(面向服务)的framework 有了一个概念上的验证。Action 并不关心服务是如何实现的,它只关心如何输入和如何输出。

在web端,structs的开发者不会看到复杂的API和各种服务如何执行,他们只需要去调用 – 那些复杂的东西就好像被屏蔽了一样。

如果 x18p-config.xml 能够被恰当的设计,那么structs的开发者与后台业务逻辑的开发者就可以协调的工作了!

下面看看我们新的framework架构:
设计一个容易的service-oriented(面向服务)的J2EE应用

下面我们把一些服务提供者和实现方式归纳一下,当然,您可以可以方便的增加新的内容:

服务类型           服务提供者               实现方式
POJO      POJOController     J2SE
Web Service  WebServiceController Apache Axis
EJB     EJB Controller              J2EE
JMX      JMXController              M4JX


我们现在就把如何实现web service controller 来做一个例子!
WebServiceController 创建一个Axis 服务的对象,然后把需要的参数传给它,并且调用它的方法,然后返回。为了让这个例子简单些,我们只用String 来作为例子。

public Object process(ServiceRequest requester) throws Exception {
      String ret = null;
      try {
         Service service = new Service();
         Call call = (Call) service.createCall();
         String methodName = requester.getOperationName();
         call.setTargetEndpointAddress(new java.net.URL(endpointUrl));
         call.setOperationName(methodName);
         List parameters = (List) requester.getServiceInputs();
         //
         int sizeOfParameters = parameters.size();
         Object[] args = new Object[sizeOfParameters ];
         log.debug("REQUESTING Web Service:  [" + methodName + "], Inputs   [" +  sizeOfParameters + "]");
         //TODO
         boolean isMethodFound = false;
         
         for (int i = 0; i < sizeOfParameters; i++) {
            int currentIndex = i;
            
            call.addParameter("op" + (currentIndex + 1), XMLType.XSD_STRING, ParameterMode.IN);
            args[currentIndex] = parameters.get(currentIndex);
            
            log.debug("SET [" + currentIndex + "], VALUE [" +  args[currentIndex] + "]");
         }
         call.setReturnType(XMLType.XSD_STRING);
         ret = (String) call.invoke(args);
         log.debug(" Web Service: " + methodName + ", Got result : " + ret);
      }
      catch (java.net.MalformedURLException ue) {
         ue.printStackTrace();
      }
      catch (java.rmi.RemoteException re) {
         re.printStackTrace();
      }
      catch (javax.xml.rpc.ServiceException e) {
         e.printStackTrace();
      }
      return ret;
   }



事实上,Axis可以支持很多java类型。不过从上面的小代码片断您可以看到如何去实现一个服务提供者,而且,我们发现实现一个服务提供者并不是非常的难。




4. 和用户交流并测试

上面的代码仅仅是作为一个演示,我们可不能把它直接拿来就用。还有很多工作的需要完成,但是我们怎么知道我们需要完善哪些地方?答案就是反复地问。X18p framework的用户会让你知道哪些地方需要改善,你的责任就是使x18p framework 更加强壮。
你已经了解了x18p framework的架构,那么找到哪里是用户所关注的,就不会很难了吧?然而,测试还是不可缺少的,并且是很重要的。

当然我们还有其他一些需要考虑的事情:
• 在服务端提供缓存的功能
• JDOM的事物支持



剩余的部分我就不在这里贴了,大家可以下载附件去看。因为我觉得剩下的部分以今天
的观点来看,有点过时了设计一个容易的service-oriented(面向服务)的J2EE应用

本文章所涉及的代码下载:
http://www.javaworld.com/javaworld/jw-10-2004/soa/jw-1004-soa.zip
1 楼 gfh21cn 2007-06-13  
好文,先顶
2 楼 kamiiyu 2007-06-13  
谢lz,下了以后慢慢看