Entwerfen Sie ein einfaches serviceorientiertes J2EE-Anwendungsframework
Heutzutage werden Entwickler mit Open-Source-Frameworks überschwemmt, die bei der J2EE-Programmierung helfen: Struts, Spring, Hibernate, Tiles, Avalon, WebWorks, Tapestry oder Oracle ADF, um nur einige zu nennen. Viele Entwickler stellen fest, dass diese Frameworks nicht das Allheilmittel für ihre Probleme sind. Nur weil sie Open Source sind, heißt das nicht, dass sie leicht zu ändern und zu verbessern sind. Wenn ein Framework in einem Schlüsselbereich zu kurz kommt, nur eine bestimmte Domäne anspricht oder nur aufgebläht und zu teuer ist, müssen Sie möglicherweise ein eigenes Framework darauf aufbauen. Das Erstellen eines Frameworks wie Struts ist eine nicht triviale Aufgabe. Die schrittweise Entwicklung eines Frameworks, das Struts und andere Frameworks nutzt, muss jedoch nicht erforderlich sein.
In diesem Artikel zeige ich Ihnen, wie Sie X18p (Xiangnong 18 Palm, benannt nach einem legendären leistungsstarken Kung-Fu-Kämpfer) entwickeln, ein Beispielframework, das zwei häufig auftretende Probleme behebt, die von den meisten J2EE-Frameworks ignoriert werden: enge Kopplung und aufgeblähtes DAO (Datenzugriffsobjekt) Code. Wie Sie später sehen werden, nutzt X18p Struts, Spring, Axis, Hibernate und andere Frameworks auf verschiedenen Ebenen. Hoffentlich können Sie mit ähnlichen Schritten Ihr eigenes Framework mühelos rollen und von Projekt zu Projekt erweitern.
Der Ansatz, den ich bei der Entwicklung dieses Frameworks verfolge, verwendet Konzepte aus IBMs Rational Unified Process (RUP). Ich folge diesen Schritten:
- Setzen Sie sich zunächst einfache Ziele
- Analysieren Sie die vorhandene J2EE-Anwendungsarchitektur und identifizieren Sie die Probleme
- Vergleichen Sie alternative Frameworks und wählen Sie das aus, mit dem Sie am einfachsten erstellen können
- Entwickeln Sie Code schrittweise und überarbeiten Sie ihn häufig
- Treffen Sie sich mit dem Endbenutzer des Frameworks und sammeln Sie regelmäßig Feedback
- Test, Test, Test
Schritt 1. Setzen Sie sich einfache Ziele
Es ist verlockend, ehrgeizige Ziele zu setzen und ein innovatives Framework zu implementieren, das alle Probleme löst. Wenn Sie über ausreichende Ressourcen verfügen, ist dies keine schlechte Idee. Im Allgemeinen wird die Entwicklung eines Frameworks im Voraus für Ihr Projekt als Overhead betrachtet, der keinen greifbaren Geschäftswert bietet. Wenn Sie kleiner anfangen, können Sie die unvorhergesehenen Risiken verringern, weniger Entwicklungszeit genießen, die Lernkurve verkürzen und das Buy-in der Projektbeteiligten erhalten. Für X18p habe ich nur zwei Ziele festgelegt, die auf meinen früheren Begegnungen mit J2EE-Code basieren:
- Reduzieren Sie die J2EE
Action
-Codekopplung - Reduzieren Sie die Code-Wiederholung auf der J2EE DAO-Ebene
Insgesamt möchte ich Code mit besserer Qualität bereitstellen und die Gesamtkosten für Entwicklung und Wartung senken, indem ich meine Produktivität erhöhe. Damit durchlaufen wir zwei Iterationen der Schritte 2 bis 6, um diese Ziele zu erreichen.
Codekopplung reduzieren
Schritt 2. Analysieren Sie die vorherige J2EE-Anwendungsarchitektur
Wenn ein J2EE-Anwendungsframework vorhanden ist, müssen wir zuerst sehen, wie es verbessert werden kann. Es macht natürlich keinen Sinn, von vorne zu beginnen. Schauen wir uns für X18p ein typisches Anwendungsbeispiel für J2EE Struts an (siehe Abbildung 1).
Action
Anrufe XXXManager
und XXXManager
Anrufe XXXDAO
s. In einem typischen J2EE-Design mit Streben haben wir folgende Elemente:
HttpServlet
oder eine Struts-Action
Ebene, dieHttpRequest
und behandeltHttpResponse
- Geschäftslogikschicht
- Datenzugriffsschicht
- Domänenebene, die den Domänenentitäten zugeordnet ist
Was ist falsch an der obigen Architektur? Die Antwort: enge Kopplung. Die Architektur funktioniert einwandfrei, wenn die Logik Action
einfach ist. Was aber, wenn Sie auf viele EJB-Komponenten (Enterprise JavaBeans) zugreifen müssen? Was ist, wenn Sie aus verschiedenen Quellen auf Webdienste zugreifen müssen? Was ist, wenn Sie auf JMX (Java Management Extensions) zugreifen müssen? Verfügt Struts über ein Tool, mit dem Sie diese Ressourcen in der struts-config.xml
Datei nachschlagen können ? Die Antwort ist nein. Struts soll ein Nur-Web-Tier-Framework sein. Es ist möglich, Action
s als verschiedene Clients zu codieren und das Back-End über das Service Locator-Muster aufzurufen. Dabei werden jedoch zwei verschiedene Codetypen in Action
der execute()
Methode gemischt.
Der erste Codetyp bezieht sich auf die Webschicht HttpRequest
/ HttpResponse
. Beispielsweise ruft Code HTTP-Formulardaten von ActionForm
oder ab HttpRequest
. Sie haben auch Code, der Daten in einer HTTP-Anforderung oder HTTP-Sitzung festlegt und zur Anzeige an eine JSP-Seite (JavaServer Pages) weiterleitet.
Der zweite Codetyp bezieht sich jedoch auf die Geschäftsschicht. In Action
rufen Sie auch Backend-Code auf, z. B. EJBObject
ein JMS-Thema (Java Message Service) oder sogar JDBC-Datenquellen (Java Database Connectivity), und rufen die Ergebnisdaten aus den JDBC-Datenquellen ab. Sie können das Service Locator-Muster verwenden Action
, um die Suche zu erleichtern. Es ist auch möglich Action
, nur auf ein lokales POJO (einfaches altes Java-Objekt) zu verweisen xxxManager
. Trotzdem xxxManager
sind Signaturen auf Backend-Objekt- oder Methodenebene verfügbar Action
.
So Action
funktioniert das, oder? Es Action
handelt sich um ein Servlet, das sich darum kümmern soll, wie Daten aus HTML aufgenommen und mit einer HTTP-Anforderung / Sitzung in HTML umgewandelt werden. Es ist auch eine Schnittstelle zur Geschäftslogikschicht, um Daten von dieser Schicht abzurufen oder zu aktualisieren, aber in welcher Form oder in welchem Protokoll Action
könnte es weniger interessieren.
Wie Sie sich vorstellen können, kann es beim Wachstum einer Struts-Anwendung zu engen Verweisen zwischen Action
s (Webschicht) und Geschäftsmanagern (Geschäftsschicht) kommen (siehe die roten Linien und Pfeile in Abbildung 1).
Um dieses Problem zu lösen, können wir die offenen Rahmenbedingungen auf dem Markt berücksichtigen - lassen Sie sie unser eigenes Denken anregen, bevor wir etwas bewirken. Spring Framework kommt auf meinem Radarschirm.
Schritt 3. Vergleichen Sie alternative Frameworks
Der Kern von Spring Framework ist ein Konzept namens BeanFactory
, das eine gute Implementierung der Lookup-Factory darstellt. Es unterscheidet sich vom Service Locator-Muster dadurch, dass es über eine IoC-Funktion (Inversion-of-Control) verfügt, die zuvor als Injection Dependency bezeichnet wurde . Die Idee ist , ein Objekt zu erhalten , indem Ihr Aufruf ApplicationContext
‚s - getBean()
Methode. Diese Methode sucht in der Spring-Konfigurationsdatei nach Objektdefinitionen, erstellt das Objekt und gibt ein java.lang.Object
Objekt zurück. getBean()
ist gut für Objektsuchen. Es scheint, dass nur eine Objektreferenz in der referenziert ApplicationContext
werden muss Action
. Dies ist jedoch nicht der Fall, wenn wir es direkt in verwenden Action
, da wir den getBean()
Rückgabeobjekttyp an den EJB / JMX / JMS / Web-Service-Client zurückgeben müssen.Action
muss immer noch das Backend-Objekt auf Methodenebene kennen. Es besteht noch eine enge Kopplung.
Was können wir noch verwenden, wenn wir eine Referenz auf Objektmethodenebene vermeiden möchten? Natürlich fällt mir der Service ein . Service ist ein allgegenwärtiges, aber neutrales Konzept. Alles kann ein Dienst sein, nicht unbedingt nur die sogenannten Webdienste. Action
kann die Methode einer zustandslosen Session-Bean auch als Service behandeln. Das Aufrufen eines JMS-Themas kann auch das Konsumieren eines Dienstes behandeln. Die Art und Weise, wie wir einen Service nutzen, kann sehr allgemein sein.
Mit einer Strategie, die aus den obigen Analysen und Vergleichen formuliert, Gefahren erkannt und Risiken gemindert wird, können wir unsere Kreativität anregen und eine dünne Service-Broker-Schicht hinzufügen, um das serviceorientierte Konzept zu demonstrieren.
Schritt 4. Entwickeln und umgestalten
Um das serviceorientierte Konzeptdenken in Code umzusetzen, müssen wir Folgendes berücksichtigen:
- Die Service Broker-Schicht wird zwischen der Webschicht und der Geschäftsschicht hinzugefügt.
- Konzeptionell
Action
ruft ein nur eine Geschäftsdienstanforderung auf, die die Anforderung an einen Dienstrouter weiterleitet. Der Service-Router kann Geschäftsdienstanforderungen an verschiedene Controller oder Adapter von Service Providern anschließen, indem er eine XML-Datei für die Servicezuordnung aufruftX18p-config.xml
. - The service provider controller has specific knowledge of finding and invoking the underlying business services. Here, business services could be anything from POJO, LDAP (lightweight directory access protocol), EJB, JMX, COM, and Web services to COTS (commercial off the shelf) product APIs.
X18p-config.xml
should supply sufficient data to help the service provider controller get the job done. - Leverage Spring for X18p's internal object lookup and references.
- Build service provider controllers incrementally. As you will see, the more service provider controllers implemented, the more integration power X18p has.
- Protect existing knowledge such as Struts, but keep eyes open for new things coming up.
Now, we compare the Action
code before and after applying the service-oriented X18p framework:
Struts Action without X18p
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException { ... UserManager userManager = new UserManager(); String userIDRetured = userManager.addUser("John Smith") ... }
Struts Action with X18p
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 supports lookups to the business service request and other objects, including POJO managers, if any.
Figure 2 shows how the Spring configuration file, applicationContext.xml
, supports the lookup of businessServiceRequest
and serviceRouter
.

