صورة جافا الأصلية: التحقق من قابلية الاستخدام



منذ وقت ليس ببعيد ، أصدرت Oracle الإصدار الأول من مشروع GraalVM (https://www.graalvm.org/). تم تعيين الإصدار على الفور الرقم 19.0.0 ، على ما يبدو من أجل إقناع أن المشروع ناضج وجاهز للاستخدام في التطبيقات الخطيرة. أحد أجزاء هذا المشروع: Substrate VM هو إطار يسمح لك بتحويل تطبيقات Java إلى ملفات تنفيذية أصلية (وكذلك مكتبات أصلية يمكن توصيلها في تطبيقات مكتوبة ، على سبيل المثال ، في C / C ++). تم إعلان هذه الميزة تجريبية حتى الآن. تجدر الإشارة أيضًا إلى أن تطبيقات Java الأصلية بها بعض القيود: يجب عليك إدراج جميع الموارد المستخدمة لإدراجها في البرنامج الأصلي ؛ تحتاج إلى سرد جميع الفئات التي سيتم استخدامها مع انعكاس وغيرها من القيود. يتم إعطاء قائمة كاملة هنا من خلال قيود جافا الأصلية . بعد دراسة هذه القائمة ، من المفهوم من حيث المبدأ أن القيود ليست مهمة جدًا بحيث يكون من المستحيل تطوير تطبيقات أكثر تعقيدًا من تطبيقات hellwords. لقد حددت هذا الهدف: تطوير برنامج صغير يحتوي على خادم ويب مضمن ، ويستخدم قاعدة بيانات (عبر مكتبة ORM) ويترجم إلى ثنائي أصلي يمكن تشغيله على أنظمة بدون تثبيت جهاز Java.

سأجري تجربة على Ubuntu 19.04 (وحدة المعالجة المركزية Intel Core i3-6100 @ 3.70 جيجاهرتز × 4).

تثبيت GraalVM


يتم تثبيت GraalVM بسهولة باستخدام SDKMAN . أمر تثبيت GraalVM:

sdk install java 19.0.0-grl 

سيتم تثبيت OpenJDK GraalVM CE 19.0.0 ، CE هو Community Edition. يوجد أيضًا الإصدار Enterprise Edition (EE) ، لكن يلزم تنزيل هذا الإصدار من Oracle Technology Network ، فالرابط موجود في صفحة تنزيلات GraalVM .

بعد تثبيت GraalVM ، باستخدام مدير تحديث مكون gu بالفعل من GraalVM ، قمت بتثبيت دعم الترجمة في الثنائي الأصلي -

 gu install native-image 

كل شيء ، وأدوات العمل جاهزة ، الآن يمكنك البدء في تطوير التطبيقات.

تطبيق الأصلي بسيط


كنظام بناء ، يمكنني استخدام Maven. لإنشاء ثنائيات أصلية ، هناك مكون إضافي مخضرم:

الأم-صورة-مخضرم-المساعد
 <build> <plugins> <plugin> <groupId>com.oracle.substratevm</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>${graal.version}</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <imageName>nativej</imageName> <buildArgs> --no-server </buildArgs> </configuration> </plugin> </plugins> </build> 


لا يزال مطلوبًا تعيين الفئة الرئيسية للتطبيق. يمكن أن يتم ذلك في كل من البرنامج المساعد لإدخال الصور الأصلية ، وبالطريقة التقليدية ، عبر:

مخضرم جرة-المساعد
 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> <configuration> <archive> <manifest> <mainClass>nativej.Startup</mainClass> </manifest> </archive> </configuration> </plugin> 


قم بإنشاء الفئة الرئيسية:

Startup.java
 public class Startup { public static void main(String[] args) { System.out.println("Hello world!"); } } 


يمكنك الآن تشغيل الأمر maven لإنشاء التطبيق:

 mvn clean package 

يستغرق بناء الثنائي الأصلي على الجهاز 35 ثانية. نتيجة لذلك ، يتم الحصول على ملف ثنائي بحجم 2.5 ميغابايت في الدليل الهدف. لا يتطلب البرنامج جهاز Java المثبت ويعمل على الأجهزة التي تفتقد Java إليها.

رابط المستودع: جيثب: عرض أصلي جافا جافا .

JDBC Postgres Driver


وهكذا ، يعمل تطبيق بسيط ، يعرض "Hello world". ليست هناك حاجة إلى حلول. سأحاول الانتقال لمستوى أعلى: سأقوم بتوصيل برنامج تشغيل بوستجرس JDBC لطلب البيانات من قاعدة البيانات. تأتي المشكلات على github GraalVM عبر الأخطاء المتعلقة بسائق Postgres ، ولكن على مرشحي الإصدار GraalVM. كل منهم يتم وضع علامة ثابتة.

أقوم بتوصيل تبعية postgresql:

 <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.5</version> </dependency> 

أكتب رمز لاستخراج البيانات من قاعدة البيانات (تم إنشاء لوحة المستخدم أبسط):

Startup.java
 public class Startup { public static void main(String[] args) SQLException { final PGSimpleDataSource ds = new PGSimpleDataSource(); ds.setUrl("jdbc:postgresql://localhost/demo_nativem"); ds.setUser("test"); ds.setPassword("test"); try ( Connection conn = ds.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM \"public\".\"user\""); ) { while(rs.next()){ System.out.print("ID: " + rs.getLong("id")); System.out.println(", Name: " + rs.getString("name")); } } } } 


