RPC- oder Remote -Verfahrensanruf besteht nur dazu, es einfach auszudrücken: Das Aufrufen von Diensten auf Remote -Computern ist genau wie das Aufrufen lokaler Dienste.
RPC kann auf dem HTTP- oder TCP -Protokoll basieren. Webdienst ist ein RPC, das auf dem HTTP -Protokoll basiert. Es hat eine gute plattformübergreifende Leistung, aber seine Leistung ist nicht so gut wie RPC auf der Grundlage des TCP-Protokolls. Zwei Aspekte beeinflussen direkt die Leistung von RPC, eine ist die Übertragungsmethode und die andere ist die Serialisierung.
Wie wir alle wissen, ist TCP das Transportschichtprotokoll, HTTP ist das Protokoll für Anwendungsschicht, und die Transportschicht befindet sich mehr unter der Anwendungsschicht. In Bezug auf die Datenübertragung ist die untere Schicht schneller. Daher muss TCP im Allgemeinen schneller sein als HTTP. In Bezug auf die Serialisierung stellt Java die Standard -Serialisierungsmethode bereit, aber im Fall einer hohen Parallelität bringt diese Methode einige Leistungs Engpässe mit sich, sodass auf dem Markt eine Reihe hervorragender Serialisierungsrahmen entstanden sind, wie z.
Um eine hohe Parallelität zu unterstützen, ist das traditionelle Blockierungs -IO offensichtlich nicht geeignet, daher brauchen wir asynchrone IO, d. H. NIO. Java bietet NIO -Lösungen und Java 7 bietet auch eine bessere NIO.2 -Unterstützung. Die Implementierung von NIO mit Java ist keine entfernte Sache, aber wir müssen mit den technischen Details von NIO vertraut sein.
Wir müssen in einer verteilten Umgebung Dienste auf verschiedenen Knoten bereitstellen. Durch die Dienstregistrierung kann der Kunde automatisch die derzeit verfügbaren Dienste ermitteln und diese Dienste anrufen. Dies erfordert eine Serviceregistrierungskomponente, um alle Serviceadressen (einschließlich: Hostname und Portnummer) in einer verteilten Umgebung zu registrieren.
Die Beziehung zwischen Anwendung, Service und Servicemittel ist in der folgenden Abbildung dargestellt:
Auf jedem Server können mehrere Dienste veröffentlicht werden. Diese Dienste teilen sich einen Host und einen Port. In einer verteilten Umgebung wird der Server zur gemeinsamen Bereitstellung von Diensten bereitgestellt. Um zu verhindern, dass ein einzelner Ausfallspunkt des Servicegisteriums in einer Cluster -Umgebung eingebaut werden muss.
In diesem Artikel wird der spezifische Prozess der Entwicklung eines leichten verteilten RPC -Frameworks angezeigt. Dieses Framework basiert auf dem TCP -Protokoll, bietet NIO -Funktionen, bietet effiziente Serialisierungsmethoden und bietet auch die Möglichkeit, Dienste zu registrieren und zu entdecken.
Nach den oben genannten technischen Anforderungen können wir die folgende Technologieauswahl verwenden:
Für verwandte Maven -Abhängigkeiten finden Sie im letzten Anhang.
Schritt 1: Schreiben Sie eine Serviceschnittstelle
öffentliche Schnittstelle HelloService {String Hello (String -Name);}Legen Sie diese Schnittstelle zur Verwendung in ein eigenständiges Client -Jar -Paket.
Schritt 2: Schreiben Sie die Implementierungsklasse der Serviceschnittstelle
@RpcService (HelloService.class) // Geben Sie die Remote -Schnittstelle public class helloServiceImpl an. + Name; }}
Verwenden Sie die RPCService -Annotation, um die Implementierungsklasse der Serviceschnittstelle zu definieren. Sie müssen eine Remote -Schnittstelle für die Implementierungsklasse angeben, da die Implementierungsklasse möglicherweise mehrere Schnittstellen implementiert, sodass Sie das Framework mit der Remote -Schnittstelle mitteilen müssen.
Der RPCService -Code lautet wie folgt:
@Target ({elementtype.type})@retention(retentionPolicy.Runtime)@component // Zeigt an, dass es von Spring public @Interface rpcService {class <?> Value ();} gescannt werden kannDiese Annotation hat die Eigenschaften der Annotation des Frühlingskomponenten und kann durch den Frühling gescannt werden.
Diese Implementierungsklasse wird im Server -JAR -Paket platziert, das auch einige Serverkonfigurationsdateien und Bootstrap -Programme zum Starten des Dienstes enthält.
Schritt 3: Konfigurieren Sie den Server
Die Server -Spring -Konfigurationsdatei heißt Spring.xml und der Inhalt lautet wie folgt:
<beans ...> <context: component-scan base-package = "com.xxx.rpc.Sampel value = "$ {registry.address}"/> </bean> <!-RPC-Server konfigurieren-> <bean id = "rpcserver"> <constructor-arg name = "serveraddress" value = "$ {server.address}"/> <constructor-arg name = "serviceregistry"/ban >-Die spezifischen Konfigurationsparameter befinden sich in der Datei config.Properties, und der Inhalt lautet wie folgt:
# Zookeeper Serverregistrierung.Address = 127.0.0.1: 2181# RPC Server.address = 127.0.0.1: 8000
Die obige Konfiguration zeigt an, dass der lokale Zookeeper -Server verbunden ist und der RPC -Dienst auf Port 8000 freigegeben wird.
Schritt 4: Starten Sie den Server und veröffentlichen Sie den Dienst
Um Spring -Konfigurationsdateien für die Veröffentlichung eines Dienstes zu laden, schreiben Sie einfach einen Bootloader:
public class rpcbootstrap {public static void main (String [] args) {new classPlaPlApplicationContext ("Spring.xml"); }}Führen Sie die Hauptmethode der RPCbootstrap -Klasse aus, um den Server zu starten. Es gibt jedoch zwei wichtige Komponenten, die noch nicht implementiert wurden, nämlich: Serviceregistry und RPCServer. Die spezifischen Implementierungsdetails werden unten angegeben.
Schritt 5: Implementierung der Serviceregistrierung
Die Serviceregistrierungsfunktion kann einfach mit dem Zookeeper -Client implementiert werden. Der Serviceregistry -Code lautet wie folgt:
public class serviceregistry {private static final logger logger = loggerfactory.getLogger (serviceregistry.class); Private Countdownlatch Latch = New Countdownlatch (1); private String registryAddress; public serviceregistry (String registryAddress) {this.registryAddress = RegistryAddress; } public void Register (String -Daten) {if (data! = null) {zookeeper zk = connectServer (); if (zk! = null) {createNode (zk, data); }}} private zookeeper ConnectServer () {zookeeper zk = null; Versuchen Sie {zk = neu zookeeper (RegistryAddress, Constant.zk_Session_timeout, New Watcher () {@Override public void prozess (angesehene Ereignis) {if (Event.getState () == Ereignis.KeeperState.syncconnected) {latch.countdown ();}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}; latch.aait (); } catch (ioException | interruptedException e) {logger.Error ("", e); } return zk; } private void createNode (Zookeeper ZK, String Data) {try {byte [] bytes = data.getBytes (); String path = zk.create (constant.zk_data_path, bytes, zoodefs.ids.open_acl_unsafe, createMode.ephemeral_sequential); Logger.debug ("Zookeeper -Knoten erstellen ({} => {})", Pfad, Daten); } catch (keeteRexception | interruptedException e) {logger.Error ("", e); }}}Unter ihnen sind alle Konstanten durch Konstante konfiguriert:
öffentliche Schnittstelle Konstante {int zk_session_timeout = 5000; String zk_registry_path = "/Registrierung"; String zk_data_path = zk_registry_path + "/data";}Hinweis: Zunächst müssen Sie die Befehlszeile von Zookeeper Client verwenden, um dauerhafte Knoten zu erstellen/zu registrieren, um alle temporären Dienstknoten zu speichern.
Schritt 6: Implementieren Sie den RPC -Server
Mit Netty kann ein RPC -Server implementiert werden, der NIO unterstützt. Sie müssen die Serviceregistry verwenden, um die Serviceadresse zu registrieren. Der RPCServer -Code lautet wie folgt:
public class rpcserver implementiert applicationContextaware, initialisierungsbean {private static final logger logger = loggerfactory.getLogger (rpcserver.class); private String serveraddress; private Serviceregistry Serviceregistry; private map <String, Objekt> HandleMap = New HashMap <> (); // Speichern Sie die Zuordnungsbeziehung zwischen dem Schnittstellennamen und dem Service -Objekt Public RPCServer (String ServerAddress) {this.ServerAddress = ServeradDress; } public RPCServer (String ServerAddress, Serviceregistry Serviceregistry) {this.serveraddress = serveraddress; this.serviceregistry = serviceregistry; } @Override public void setApplicationContext (applicationContext ctx) löst beansexception {map <String, Object> serviceBeanMap = ctx.getBeanWithannotation (rpcService.class) aus; // Erhalten Sie alle Federn mit RPCService -Annotationen bean if (maputils.isnotEmpty (serviceBeanMap)) {für (Object ServiceBean: serviceBeanMap.Values ()) {String interfacename = serviceBean.getClass (). HandleMap.put (Interfacename, ServiceBean); }}} @Override public void AfterPertieStieSt () löst eine Ausnahme aus {EventLoopGroup bosgruppe = new NioeVentLoopGroup (); EventLoopGroup WorkerGroup = new NioeVentLoopGroup (); versuche {serverbootstrap bootstrap = new Serverbootstrap (); bootstrap.group (Bossgroup, WorkerGroup) .Channel (NioServerSocketchannel.Class) .ChildHandler (neuer ChannelInitializer <Socketchannel> () {@override public void Initchannel (Socketchannel -Kanal) wirft Ausnahme {Channel.pipeline (). RPC -Anfrage (um die Anforderung zu behandeln) .Addlast (neuer RPCEncoder (RPCResponse.class)) // Die RPC -Antwort zurückgibt (um die Antwort zurückzugeben) .Addlast (neuer rpchandler (HandleMap); String [] array = serveraddress.Split (":"); String host = Array [0]; int port = Integer.ParseInt (Array [1]); ChannelelfUture Future = Bootstrap.bind (Host, Port) .Sync (); Logger.debug ("Server auf Port {}", Port); if (serviceregistry! = null) {serviceregistry.register (serveradDresse); // Service -Adresse} future.channel (). Closefuture (). Sync (); } endlich {WorkerGroup.ShutdownGracefully (); bossGroup.Shutdowngraceduty (); }}}Im obigen Code müssen zwei wichtige Pojos beschrieben werden, nämlich RPCrequest und RPCresponse.
Verwenden Sie RPCRequest, um RPC -Anforderungen zu verkapseln. Der Code lautet wie folgt:
public class rpcrequest {private String requestId; private String -Klassenname; private String methodName; private Klasse <?> [] Parameterypes; privates Objekt [] Parameter; // Getter/Setter ...}Verwenden Sie RPCresponse, um die RPC -Antwort zu verringern. Der Code lautet wie folgt:
public class rpCresponse {private String requestId; privater Throwable -Fehler; Ergebnis des privaten Objekts; // Getter/Setter ...}Verwenden Sie RPCDeCoder, um die RPC -Dekodierung bereitzustellen. Erweitern Sie einfach Netty's BytetomessAnedCoder abstrakter Klassen -Decodemethode, der Code lautet wie folgt:
öffentliche Klasse RPCDeCoder erweitert BytetomessAnedEcoder {private class <?> GenericClass; public rpcDecoder (class <?> genericClass) {this.genericclass = GenericClass; } @Override public void decode (ChannelHandlerContext ctx, bytebuf in, list <Ontject> out) löst eine Ausnahme aus {if (in.readableBytes () <4) {return; } in.markreaderIndex (); int datalength = in.readint (); if (datalength <0) {ctx.close (); } if (in zurückkehren; } byte [] data = new byte [datalength]; in.readBytes (Daten); Objekt obj = SerializationUtil.Deserialize (Daten, GenerikaClass); out.add (obj); }}Verwenden Sie RPCECODER, um die RPC -Codierung bereitzustellen. Erweitern Sie einfach Netty's MessAgetobyteenCoder Abstract Class -Encode -Methode, der Code lautet wie folgt:
public class rpcencoder erweitert MessAgetObyTeCoder {private class <?> GenericClass; public rpcencoder (class <?> genericClass) {this.genericclass = genericClass; } @Override public void code (ChannelHandlerContext CTX, Objekt in, bytebuf out) löst eine Ausnahme aus {if (genericClass.issinstance (in)) {byte [] data = SerializationUtil.serialize (in); out.writeInt (Data.length); out.writebytes (Daten); }}}Schreiben Sie eine SerializationUtil -Werkzeugklasse und verwenden Sie Protostuff, um die Serialisierung zu implementieren:
öffentliche Klasse SerializationUtil {private statische Karte <Klasse <?>, Schema <>> cachedschema = new ConcurrentHasMap <> (); private statische Oblenesis Obsenesis = Neue Obgelssestd (wahr); private serializationUtil () {} @SuppressWarnings ("deaktiviert") Private static <T> Schema <T> getSchema (Klasse <T> cls) {Schema <T> Schema = (Schema <T>) Cachedschema.get (Cls); if (schema == null) {schema = runtimeschema.createFrom (cls); if (schema! = null) {cachedschema.put (cls, schema); }} Rückgabeschema; } @SuppressWarnings ("deaktiviert") public static <T> byte [] serialize (t obj) {class <T> cls = (class <t>) obj.getClass (); LinkedBuffer buffer = linkedBuffer.AllCode (linkedBuffer.default_buffer_size); Versuchen Sie {Schema <T> Schema = GetSchema (CLS); return protostuffioutil.tobytearray (OBJ, Schema, Puffer); } catch (Ausnahme E) {neue IllegalStateException werfen (e.getMessage (), e); } endlich {buffer.clear (); }} public static <t> t Deserialize (Byte [] Daten, Klasse <T> cls) {try {t message = (t) obsenesis.newinstance (cls); Schema <T> Schema = GetSchema (CLS); Protostuffioutil.mergeFrom (Daten, Nachricht, Schema); Meldung zurückgeben; } catch (Ausnahme E) {neue IllegalStateException werfen (e.getMessage (), e); }}}Das obige verwendet Objenesis, um Objekte zu instanziieren, was mächtiger ist als die Java -Reflexion.
HINWEIS: Wenn Sie andere Serialisierungsrahmen ersetzen müssen, ändern Sie einfach die SerializationUtil. Eine bessere Möglichkeit, es besser zu implementieren, besteht darin, Konfigurationselemente bereitzustellen, um zu entscheiden, welche Serialisierungsmethode verwendet werden soll.
Um RPC -Anfragen in RPchandler zu bearbeiten, müssen Sie nur die SimplechannelinBoundHandler -Abstract -Klasse von Netty erweitern. Der Code lautet wie folgt:
public class rpchandler erweitert simplechannelinBoundHandler <rpcrequest> {private static Final Logger logger = loggerfactory.getLogger (rpchandler.class); private endgültige Karte <String, Objekt> HandleMap; public rpchandler (map <string, Objekt> HandleMap) {this.Handlermap = HandleMap; } @Override public void ChannelRead0 (Final ChannelHandlerContext CTX, RPCRequest -Anforderung) löst eine Ausnahme aus {rpCresponse response = new rpCresponse (); response.setRequestID (request.getRequestid ()); try {Object ergebnis = handle (request); Antwort.SetResult (Ergebnis); } catch (throwable t) {response.setError (t); } ctx.writeAndFlush (Antwort) .AddListener (ChannelelfUTurelistener.CLOSE); } Private Object -Handle (rpcrequest request) löst Throwable {String className = request.getClassName () aus; Object ServiceBean = HandleMap.get (KlasseName); Klasse <?> ServiceClass = serviceBean.getClass (); String methodname = request.getMethodname (); Class <?> [] ParameterTypes = request.getParameterTypes (); Object [] parameter = request.getParameters (); /*Method method = serviceClass.getMethod (methodName, parameterypes); method.setAccessible (true); return methode.invoke (serviceBean, parameters);*/ fastClass ServicefastClass = fastClass.create (serviceClass); FastMethod serviceFastMethod = serviceFastClass.getMethod (methodName, parameterTypes); return ServiceFastMethod.invoke (ServiceBean, Parameter); } @Override public void exceptioncaught (ChannelHandlerContext CTX, Throwable Cause) {Logger.Error ("Server -Catch -Ausnahme", Ursache); ctx.close (); }}Um die Leistungsprobleme zu vermeiden, die durch die Verwendung von Java -Reflexion verursacht werden, können wir die von CGLIB bereitgestellte Reflexions -API wie FastClass und Fastmethod verwenden.
Schritt 7: Konfigurieren Sie den Client
Verwenden Sie auch Spring -Konfigurationsdateien, um den RPC -Client zu konfigurieren. Der Code für Spring.xml lautet wie folgt:
<beans ...> <context: Property-Placeholder-Standort = "classPath: config.Properties"/> <!-Configure Service Discovery Component-> <bean id = "servicediscovery"> <constructor-arg name = "RegistryAddress" value = "$ {Registrierung.Address}"/> </lean> configure rpc proxy- id = "rpcproxy"> <constructor-arg name = "servicediscovery" Ref = "Servicediscovery"/> </bean> </beans>config.Properties liefert eine spezifische Konfiguration:
# Zookeeper Serverregistrierung.Address = 127.0.0.1: 2181
Schritt 8: Implementierung der Service Discovery
Verwenden Sie auch Zookeeper, um die Funktion zur Erkennung von Dienstleistungen zu implementieren. Siehe folgenden Code:
öffentliche Klasse bedient Iscovery {private statische endgültige Logger -Logger = loggerfactory.getLogger (Servicediscovery.class); Private Countdownlatch Latch = New Countdownlatch (1); private volatile list <string> datalist = new ArrayList <> (); private String registryAddress; public Servicediscovery (String RegistryAddress) {this.registryAddress = RegistryAddress; Zookeeper ZK = ConnectServer (); if (zk! = null) {watchnode (zk); }} public String Discover () {String data = null; int size = datalist.size (); if (size> 0) {if (size == 1) {data = datalist.get (0); Logger.debug ("nur Daten verwenden: {}", Daten); } else {data = datalist.get (threadLocalrandom.current (). NextInt (Größe)); Logger.debug ("mit zufälligen Daten verwenden: {}", Daten); }} Daten zurückgeben; } private zookeeper ConnectServer () {Zookeeper zk = null; try {zk = neu zookeeper (RegistryAddress, Constant.zk_Session_timeout, New Watcher () {@Override public void process (angesehene Ereignis) {if (event.getState () == Ereignis.KeeperState.syncconnected) {latch.Countdown ();}}}}); latch.aait (); } catch (ioException | interruptedException e) {logger.Error ("", e); } return zk; } private void watchnode (endgültig zookeper zk) {try {list <string> nodelist = zk.getChildren (konstant.zk_registry_path, neuer watcher () {@Override public void process (angesehene Ereignis) {if (Event.GetType () == Event.EventType.Nodeche. }); Liste <String> datalist = new ArrayList <> (); für (String -Knoten: nodelist) {byte [] bytes = zk.getData (constant.zk_registry_path + "/" + knoten, false, null); Datalist.Add (neue String (Bytes)); } Logger.debug ("Knotendaten: {}", Datalist); this.datalist = datalist; } catch (keeteRexception | interruptedException e) {logger.Error ("", e); }}}Schritt 9: Implementierung des RPC -Agenten
Hier verwenden wir die von Java bereitgestellte dynamische Proxy -Technologie, um RPC -Proxy zu implementieren (natürlich kann sie auch mit CGGLIB implementiert werden). Der spezifische Code lautet wie folgt:
public class rpcproxy {private String serveraddress; privat gewartetes Servicediscovery; public rpcproxy (String serveraddress) {this.serveraddress = serveraddress; } public rpcProxy (bedient Iscovery Servicediscovery) {this.servicediscovery = Servicediscovery; } @SuppressWarnings("unchecked") public <T> T create(Class<?> interfaceClass) { return (T) Proxy.newProxyInstance( interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throwable {rpcrequest request = new rpcrequest (); setParameterTypes (Methode Integer.ParseInt (Array [1]); } else {return response.getResult (); }}}); }}Um den RPC -Client mithilfe der RPCClient -Klasse zu implementieren, müssen Sie nur die von Netty bereitgestellte SimpleChannelinboundHandler -abstrakte Klasse erweitern. Der Code lautet wie folgt:
public class RPCClient erweitert SimplyChannelinBoundHandler <RPCResponse> {private statische endgültige logger logger = loggerfactory.getLogger (rpcclient.class); privater String -Host; privater Int -Port; Private RPCresponse -Antwort; privates endgültiges Objekt obj = neues Objekt (); public rpcclient (string host, int port) {this.host = host; this.port = port; } @Override public void ChannelRead0 (ChannelHandlerContext CTX, RPCResponse -Antwort) löst eine Ausnahme aus {this.Response = Antwort; synchronisiert (obj) {obj.notifyallAll (); // Erhalten Sie eine Antwort, wecken Sie den Thread}} @Override public void exceptioncaught (ChannelHandlerContext CTX, Throwable Cause) Ausnahme {Logger.Error ("Client -Catch -Ausnahme", Ursache); ctx.close (); } public rpCresponse send (rpcrequest request) löst eine Ausnahme aus {EventLoopGroup Group = new NioeVentLoopGroup (); versuche {bootstrap bootstrap = new bootstrap (); bootstrap.group (Gruppe) .Channel (niosocketchannel.class) .Handler (neuer ChannelInitializer <Socketchannel> () {@Override public void Initchannel (Socketchannel -Kanal) löst eine Ausnahme aus {Channel.pipeline () .addlast (neue rpcencoder (rpcrequest.classe). .Addlast (neuer RPCDeCoder (RPCResponse.Class) // Die RPC -Antwort (um die Antwort zu verarbeiten) .Addlast (rpcclient.this); ChannelelfUture Future = Bootstrap.Connect (Host, Port) .Sync (); Future.Channel (). WriteAndFlush (Anfrage) .Sync (); synchronisiert (obj) {obj.wait (); // Es wurde keine Antwort empfangen, wodurch der Thread wartet} if (Antwort! = Null) {future.channel (). Closefuture (). Sync (); } Rückgabeantwort; } endlich {Group.Shutdowngracefully (); }}}Schritt 10: RPC -Anfrage senden
Verwenden Sie JUNIT, um einen Unit -Test in Kombination mit Spring mit dem folgenden Code zu schreiben:
@Runwith (SpringJunit4ClassRunner.class) @ContextConfiguration (Locations = "ClassPath: Spring.xml") Public Class HelloServicetest {@autowired Private rpcproxy rpcproxy; @Test public void Hellotest () {HelloService HelloService = rpcproxy.create (HelloService.class); String result = helloService.hello ("Welt"); Assert.AsSertequals ("Hallo! Welt", Ergebnis); }}Führen Sie die oben genannten Unit -Tests aus, und wenn nichts Unerwartetes passiert, sollten Sie die grüne Balken sehen.
Zusammenfassen
Dieser Artikel implementiert ein leichtes RPC -Framework durch Spring + Netty + Protostuff + Zookeeper. Es verwendet Spring, um Abhängigkeitsinjektion und Parameterkonfiguration bereitzustellen, Netty zur Implementierung der NIO -Datenübertragung, verwendet Protostuff, um die Serialisierung der Objekte zu implementieren, und verwendet Zookeeper, um die Dienstregistrierung und die Erkennung von Dienstleistungen zu implementieren. Mit diesem Framework können Dienste auf jedem Knoten in einer verteilten Umgebung bereitgestellt werden. Der Client ruft die spezifische Implementierung des Servers über eine Remote-Schnittstelle auf, wodurch die Entwicklung des Servers und des Clients vollständig getrennt wird und grundlegende Unterstützung für die Implementierung großer Verteilungsanwendungen bietet.
Anhang: Maven -Abhängigkeit
<!-- JUnit --><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope></dependency><!-- SLF4J --><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version> 1.7.7 </Version> </abhängig> <!-Spring-> <De vorstellen> <gruppe> org.springFramework </Groupid> <artifactId> Spring-Context </artifactID> <version> 3.2.12.release </rechnen> </abhängig> <gruppe <GroupId> org.SpringFrameWorte </gruppen </gruppen> <gruppe org. <artifactId> Spring-Test </artifactId> <version> 3.2.12.Release </Version> <Scope> Test </scope> </abhängig> <!-netty-> <depectIn> <gruppe> IO.NETTY </GroupId> <artifactId> netty-ALL </artifactid> 4.0.24.fininal </fusion> </artifactId> 4.0.24.fininale </fusion> </artifactid> 4.0.2. <gruppe> com.dyuProject.Protostuff </GroupId> <artifactID> Protostuff-Core </artifactId> <version> 1.0.8 </Version> </abhängig> <!-Zookeeper-> <De vorangetrieben> <GroupId> org.apache <version> 3.4.6 </Version> </abhängig> <!-Apache Commons-Sammlungen-> <Depopentcy> <gruppe> org.apache.commons </Groupid> <artifactID> Commons-collections4 </artifactid> <version> 4.0 </Version> </abhängig> <! <artifactId> obfenesis </artifactId> <version> 2.1 </Version> </abhängig> <!-cglib-> <depeaponcy> <Groupid> cglib </gruppen> <artifactid> cglib </artifactid> <version> 3.1 </Version> </abhängig> </abhängig>