技术开发 频道

EJB 非常好的实践: 动态委派

  【IT168 技术文章】尽管业务委派类确实给您的企业 Java 设计带来了激动人心的新灵活性,但为您应用程序中的每个会话 bean 都编码一个业务委派还是太麻烦了。在本文向您展示如何创建业务委派类的更通用的版本:动态委派。

  在前文中,我们讨论了如何用业务委派类(请不要与业务接口(Business Interface)模式相混淆)来访问您的 EJB 组件。通过在客户机代码和 EJB 代码之间插入业务委派类,我们可以将应用程序的 Web 层与 EJB 语义和业务逻辑隔离开来。

  研究这类设计的一种方法是看它有多通用。先从一个应用程序入手,该应用程序中的业务逻辑和技术函数是紧密地交织在一起的,我们已经逐步分离出应用程序的不同层,并使用不同的技术来降低它们的相互依赖。在这样做时,您应该会发现:应用程序底层结构越通用,则随着时间的推移,它就会越健壮且可维护性越好。

  业务委派类:复习

  回顾一下我们上个月讨论的 Library bean 接口的业务委派类。

  LibraryDelegate 类的大部分代码只是复制了原始 Library bean 的方法。 LibraryDelegate 添加了 init() 、 destroy() 和构造器方法,然后用这些方法将任务委派给 Library bean。在这样做时,委派充当 Web 层和企业 bean 之间的缓冲区。这里是 原始 bean 的业务接口。

  方法的繁殖

  除非您考虑到多个会话 bean 有 10 个、20 个或更多方法,否则这种方法的问题并不明显。实际上,找到拥有 50 个或更多方法的会话 bean 并不罕见!因为 bean 的业务接口必须包括该 bean 的所有方法,所以业务委派类也将这样做。那会使代码过于庞大,并很容易出错。

  除了庞大的代码之外,我们还必须考虑变化因素。因为 Delegate 类必须复制 bean 的所有方法,并且随着时间推移,bean 不可避免地会发生变化,您会发现需要花费很多时间来将新的方法添加到 Delegate ,更别提重新编译了,有可能还要测试新代码。

  就其本身而言,这看起来似乎不是很严重的问题。但假如我们开始使用业务委派类来从技术基础结构(在本文是指 EJB 组件)中抽象业务和表示逻辑呢。如果更改远程接口需要对业务委派进行更改,那么,实际上,我们的业务委派仍然与底层组件联系在一起。

  我们需要更好的方法,您说是不是。确实有更好的方法。

  动态委派

  解决方案是使用 动态委派,而它又使用 Java 反射(reflection)。您可以使委派动态地调用目标 EJB 组件的远程接口上的方法(通过 Java Reflection API),而不必将每个业务方法硬编码到委派中。这样允许彻底消除来自远程接口的耦合,因为,为 bean 的业务或远程接口添加方法时不需要在业务委派中进行相应更改。使用动态委派还使得更改您的技术基础结构更为容易。从远程接口和 EJB 技术迁移到另一种技术(如 Java Data Objects,JDO)只需要更改委派的 init() 方法。所有其它方法调用将继续通过 bean 的接口进行委派,并且可以继续使用无需进一步更改。清单 1 显示了 Library 业务委派的动态版本:

  清单 1. Library bean 的业务委派

1 package com.ibm.library;
2 import java.lang.reflect.Method;
3 import java.lang.reflect.InvocationTargetException;
4 import java.rmi.RemoteException;
5 import java.util.HashMap;
6 import java.util.Map;
7 import javax.ejb.CreateException;
8 import javax.naming.NamingException;
9 public class LibraryDelegate implements ILibrary {
10   private ILibrary library;
11   private Map availableMethods;
12   public LibraryDelegate() {
13     init();
14   }
15       public void init() {
16         // Look up and obtain our session bean
17         try {
18           LibraryHome libraryHome =
19             (LibraryHome)EJBHomeFactory.getInstance().lookup(
20               "java:comp/env/ejb/LibraryHome", LibraryHome.class);
21           library = libraryHome.create();
22           // Get the methods available for use in proxying
23           availableMethods = new HashMap();
24           Method[] methods = ILibrary.class.getMethods();
25           for (int i=0; i<methods.length; i++) {
26             availableMethods.put(methods[i].getName(),
27               methods[i]);
28           }
29         } catch (NamingException e) {
30           throw new RuntimeException(e);
31         } catch (CreateException e) {
32           throw new RuntimeException(e);
33         } catch (RemoteException e) {
34           throw new RuntimeException(e);
35         }
36       }
37       // All the hard-coded methods are removed
38       public Object
39         invoke(Object proxy, Method method, Object[] args)
40         throws Throwable{
41         try {
42           // See if this is init() or destroy()
43           if (method.getName().equals("init")) {
44             init();
45             return null;
46           } else if (method.getName().equals("destroy")) {
47             destroy();
48             return null;
49           } else {
50             Method method =
51               (Method)availableMethods.get(method.getName());
52             // See if we found anything
53             if (method != null) {
54               return method.invoke(library, args);
55             } else {
56               throw new
57                 NoSuchMethodException("The Library does not " +
58                 "support the  " + method.getName() +" method.");
59             }
60           }
61         } catch (InvocationTargetException e) {
62           // We don't support throwing RuntimeExceptions from EJBs
63           // directly
64           if (e.getTargetException() instanceof RemoteException) {
65             throw new RuntimeException(e);
66             } else {
67               throw e.getTargetException();
68             }
69           }
70         }
71         public void destroy() {
72           // In this case, do nothing
73         }
74       }
75

  动态委派出色地解决了委派、bean 及其业务接口之间的耦合问题。但是,它并不是完美的解决方案,也不会总是最好的解决方案。虽然您从这种方法中获得了极大的灵活性,但也付出了性能代价。Java 反射并不十分快,因此在调用 invoke() 和获得结果之间,您会感到一些延时。前一篇技巧文章中展示的静态业务委派类是更快的解决方案,但它使您的业务层和技术层的耦合程度比您所希望的要高。因此,在权衡这两个选择时,选择哪一个要根据设计或性能而定。当应用程序的设计比整体性能更重要时,动态委派是更好的选择。当性能是更重要的因素时,业务委派是更好的选择。

  您可能会发现自己正在将动态委派用于内部网应用程序,在内部网中,所有机器都在本地网络上,并且您会经常添加或更改功能。对于电子商业和面向顾客的应用程序,原始的业务委派可能是更好的选择。在这两种情况下,您现在都应该对业务委派及其工作原理有了更好的理解。

0
相关文章