أقوم بجمع البيانات الثنائية الأصلية والحصول على خطأ في البنية على الفور:

Error: No instances are allowed in the image heap for a class that is initialized or reinitialized at image runtime: org.postgresql.Driver. Try marking this class for build-time initialization with --initialize-at-build-time=org.postgresql.Driver


الحقيقة هي أن منشئ التطبيق الأصلي يقوم بتهيئة جميع الحقول الثابتة أثناء عملية الإنشاء (ما لم ينص على خلاف ذلك) ، وهو يقوم بذلك عن طريق فحص تبعيات الفئات. لا يشير الرمز الخاص بي إلى org.postgresql.Driver ، لذلك لا يعرف المجمع كيفية التهيئة بشكل أفضل (عند البناء ، أو عند بدء تشغيل التطبيق) ، ويقدم تسجيله للتهيئة عند البناء. يمكن القيام بذلك عن طريق إضافته إلى وسيطات maven الخاصة بالبرنامج المساعد الخاص بالصورة الأصلية - maven-plugin ، كما هو موضح في وصف الخطأ. بعد إضافة برنامج التشغيل ، تظهر لي نفس الخطأ المتعلق بـ org.postgresql.util.SharedTimer. مرة أخرى ، أقوم بجمع مثل هذا الخطأ في البناء:

Error: Class initialization failed: org.postgresql.sspi.SSPIClient


لا توجد توصيات لتصحيح. ولكن بالنظر إلى مصدر الفصل ، من الواضح أنه يتعلق بتنفيذ التعليمات البرمجية في نظام Windows. على نظام Linux ، فشلت التهيئة (التي تحدث أثناء التجميع) مع وجود خطأ. هناك فرصة لتأخير التهيئة في بداية التطبيق: - تهيئة وقت التشغيل = org.postgresql.sspi.SSPIClient. لن تحدث التهيئة على نظام Linux ولن نحصل على أخطاء تتعلق بهذه الفئة. بناء الحجج:

 <buildArgs> --no-server --no-fallback --initialize-at-build-time=org.postgresql.Driver --initialize-at-build-time=org.postgresql.util.SharedTimer --initialize-at-run-time=org.postgresql.sspi.SSPIClient </buildArgs> 

