首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > JAVA > J2EE开发 >

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

2012-11-07 
设计一个简单的service-oriented(面向服务)的J2EE应用这篇文档翻译自 http://www.javaworld.com/javaworld

设计一个简单的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 吧!



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:


在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。


对于“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架构:


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

服务类型          服务提供者               实现方式
POJO     POJOController    J2SE
Web Service  WebServiceControllerApache 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的事物支持



剩余的部分我就不在这里贴了,大家可以下载附件去看。因为我觉得剩下的部分以今天
的观点来看,有点过时了

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

热点排行