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:

  1. Setzen Sie sich zunächst einfache Ziele
  2. Analysieren Sie die vorhandene J2EE-Anwendungsarchitektur und identifizieren Sie die Probleme
  3. Vergleichen Sie alternative Frameworks und wählen Sie das aus, mit dem Sie am einfachsten erstellen können
  4. Entwickeln Sie Code schrittweise und überarbeiten Sie ihn häufig
  5. Treffen Sie sich mit dem Endbenutzer des Frameworks und sammeln Sie regelmäßig Feedback
  6. 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:

  1. Reduzieren Sie die J2EE Action-Codekopplung
  2. 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).

ActionAnrufe XXXManagerund XXXManagerAnrufe XXXDAOs. In einem typischen J2EE-Design mit Streben haben wir folgende Elemente:

  • HttpServletoder eine Struts- ActionEbene, die HttpRequestund 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 Actioneinfach 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.xmlDatei nachschlagen können ? Die Antwort ist nein. Struts soll ein Nur-Web-Tier-Framework sein. Es ist möglich, Actions als verschiedene Clients zu codieren und das Back-End über das Service Locator-Muster aufzurufen. Dabei werden jedoch zwei verschiedene Codetypen in Actionder execute()Methode gemischt.

Der erste Codetyp bezieht sich auf die Webschicht HttpRequest/ HttpResponse. Beispielsweise ruft Code HTTP-Formulardaten von ActionFormoder 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 Actionrufen Sie auch Backend-Code auf, z. B. EJBObjectein 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 xxxManagersind Signaturen auf Backend-Objekt- oder Methodenebene verfügbar Action.

So Actionfunktioniert das, oder? Es Actionhandelt 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 Actionkönnte es weniger interessieren.

Wie Sie sich vorstellen können, kann es beim Wachstum einer Struts-Anwendung zu engen Verweisen zwischen Actions (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.ObjectObjekt zurück. getBean()ist gut für Objektsuchen. Es scheint, dass nur eine Objektreferenz in der referenziert ApplicationContextwerden 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.Actionmuss 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. Actionkann 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 Actionruft 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 aufruft X18p-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.xmlStruts 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