بدأ التجميع يستغرق دقيقة واحدة و 20 ثانية وانتفاخ الملف حتى 11 ميغابايت. أضفت علمًا إضافيًا لبناء الثنائي: - لا يحظر النسخ الاحتياطي توليد ثنائي أصلي يتطلب جهاز جافا مثبتًا. يتم إنشاء مثل هذا الثنائي إذا اكتشف المجمّع استخدام ميزات اللغة التي لا تكون مدعومة في Substrate VM أو تتطلب التكوين ، ولكن لا يوجد تكوين بعد. في حالتي ، اكتشف الجامع الاستخدام المحتمل للتأمل في برنامج تشغيل JDBC. ولكن هذا استخدام محتمل فقط ، وهو غير مطلوب في برنامجي ، وبالتالي ، لا يلزم تكوين إضافي (سيتم عرض كيفية القيام بذلك لاحقًا). هناك أيضًا العلم الثابت الذي يفرض على المولد رابط ثابت بين libc. ولكن إذا كنت تستخدمه ، فسيتعطل البرنامج بسبب خطأ تجزئة عند محاولة حل اسم الشبكة إلى عنوان IP. بحثت عن أي حلول لهذه المشكلة ، لكنني لم أجد أي شيء مناسب ، لذلك تركت الاعتماد على البرنامج على libc.

أركض الثنائي الناتج وأحصل على الخطأ التالي:

Exception in thread "main" org.postgresql.util.PSQLException: Could not find a java cryptographic algorithm: TLS SSLContext not available.


بعد إجراء بعض الأبحاث ، تم تحديد سبب الخطأ: تقوم Postgres ، بشكل افتراضي ، بإنشاء اتصال TLS باستخدام Elliptic Curve. لا يتضمن SubstrateVM تطبيق مثل هذه الخوارزميات لـ TLS ، وهنا المشكلة المفتوحة المقابلة - دعم TLS أحادي ثنائي ECC (ECDSA / ECDHE) لـ SubstrateVM . هناك عدة حلول: ضع المكتبة من حزمة GraalVM: libsunec.so بجانب التطبيق ، قم بتكوين قائمة الخوارزميات على خادم Postgres ، استبعد خوارزميات Elliptic Curve أو ببساطة قم بتعطيل اتصال TLS في برنامج تشغيل Postgres (تم اختيار هذا الخيار):

 dataSource.setSslMode(SslMode.DISABLE.value); 

بعد التخلص من خطأ إنشاء اتصال مع Postgres ، أقوم بتشغيل التطبيق الأصلي ، ويتم تشغيله وعرض البيانات من قاعدة البيانات.

مستودع الروابط: جيثب: عرض أصلي لجافا بوستجريس .

إطار DI وخادم الويب المضمن


عند تطوير تطبيق Java معقد ، يستخدمون عادةً نوعًا ما من الإطار ، على سبيل المثال ، Spring Boot. ولكن بناءً على هذا المقال بدعم GraalVM للصور الأصلية ، فإن عمل Spring Boot في الصورة الأصلية "خارج الصندوق" وعدنا به فقط في Spring Boot 5.3.

ولكن هناك إطار Micronaut رائع يدعي أنه يعمل في صورة GraalVM الأصلية . بشكل عام ، لا يتطلب توصيل Micronaut إلى أحد التطبيقات التي سيتم تجميعها في ملف ثنائي أي إعدادات خاصة أو حل المشكلات. في الواقع ، يتم بالفعل إعداد العديد من الإعدادات لاستخدام موارد التفكير والاتصال لـ Substrate VM داخل Micronaut. بالمناسبة ، يمكن وضع نفس الإعدادات داخل التطبيق الخاص بك في ملف الإعدادات META-INF / native-image / $ {groupId} / $ {artifactId} /native-image.properties (مثل هذا المسار الموصى به من قبل Substrate VM) محتوى الملف:

native-image.properties
 Args = \ -H:+ReportUnsupportedElementsAtRuntime \ -H:ResourceConfigurationResources=${.}/resource-config.json \ -H:ReflectionConfigurationResources=${.}/reflect-config.json \ -H:DynamicProxyConfigurationResources=${.}/proxy-config.json \ --initialize-at-build-time=org.postgresql.Driver \ --initialize-at-build-time=org.postgresql.util.SharedTimer \ --initialize-at-run-time=org.postgresql.sspi.SSPIClient 


