إن RPC ، أو استدعاء الإجراءات عن بُعد ، هو مجرد وضعها ببساطة: إن الاتصال بالخدمات على أجهزة الكمبيوتر عن بُعد يشبه الاتصال بالخدمات المحلية.
يمكن أن يعتمد RPC على بروتوكول HTTP أو TCP. خدمة الويب هي RPC تعتمد على بروتوكول HTTP. إنه يتمتع بأداء جيد من النظام الأساسي ، لكن أدائها ليس جيدًا مثل RPC استنادًا إلى بروتوكول TCP. سيؤثر جانبان بشكل مباشر على أداء RPC ، أحدهما طريقة الإرسال ، والآخر هو التسلسل.
كما نعلم جميعًا ، TCP هو بروتوكول طبقة النقل ، HTTP هو بروتوكول طبقة التطبيق ، وطبقة النقل أكثر تحت طبقة التطبيق. من حيث نقل البيانات ، تكون الطبقة السفلية أسرع. لذلك ، بشكل عام ، يجب أن يكون TCP أسرع من HTTP. بالنسبة للتسلسل ، توفر Java طريقة التسلسل الافتراضي ، ولكن في حالة التزامن العالي ، ستجلب هذه الطريقة بعض اختناقات الأداء ، لذلك ظهرت سلسلة من أطر التسلسل الممتازة في السوق ، مثل: Protobuf ، Kryo ، Hessian ، Jackson ، وما إلى ذلك ، يمكنها استبدال جافا بتقديم أداء أكثر كفاءة.
لدعم التزامن العالي ، من الواضح أن الحظر التقليدي IO غير مناسب ، لذلك نحن بحاجة إلى IO غير المتزامن ، أي NIO. توفر Java حلول NIO ، ويوفر Java 7 أيضًا دعمًا أفضل لـ Nio.2. إن تطبيق NIO مع Java ليس شيئًا بعيدًا ، لكننا بحاجة إلى أن نكون على دراية بالتفاصيل الفنية لـ NIO.
نحتاج إلى نشر الخدمات على عقد مختلفة في بيئة موزعة ، ومن خلال تسجيل الخدمة ، يمكن للعميل اكتشاف الخدمات المتاحة حاليًا تلقائيًا والاتصال بهذه الخدمات. يتطلب ذلك مكون تسجيل خدمة لتسجيل جميع عناوين الخدمة (بما في ذلك: اسم المضيف ورقم المنفذ) في بيئة موزعة.
يتم عرض العلاقة بين التطبيق والخدمة وسجل الخدمة في الشكل أدناه:
يمكن نشر خدمات متعددة على كل خادم. تشترك هذه الخدمات في مضيف ومنفذ. في بيئة موزعة ، سيتم توفير الخادم لتقديم الخدمات بشكل مشترك. بالإضافة إلى ذلك ، لمنع نقطة فشل سجل الخدمة ، يجب أن تكون مدمجة في بيئة عنقودية.
ستكشف هذه المقالة عن عملية محددة لتطوير إطار RPC الموزع خفيفة الوزن. يعتمد هذا الإطار على بروتوكول TCP ، ويوفر ميزات NIO ، ويوفر طرقًا فعالة للتسلسل ، ولديه أيضًا القدرة على التسجيل واكتشاف الخدمات.
وفقًا للمتطلبات الفنية أعلاه ، يمكننا استخدام اختيار التكنولوجيا التالي:
لتبعيات Maven ذات الصلة ، يرجى الاطلاع على الملحق الأخير.
الخطوة 1: اكتب واجهة خدمة
الواجهة العامة HelloService {String Hello (اسم السلسلة) ؛}ضع هذه الواجهة في حزمة جرة عميل مستقلة للاستخدام.
الخطوة 2: اكتب فئة التنفيذ لواجهة الخدمة
RPCService (HelloService.Class) // حدد الواجهة العامة من الفئة العامة HelloServiceImpl تنفذ HelloService {Override Public String Hello (اسم السلسلة) {return "Hello!" + الاسم ؛ }}استخدم شرح RPCService لتحديد فئة التنفيذ لواجهة الخدمة. تحتاج إلى تحديد واجهة بعيدة لفئة التنفيذ ، لأن فئة التنفيذ قد تنفذ واجهات متعددة ، لذلك يجب أن تخبر الإطار الذي هو الواجهة البعيدة.
رمز RPCService كما يلي:
TARGET ({elementType.type})@الاحتفاظ (reentionepolicy.runtime)@component // يشير إلى أنه يمكن مسحه بواسطة Spring public interface rpcservice {class <؟> value () ؛}يحتوي هذا التعليق على خصائص شرح مكونات الربيع ويمكن مسحها ضوئيًا بحلول الربيع.
يتم وضع فئة التنفيذ هذه في حزمة Server Jar ، والتي توفر أيضًا بعض ملفات تكوين الخادم وبرامج bootstrap لبدء الخدمة.
الخطوة 3: تكوين الخادم
يسمى ملف تكوين Server Spring spring.xml ، والمحتوى كما يلي:
<beans ...> <context: component-scan base-package = "com.xxx.rpc.sample.server"/> <context: property-placeholder location = "classpath: config.properties"/> <! value = "$ {registry.address}"/> </bean> <!-تكوين خادم RPC-> <bean id = "rpcserver"توجد معلمات التكوين المحددة في ملف config.properties ، والمحتوى كما يلي:
# Zookeeper Server Registry.address = 127.0.0.1: 2181# RPC Server.address = 127.0.0.1: 8000
يشير التكوين أعلاه إلى أن خادم Zookeeper المحلي متصل ويتم إصدار خدمة RPC على المنفذ 8000.
الخطوة 4: ابدأ الخادم ونشر الخدمة
لتحميل ملفات تكوين الربيع لنشر خدمة ، ما عليك سوى كتابة أداة تحميل للتمهيد:
الفئة العامة rpcbootstrap {public static void main (string [] args) {new classPathxMlapPlicationContext ("spring.xml") ؛ }}قم بتشغيل الطريقة الرئيسية لفئة RPCbootStrap لبدء الخادم ، ولكن هناك عنصرين مهمين لم يتم تنفيذه بعد ، وهما: ServiceRegistry و RPCServer. سيتم تقديم تفاصيل التنفيذ المحددة أدناه.
الخطوة 5: تنفيذ تسجيل الخدمة
يمكن تنفيذ وظيفة تسجيل الخدمة بسهولة باستخدام عميل ZookeEper. رمز التحليل من الخدمة كما يلي:
serviceRegistry {private static final logger = loggerFactory.getLogger (serviceRegistry.class) ؛ Private Countdownlatch Latch = New CountDownLatch (1) ؛ سلسلة خاصة registryaddress ؛ serviceRegistry (سلسلة registryAddress) {this.registryaddress = registryAddress ؛ } سجل void العام (بيانات السلسلة) {if (data! = null) {zookeeper zk = connectServer () ؛ if (zk! = null) {createNode (zk ، data) ؛ }}} خاص zookeeper connectServer () {zookeeper zk = null ؛ جرب {zk = new ZookeEper (registryAddress ، statter.zk_session_timeout ، new Watcher () {Override Public Void Process (WatchEdevent Event) {if (event.getState () == Event.Severy.SyncConnected) {latch.contdown () ؛}}}}}} latch.await () ؛ } catch (ioException | interruptedException e) {logger.error ("" ، e) ؛ } إرجاع ZK ؛ } private void createNode (Zookeeper ZK ، String Data) {try {byte [] bytes = data.getBytes () ؛ مسار السلسلة = zk.create (ثابت. logger.debug ("إنشاء عقدة zookeeper ({} => {})" ، المسار ، البيانات) ؛ } catch (keeperexception | interruptedException e) {logger.error ("" ، e) ؛ }}}من بينها ، يتم تكوين جميع الثوابت من خلال ثابت:
الواجهة العامة ثابت {int zk_session_timeout = 5000 ؛ string zk_registry_path = "/registry" ؛ string zk_data_path = zk_registry_path + "/data" ؛}ملاحظة: أولاً ، تحتاج إلى استخدام سطر أوامر عميل ZookeEper لإنشاء/تسجيل العقد الدائمة لتخزين جميع العقد المؤقتة للخدمة.
الخطوة 6: تنفيذ خادم RPC
يمكن باستخدام Netty تطبيق خادم RPC يدعم NIO. تحتاج إلى استخدام ServiceRegistry لتسجيل عنوان الخدمة. رمز RPCServer كما يلي:
الفئة العامة RPCServer تنفذ ApplicationContextAware ، PILTICYBEAN {private static final logger = loggerfactory.getLogger (rpcserver.class) ؛ سلسلة خاصة ServerAddress ؛ ServiceRegistry ServiceRegistry ؛ الخريطة الخاصة <string ، Object> handlermap = new HashMap <> () ؛ . } public rpcserver (String serveraddress ، serviceRegistry serviceRegistry) {this.serveraddress = serverAddress ؛ this.ServicEregistry = serviceRegistry ؛ } Override public void setapplicationContext (ApplicationContext CTX) يلقي beansexception {map <string ، object> serviceBeanMap = ctx.getBeanswithannotation (rpcservice.class) ؛ // احصل على جميع الينابيع مع RPCService annotations Bean if (MapUtils.isnotempty (serviceBeanMap)) {for (Object serviceBean: ServiceBeanMap.values ()) {String interfacename = serviceBean.getClass (). getannotation (rpcservice.class). handlermap.put (interfacename ، serviceBean) ؛ }}} Override public void بعد propertiesset () يلقي الاستثناء {eventLoopGroup bossgroup = nioeventloopgroup () ؛ EventLoopGroup WorkerGroup = nioeventloopgroup () جديد ؛ حاول {serverBootStrap bootstrap = new ServerBootStrap () ؛ bootstrap.group (possgroup ، workergroup). القنوات (nioserversocketchannel.class) .ChildHandler (New ChannelInitializer <OcketchAnl> () {Override public void initchannel (socketchannel) rems {channel.pipeline (). طلب RPC (للتعامل مع الطلب). addlast (RPCencoder جديد (rpcresponse.class)) string [] array = serveraddress.split (":") ؛ سلسلة مضيف = صفيف [0] ؛ int port = integer.parseint (Array [1]) ؛ Channelfuture Future = bootstrap.bind (مضيف ، منفذ) .sync () ؛ logger.debug ("بدأ الخادم على المنفذ {}" ، المنفذ) ؛ if (serviceRegistry! = null) {serviceRegistry.register (serverAddress) ؛ // تسجيل عنوان الخدمة} future.channel (). closefuture (). sync () ؛ } أخيرًا {workergroup.shutdowngracely () ؛ bossgroup.shutdownlyly () ؛ }}}في الكود أعلاه ، هناك نوعان من pojos المهمة التي يجب وصفها ، وهما rpcrequest و rpcresponse.
استخدم RPCrequest لتغليف طلبات RPC ، الرمز كما يلي:
الفئة العامة rpcrequest {private String requestId ؛ سلسلة خاصة سلسلة خاصة methodname ؛ فئة خاصة <؟> [] ParameterTypes ؛ كائن خاص [] المعلمات ؛ // getter/setter ...}استخدم RPCResponse لتغليف استجابة RPC ، الرمز كما يلي:
الفئة العامة rpcresponse {private string requestId ؛ خطأ رمي خاص ؛ نتيجة الكائن الخاص ؛ // getter/setter ...}استخدم RPCDecoder لتوفير فك تشفير RPC ، ما عليك سوى تمديد طريقة فك تشفير الفئة المجردة من Netty's BytetomessagedEcoder ، والرمز كما يلي:
يمتد RPCDecoder من الطبقة العامة بتوسيع BytetomessagedEcoder {private class <؟> genericclass ؛ RPCDecoder العامة (الفئة <؟> genericclass) {this.genericclass = genericClass ؛ } override public void decode (ChannelHandLerContext CTX ، bytebuf in ، list <object> out) يلقي الاستثناء {if (in.readablebytes () <4) {return ؛ } in.markReaderIndex () ؛ int datalength = in.readint () ؛ if (datalength <0) {ctx.close () ؛ } if (in.ReadableBytes () <datalength) {in.ResetReaderIndex () ؛ يعود؛ } byte [] data = new byte [datalength] ؛ in.ReadBytes (البيانات) ؛ Object obj = serializationutil.deserialize (البيانات ، genericclass) ؛ Out.add (OBJ) ؛ }}استخدم RPCencoder لتوفير ترميز RPC ، ما عليك سوى تمديد طريقة تشفير فئة MessagetObyTeencoder Netty من Netty ، والرمز كما يلي:
يمتد RPCencoder من الطبقة العامة messagetobyteencoder {الفئة الخاصة <؟> genericclass ؛ RPCenCoder العامة (الفئة <؟> genericclass) {this.genericclass = genericClass ؛ } Override public void Encode (ChannelHandLerContext CTX ، Object in ، Bytebuf Out) يرمي الاستثناء {if (genericclass.isinstance (in)) {byte [] data = serializationutil.serialize (in) ؛ out.writeint (data.length) ؛ out.writeBytes (البيانات) ؛ }}}اكتب فئة أدوات SerializationUtil واستخدم protostuff لتنفيذ التسلسل:
الطبقة العامة SerializationUtil {خريطة ثابتة خاصة <class <؟> ، المخطط <؟ >> cachedschema = جديد concurrenthashmap <> () ؛ objenesis الثابتة الخاصة = objenesisStd جديد (صحيح) ؛ SerializationUtil () {} suppresswarnings ("غير محدد") ثابت خاص <T> مخطط <T> getSchema (الفئة <T> cls) {schema <t> schema = (schema <t>) cachedschema.get (cls) ؛ if (schema == null) {schema = runTimesChema.CreateFrom (cls) ؛ if (schema! = null) {cachedschema.put (cls ، schema) ؛ }} مخطط الإرجاع ؛ } suppressWarnings ("Unchecked") ثابت عام <T> byte [] Serialize (t obj) {class <T> cls = (class <t>) obj.getClass () ؛ LinkedBuffer Buffer = linkedBuffer.allocate (linkedBuffer.default_buffer_size) ؛ حاول {schema <t> schema = getSchema (cls) ؛ return protostuffioutil.tobytearray (OBJ ، Schema ، Buffer) ؛ } catch (استثناء e) {رمي جديد alficalstateException (e.getMessage () ، e) ؛ } أخيرًا {buffer.clear () ؛ }} static public <T> t deserialize (byte [] data ، class <t> cls) {try {t message = (t) objenesis.newinstance (cls) ؛ المخطط <T> schema = getSchema (CLS) ؛ protostuffioutil.mergefrom (البيانات ، الرسالة ، المخطط) ؛ رسالة العودة ؛ } catch (استثناء e) {رمي جديد alficalstateException (e.getMessage () ، e) ؛ }}}يستخدم ما ورد أعلاه objenesis لإنشاء كائنات ، والتي هي أقوى من انعكاس Java.
ملاحظة: إذا كنت بحاجة إلى استبدال أطر التسلسل الأخرى ، فما عليك سوى تعديل SerializationUtil. بالطبع ، هناك طريقة أفضل لتنفيذها هي توفير عناصر التكوين لتحديد طريقة التسلسل التي يجب استخدامها.
للتعامل مع طلبات RPC في RPChandler ، تحتاج فقط إلى تمديد فئة Abstract SimpleChannelinBoundler ، الرمز كما يلي:
يمتد RpChandler العام simplechannelinboundhandler <rpcrequest> {private static final logger = loggerfactory.getLogger (rpchandler.class) ؛ الخريطة النهائية الخاصة <سلسلة ، كائن> معالجة ؛ public rpchandler (Map <String ، Object> Handlermap) {this.handlermap = handlermap ؛ } Override public void ChannelRead0 (Final ChannelHandLerContext CTX ، RPCRequest request) يلقي استثناء {rpcresponse stripte = new rpcresponse () ؛ استجابة. حاول {كائن نتيجة = مقبض (طلب) ؛ استجابة. } catch (throwable t) {response.seterror (t) ؛ } ctx.writeandflush (response) .addListener (ChannelfutureListener.Close) ؛ } مقبض الكائن الخاص (طلب RPCRequest) يلقي {string className = request.getClassName () ؛ Object ServiceBean = HandlerMap.get (className) ؛ class <؟> serviceClass = serviceBean.getClass () ؛ String methodName = request.getMethodName () ؛ الفئة <؟> [] parametertypes = request.getParameterTypes () ؛ Object [] parameters = request.getParameters () ؛ /*method method = serviceClass.getMethod (MethodName ، parametertypes) ؛ method.setAccible (true) ؛ طريقة الإرجاع. invoke (ServiceBean ، Parameters) ؛*/ fastClass ServiceFastClass = fastclass.create (serviceClass) ؛ fastmethod serviceFastMethod = serviceFastClass.getMethod (MethodName ، parametertypes) ؛ return ServiceFastMethod.invoke (ServiceBean ، المعلمات) ؛ } Override public void stisplycaught (ChannelHandLerContext CTX ، cause reamable) {logger.error ("استثناء الخادم Catch" ، السبب) ؛ ctx.close () ؛ }}من أجل تجنب مشاكل الأداء الناتجة عن استخدام انعكاس Java ، يمكننا استخدام واجهة برمجة تطبيقات الانعكاس التي توفرها CGLIB ، مثل FastClass و FastMethod المستخدمة أعلاه.
الخطوة 7: تكوين العميل
استخدم أيضًا ملفات تكوين الربيع لتكوين عميل RPC. رمز spring.xml كما يلي:
<beans ...> <context: property-placeholder location = "classpath: config.properties"/> <!-تكوين مكون اكتشاف الخدمة-> <bean id = "servicediscovery"> <constructor-arg name = "registryaddress" value = "$ {registry.address}"/> <name name = "serviceDiscovery" ref = "servicediscovery"/> </bean> </bans>يوفر config.properties تكوينًا محددًا:
# Zookeeper Server Registry.Address = 127.0.0.1: 2181
الخطوة 8: تنفيذ اكتشاف الخدمة
استخدم أيضًا ZookeEper لتنفيذ وظيفة اكتشاف الخدمة ، راجع الكود التالي:
الطبقة العامة servicediscovery {private static final logger = loggerfactory.getLogger (servicediscovery.class) ؛ Private Countdownlatch Latch = New CountDownLatch (1) ؛ قائمة متقلبة خاصة <string> Datalist = new ArrayList <> () ؛ سلسلة خاصة registryaddress ؛ signediscovery (string registryAddress) {this.registryaddress = registryAddress ؛ ZOOKEEPER ZK = ConnectServer () ؛ if (zk! = null) {watchnode (zk) ؛ }} السلسلة العامة discover () {string data = null ؛ int size = datalist.size () ؛ if (size> 0) {if (size == 1) {data = datalist.get (0) ؛ logger.debug ("باستخدام البيانات فقط: {}" ، البيانات) ؛ } آخر {data = datalist.get (threadlocalrandom.current (). nextInt (size)) ؛ logger.debug ("باستخدام بيانات عشوائية: {}" ، البيانات) ؛ }} إرجاع البيانات ؛ } zookeeper private connectServer () {zookeeper zk = null ؛ حاول {zk = new ZookeEper (registryAddress ، statter.zk_session_timeout ، new Watcher () {Override public void process (watchedevent event) {if (event.getState () == Event.Severy.SyncConnected) {latch.countdown () ؛) ؛}}}) ؛ latch.await () ؛ } catch (ioException | interruptedException e) {logger.error ("" ، e) ؛ } إرجاع ZK ؛ } private void watchnode (Zookeeper ZK النهائي) {try {list <string> nodeList = zk.getChildren (statter.zk_registry_path ، watcher new () { @over process process (watchedevent) }) ؛ قائمة <Tring> Datalist = new ArrayList <> () ؛ لـ (string node: nodeList) {byte [] bytes = zk.getData (constant.zk_registry_path + "/" + node ، false ، null) ؛ Datalist.add (سلسلة جديدة (بايت)) ؛ } logger.debug ("NODE DATA: {}" ، Datalist) ؛ this.datalist = Datalist ؛ } catch (keeperexception | interruptedException e) {logger.error ("" ، e) ؛ }}}الخطوة 9: تنفيذ وكيل RPC
نستخدم هنا تقنية الوكيل الديناميكي التي توفرها Java لتنفيذ وكيل RPC (بالطبع ، يمكن أيضًا تنفيذها باستخدام CGLIB). الرمز المحدد كما يلي:
الفئة العامة RPCProxy {private String ServerAddress ؛ servicediscovery servicediscovery. public rpcproxy (String serveraddress) {this.serveraddress = serverAddress ؛ } public rpcproxy (serviceDiscovery servicediscovery) {this.servicediscovery = servicediscovery ؛ } suppressWarnings ("unchected") عامة <T> t إنشاء (فئة <؟> interfaceClass) {return (t) proxy.newproxyinstance (interfaceClass.getClasslassloader () ، فئة جديدة <؟> [] args) رمي {rpcrequest request = new rpcrequest () ؛ request.setparametertypes (method.getParametertypes () ؛ integer.parseint (1]) ؛ } آخر {return response.getResult () ؛ }}}) ؛ }}لتنفيذ عميل RPC باستخدام فئة RPCClient ، تحتاج فقط إلى تمديد فئة Abstract SimpleChannelinBoundland التي توفرها Netty ، كما يلي:
يمتد RPCClient من الفئة العامة SimpleChannelinBoundHandler <RpCresponse> {private static final logger = loggerfactory.getLogger (rpcclient.class) ؛ مضيف سلسلة خاصة منفذ الباحث الخاص ؛ استجابة RPCResponse الخاصة ؛ الكائن النهائي الخاص obj = new Object () ؛ public rpcclient (string host ، int port) {this.host = host ؛ this.port = port ؛ } Override public void ChannelRead0 (ChannelHandLerContext CTX ، RPCResponse) يلقي الاستثناء {this.response = reponse ؛ Synchronized (obj) {obj.notifyall () ؛ // تلقي استجابة ، استيقظ على مؤشر الترابط}} Override Public Void Conventcaught (ChannelHandLerContext CTX ، سبب قابلاً للترمي). ctx.close () ؛ } الإرسال العام RPCRESPONSE (RPCREQUEST request) يلقي الاستثناء {eventLoopGroup Group = nioeventloopgroup () ؛ حاول {bootstrap bootstrap = new Bootstrap () ؛ bootstrap.group (group) .Channel (niosocketchannel.class) .Handler (new ChannelInitializer <OcketchAnnel> () {Override public void initchannel (socketchannel) request {channel.pipeline () .addlast (new rpcencoder (rpcrequest.class) .addlast (RPCDecoder جديد (RPCRESPONSE.CLASS) // فك تشفير استجابة RPC (للتعامل مع الاستجابة) .addlast (RPCCLIENT.THIS) ؛ Channelfuture Future = bootstrap.connect (مضيف ، منفذ) .sync () ؛ Future.Channel (). WriteAndflush (request) .sync () ؛ Synchronized (obj) {obj.wait () ؛ // لم يتم استلام أي استجابة ، مما تسبب في انتظار مؤشر الترابط} if (response! = null) {future.channel (). closefuture (). sync () ؛ } استجابة العودة ؛ } أخيرًا {group.shutdowngracely () ؛ }}}الخطوة 10: إرسال طلب RPC
استخدم Junit لكتابة اختبار الوحدة مع الربيع ، مع الكود التالي:
Runwith (SpringJunit4ClassRunner.Class) contextConfiguration (مواقع = "classpath: spring.xml") public class HelloServicEtest {autowired private rpcproxy rpcproxy ؛ test public void hellotest () {helloservice helloservice = rpcproxy.create (helloservice.class) ؛ النتيجة السلسلة = HelloService.hello ("World") ؛ Assert.assertequals ("Hello! World" ، نتيجة) ؛ }}قم بتشغيل اختبارات الوحدة أعلاه وإذا لم يحدث شيء غير متوقع ، فيجب أن ترى الشريط الأخضر.
لخص
تنفذ هذه المقالة إطار عمل RPC خفيف الوزن من خلال Spring + Netty + Protostuff + Zookeeper. يستخدم Spring لتوفير حقن التبعية وتكوين المعلمة ، ويستخدم NetTy لتنفيذ نقل بيانات NIO ، ويستخدم protostuff لتنفيذ التسلسل الكائن ، ويستخدم ZookeEper لتنفيذ تسجيل الخدمة واكتشافها. باستخدام هذا الإطار ، يمكن نشر الخدمات على أي عقدة في بيئة موزعة. يستدعي العميل التنفيذ المحدد للخادم من خلال واجهة عن بعد ، وفصل تمامًا تطوير الخادم والعميل ، مما يوفر الدعم الأساسي لتنفيذ التطبيقات الموزعة على نطاق واسع.
التذييل: الاعتماد على Maven
<!-JUNIT-> <REPERENCED> <VERLEID> JUNIT </rougeid> <STIFACTID> JUNIT </STIFACTID> <الإصدار> 4.11 </sophy> <scope> اختبار </scope> </sependency> <!-SLF4J-> <REPERENCY> <SONED> ORG.SLF4J </rougeid> <soph> 1.7.7 </version> </sependency> <!-Spring-> <sependency> <roupeD> org.springframework </rougeid> <StifactId> spring-context </shintifactid> <spert> 3.2.12.Release </version> <StifactId> اختبار الربيع </artifactId> <sored> 3.2.12.Release </version> <scope> اختبار </scope> </sependency> <!-netty-> <repensid> -> <redence> <roupency> com.dyuproject.protostuff </groupId> <ArtifactId> protostuff-core </artifactId> <sored> 1.0.8 </version> </sependency> <!-zookeeper> <sophy> 3.4.6 </version> </sependency> <!-مجموعات Apache Commons-> <rependency> <roupiD> org.apache.commons </groupId> <trifactid> commons-collections4 </supactid> </version> 4.0 </version> <StifactId> objenesis </artifactId> <الإصدار> 2.1 </version> </respency> <!-cglib-> <reperency> <roupiD> cglib </rougeid> <StifactId> cglib </stifactid> <splex> 3.1 </sperence> </repreadency>