In ServiceRequest.java
, the service()
method simply calls Spring to find the service router and passes itself to the router:
public Object service() { return ((ServiceRouter) this.serviceContext.getBean("service router")).route(this); }
The service router in X18p routes user services to the business logic layer with X18p-config.xml
's help. The key point is that the Action
code doesn't need to know where or how user services are implemented. It only needs to be aware of the rules for consuming the service, such as pushing the parameters in the correct order and casting the right return type.
Figure 3 shows the segment of X18p-config.xml
that provides the service mapping information, which ServiceRouter
will look up in X18p.

For user services, the service type is POJO. ServiceRouter
creates a POJO service provider controller to handle the service request. This POJO's springObjectId
is userServiceManager
. The POJO service provider controller uses Spring to look up this POJO with springObjectId
. Since userServiceManager
points to class type X18p.framework.UserPOJOManager
, the UserPOJOManager
class is the application-specific logic code.
Examine 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 { //TODO 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; }
The above routing if-else block could be refactored into a Command pattern. The Config
object provides the Spring and X18p XML configuration lookup. As long as valid data can be retrieved, it's up to you how to implement the lookup mechanism.
Assuming a POJO manager, TestPOJOBusinessManager
, is implemented, the POJO service provider controller (POJOServiceController.java
) then looks for the addUser()
method from the TestPOJOBusinessManager
and invokes it with reflection (see the code available from Resources).
By introducing three classes (BusinessServiceRequester
, ServiceRouter
, and ServiceProviderController
) plus one XML configuration file, we have a service-oriented framework as a proof-of-concept. Here Action
has no knowledge regarding how a service is implemented. It cares about only input and output.
Die Komplexität der Verwendung verschiedener APIs und Programmiermodelle zur Integration verschiedener Dienstanbieter wird von Struts-Entwicklern abgeschirmt, die auf der Webebene arbeiten. Wenn X18p-config.xml
Struts und Backend-Entwickler im Voraus als Servicevertrag konzipiert wurden, können sie vertraglich gleichzeitig arbeiten.
Abbildung 4 zeigt das neue Erscheinungsbild der Architektur.
In Tabelle 1 habe ich die gängigen Controller und Implementierungsstrategien für Dienstanbieter zusammengefasst. Sie können problemlos weitere hinzufügen.
Tabelle 1. Implementierungsstrategien für gängige Dienstanbieter-Controller