تحتوي ملفات resource-config.json و reflect-config.json و proxy-config.json على إعدادات لربط الموارد والتأمل والوكلاء المستخدمين (Proxy.newProxyInstance). يمكن إنشاء هذه الملفات يدويًا أو استردادها باستخدام agentlib: native-image-agent. في حالة استخدام وكيل الصور الأصلي ، تحتاج إلى تشغيل الجرة المعتادة (وليس الثنائية الأصلية) باستخدام الوكيل:

 java -agentlib:native-image-agent=config-output-dir=output -jar my.jar 

حيث الإخراج هو الدليل الذي توجد به الملفات الموضحة أعلاه. في هذه الحالة ، لا يحتاج البرنامج فقط للتشغيل ، ولكن أيضًا لتنفيذ البرامج النصية في البرنامج ، لأن الإعدادات مكتوبة على الملفات أثناء استخدامك للانعكاس ، والموارد المفتوحة ، وإنشاء وكيل. يمكن وضع هذه الملفات META-INF / native-image / $ {groupId} / $ {artifactId} والإشارة إليها في local-image.properties.

قررت توصيل التسجيل باستخدام logback: لقد أضفت تبعية إلى مكتبة logback- الكلاسيكية وملف logback.xml. بعد ذلك ، جمعت جرة عادية وركضتها باستخدام وكيل الصورة الأصلي. في نهاية البرنامج ، ملفات الإعدادات اللازمة. إذا نظرت إلى محتوياتها ، يمكنك أن ترى أن الوكيل سجل استخدام logback.xml للترجمة إلى الملف الثنائي. أيضًا ، يحتوي ملف reflection-config.json على جميع حالات استخدام الانعكاس: بالنسبة للفئات المحددة ، ستدخل معلومات التعريف في الملف الثنائي.

بعد ذلك أضفت تبعية إلى مكتبة micronaut-http-server-netty لاستخدام خادم الويب المدمج المستند إلى netty وإنشاء وحدة تحكم:

Startup.java
 @Controller("/hello") public class HelloController { @Get("/{name}") @Produces(MediaType.TEXT_PLAIN) public HttpResponse<String> hello(String name) { return HttpResponse.ok("Hello " + name); } } 


والطبقة الرئيسية:

HelloController.java
 public class Startup { public static void main(String[] args) { Signal.handle(new Signal("INT"), sig -> System.exit(0)); Micronaut.run(Startup.class, args); } } 


الآن يمكنك محاولة بناء ثنائي أصلي. استغرق تجميعي 4 دقائق. إذا قمت بتشغيله وانتقل إلى العنوان http: // localhost: 8080 / hello / user ، فسيحدث خطأ:

 {"_links":{"self":{"href":"/hello/user","templated":false}},"message":"More than 1 route matched the incoming request. The following routes matched /hello/user: GET - /hello/user, GET - /hello/user"} 

بصراحة ، ليس من الواضح تمامًا سبب حدوث ذلك ، ولكن بعد البحث عن طريق الكتابة ، وجدت أن الخطأ يختفي إذا تمت إزالة الأسطر التالية من ملف resource-config.json (الذي أنشأه الوكيل):

  {"pattern":"META-INF/services/com.fasterxml.jackson.databind.Module"}, {"pattern":"META-INF/services/io.micronaut.context.env.PropertySourceLoader"}, {"pattern":"META-INF/services/io.micronaut.http.HttpResponseFactory"}, {"pattern":"META-INF/services/io.micronaut.inject.BeanConfiguration"}, {"pattern":"META-INF/services/io.micronaut.inject.BeanDefinitionReference"}, 

يسجل Micronaut هذه الموارد ويبدو أن إعادة التسجيل تؤدي إلى تسجيل مزدوج لوحدة التحكم والخطأ. إذا قمت بعد تصحيح الملف ، وإعادة إنشاء الملف الثنائي وتشغيله ، فلن يكون هناك المزيد من الأخطاء ، سيتم عرض النص "Hello user" على http: // localhost: 8080 / hello / user .

أريد أن ألفت الانتباه إلى استخدام السطر التالي في الفصل الرئيسي:

 Signal.handle(new Signal("INT"), sig -> System.exit(0)); 

يجب إدخاله حتى ينتهي Micronaut بشكل صحيح. على الرغم من أن Micronaut يعلق خطافًا لإغلاقه ، إلا أنه لا يعمل في الملف الثنائي الأصلي. هناك مشكلة مقابلة: Shutdownhook لا تطلق النار مع الأم . تم تمييزه على أنه ثابت ، لكن في الواقع ، يحتوي فقط على حل بديل باستخدام فئة الإشارة.

مستودع مستودع الارتباط: جيثب: عرض أصلي جافا جافا postgres .

اتصال ORM


JDBC جيدة ، لكن إطارات الشفرة المتكررة ، SELECT و UPDATE التي لا نهاية لها. سأحاول تسهيل (أو تعقيد ، اعتمادًا على الجانب الذي يجب أن أبدو) حياتي من خلال ربط نوع من ORM.

السبات


في البداية قررت تجربة Hibernate ، لأنها واحدة من أكثر ORMs شيوعًا لجافا. لكنني لم أتمكن من إنشاء صورة أصلية باستخدام الإسبات بسبب خطأ في البنية:

 Error: Field java.lang.reflect.Method.defaultValue is not present on type java.lang.reflect.Constructor. Error encountered while analysing java.lang.reflect.Method.getDefaultValue() Parsing context: parsing org.hibernate.annotations.common.annotationfactory.AnnotationProxy.getAnnotationValues(AnnotationProxy.java:63) parsing org.hibernate.annotations.common.annotationfactory.AnnotationProxy(AnnotationProxy.java:52) ... 

هناك مشكلة مفتوحة مقابلة: [الصورة الأصلية] يؤدي Micronaut + Hibernate إلى حدوث خطأ أثناء تحليل java.lang.reflect.Method.getDefaultValue () .

جوخ


ثم قررت تجربة jOOQ . تمكنت من بناء ثنائي أصلي ، على الرغم من أنني اضطررت إلى إجراء الكثير من الإعدادات: تحديد الفئات التي يجب تهيئتها (وقت البناء ، وقت التشغيل) والفوضى مع التفكير. في النهاية ، يعود الأمر كله إلى حقيقة أنه عند بدء تشغيل التطبيق ، يقوم jOOQ بتهيئة الوكيل org.jooq.impl.ParserImpl $ Ignore كعضو ثابت في الفئة org.jooq.impl.Tools. ويستخدم هذا البروكسي MethodHandle ، والذي لا يدعمه Substrate VM. إليك مشكلة مفتوحة مماثلة: [الصورة الأصلية] يفشل Micronaut + Kafka في إنشاء صورة أصلية باستخدام وسيطة MethodHandle ولا يمكن اختصارها في مكالمة واحدة على الأكثر .

اباتشي كايين


أباتشي كايين أقل شيوعًا ، لكنه يبدو وظيفيًا جدًا. سأحاول توصيله. لقد قمت بإنشاء ملفات XML لوصف مخطط قاعدة البيانات ، ويمكن إنشاؤها إما يدويًا أو باستخدام أداة CayenneModeler GUI ، أو بناءً على قاعدة بيانات موجودة. باستخدام البرنامج المساعد cayenne-maven-plugin في ملف pom ، سيتم تنفيذ إنشاء شفرة للفئات التي تتوافق مع جداول قاعدة البيانات:

، مخضرم حريف-المساعد
 <plugin> <groupId>org.apache.cayenne.plugins</groupId> <artifactId>cayenne-maven-plugin</artifactId> <version>${cayenne.version}</version> <configuration> <map>src/main/resources/db/datamap.map.xml</map> <destDir>${project.build.directory}/generated-sources/cayenne</destDir> </configuration> <executions> <execution> <goals> <goal>cgen</goal> </goals> </execution> </executions> </plugin> 


ثم أضفت فئة CayenneRuntimeFactory لتهيئة مصنع سياق قاعدة البيانات:

CayenneRuntimeFactory.java
 @Factory public class CayenneRuntimeFactory { private final DataSource dataSource; public CayenneRuntimeFactory(DataSource dataSource) { this.dataSource = dataSource; } @Bean @Singleton public ServerRuntime cayenneRuntime() { return ServerRuntime.builder() .dataSource(dataSource) .addConfig("db/cayenne-test.xml") .build(); } } 


HelloController المراقب المالي:

HelloController.java
 @Controller("/hello") public class HelloController { private final ServerRuntime cayenneRuntime; public HelloController(ServerRuntime cayenneRuntime) { this.cayenneRuntime = cayenneRuntime; } @Get("/{name}") @Produces(MediaType.TEXT_PLAIN) public HttpResponse<String> hello(String name) { final ObjectContext context = cayenneRuntime.newContext(); final List<User> result = ObjectSelect.query(User.class).select(context); if (result.size() > 0) { result.get(0).setName(name); } context.commitChanges(); return HttpResponse.ok(result.stream() .map(x -> MessageFormat.format("{0}.{1}", x.getObjectId(), x.getName())) .collect(Collectors.joining(","))); } } 


ثم أطلق البرنامج كجرار منتظم ، باستخدام agentlib: الوكيل المحلي للصور ، لجمع المعلومات حول الموارد المستخدمة والتفكير.

قمت بجمع الثنائي الأصلي ، وقم بتشغيله ، وانتقل إلى العنوان http: // localhost: 8080 / hello / user وأحصل على خطأ:

 {"message":"Internal Server Error: Provider com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl not found"} 

اتضح من agentlib: لم يكتشف وكيل الصور الأصلي استخدام هذه الفئة في التفكير.

إضافته يدويًا إلى ملف reflect-config.json:

 { "name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl", "allDeclaredConstructors":true } 

مرة أخرى ، أقوم بجمع البيانات الثنائية وبدء وتحديث صفحة الويب وأحصل على خطأ آخر:

 Caused by: java.util.MissingResourceException: Resource bundle not found org.apache.cayenne.cayenne-strings. Register the resource bundle using the option -H:IncludeResourceBundles=org.apache.cayenne.cayenne-strings. 

كل شيء واضح هنا ، وأضيف الإعداد ، كما هو مبين في الحل المقترح. مرة أخرى ، أقوم بجمع الثنائي (هذا هو 5 دقائق من الوقت) ، وأبدأ تشغيله مرة أخرى ومرة ​​أخرى خطأ ، وآخر:

 No DataMap found, can't route query org.apache.cayenne.query.SelectQuery@2af96966[root=class name.voyachek.demos.nativemcp.db.User,name=]"} 

اضطررت إلى العبث بهذا الخطأ ، بعد العديد من الاختبارات ، ودراسة المصادر ، أصبح من الواضح أن سبب الخطأ يكمن في هذا السطر من الفصل org.apache.cayenne.resource.URLResource:

 return new URLResource(new URL(url, relativePath)); 

كما اتضح فيما بعد ، يقوم Substrate VM بتحميل المورد بواسطة عنوان url ، والذي يشار إليه كقاعدة ، وليس بواسطة عنوان url ، الذي يجب تشكيله على أساس الأساس ونسب المسار. ما هي المشكلة التالية التي تم تسجيلها بواسطتي: محتوى مورد غير صالح عند استخدام عنوان URL جديد (سياق URL ، سلسلة المواصفات) .

يتم تحديد الخطأ ، والآن تحتاج إلى البحث عن حلول. لحسن الحظ ، اتضح أن أباتشي كايين كان أمرًا قابلاً للتخصيص. كان عليك تسجيل محمل الموارد الخاص بك:

 ServerRuntime.builder() .dataSource(dataSource) .addConfig("db/cayenne-test.xml") .addModule(binder -> { binder.bind(ResourceLocator.class).to(ClassLoaderResourceLocatorFix.class); binder.bind(Key.get(ResourceLocator.class, Constants.SERVER_RESOURCE_LOCATOR)).to(ClassLoaderResourceLocatorFix.class); }) .build(); 

هنا هو رمزه:

ClassLoaderResourceLocatorFix.java
 public class ClassLoaderResourceLocatorFix implements ResourceLocator { private ClassLoaderManager classLoaderManager; public ClassLoaderResourceLocatorFix(@Inject ClassLoaderManager classLoaderManager) { this.classLoaderManager = classLoaderManager; } @Override public Collection<Resource> findResources(String name) { final Collection<Resource> resources = new ArrayList<>(3); final Enumeration<URL> urls; try { urls = classLoaderManager.getClassLoader(name).getResources(name); } catch (IOException e) { throw new ConfigurationException("Error getting resources for "); } while (urls.hasMoreElements()) { resources.add(new URLResourceFix(urls.nextElement())); } return resources; } private class URLResourceFix extends URLResource { URLResourceFix(URL url) { super(url); } @Override public Resource getRelativeResource(String relativePath) { try { String url = getURL().toString(); url = url.substring(0, url.lastIndexOf("/") + 1) + relativePath; return new URLResource(new URI(url).toURL()); } catch (MalformedURLException | URISyntaxException e) { throw new CayenneRuntimeException( "Error creating relative resource '%s' : '%s'", e, getURL(), relativePath); } } } } 


لديها خط

 return new URLResource(new URL(url, relativePath)); 

يحل محله:

 String url = getURL().toString(); url = url.substring(0, url.lastIndexOf("/") + 1) + relativePath; return new URLResource(new URI(url).toURL()); 

أقوم بجمع الثنائي (70 ميغابايت) ، وابدأ تشغيله ، انتقل إلى http: // localhost: 8080 / hello / user ويعمل كل شيء ، ويتم عرض البيانات من قاعدة البيانات على الصفحة.

رابط المستودع: جيثب: عرض محلي-صغير-حريف-تجريبي .

النتائج


تم تحقيق الهدف: تم تطوير تطبيق ويب بسيط مع الوصول إلى قاعدة البيانات باستخدام ORM. يتم تصنيف التطبيق إلى ثنائي أصلي ويمكن تشغيله على أنظمة بدون تثبيت جهاز Java. على الرغم من العديد من المشكلات ، وجدت مجموعة من الأطر والإعدادات والحلول التي سمحت لي بالحصول على برنامج عمل.

نعم ، لا تزال القدرة على إنشاء ثنائيات منتظمة من شفرة مصدر Java في حالة تجريبية. هذا واضح من خلال وفرة المشاكل ، والحاجة إلى البحث عن حلول. لكن في النهاية ، اتضح أنه تحقق النتيجة المرجوة. ماذا حصلت؟

  • الملف الوحيد المستقل ذاتيًا (تقريبًا ، هناك تبعيات على المكتبات مثل libc) يمكن تشغيلها على أنظمة بدون جهاز Java.
  • وقت البدء هو متوسط ​​40 مللي ثانية مقابل ثانيتين عند بدء تشغيل جرة عادية.

من بين أوجه القصور ، أود أن أشير إلى وقت التجميع الطويل للثنائي الأصلي. يستغرق الأمر خمس دقائق في المتوسط ​​، وعلى الأرجح ستزداد عند كتابة التعليمات البرمجية وربط المكتبات. لذلك ، من المنطقي إنشاء ثنائيات بناءً على تعليمة برمجية كاملة التصحيح. بالإضافة إلى ذلك ، تتوفر معلومات تصحيح الأخطاء للثنائيات الأصلية فقط في الإصدار التجاري من Graal VM - Enterprise Edition.

Source: https://habr.com/ru/post/ar454790/


All Articles