मेरे पास एक छोटी सी स्ट्रीमटेक्स लाइब्रेरी है जो जावा 8 स्ट्रीम एपीआई का विस्तार करती है। मैं परंपरागत रूप से मावेन के माध्यम से पुस्तकालय इकट्ठा करता हूं, और अधिकांश भाग के लिए मैं हर चीज से खुश हूं। हालाँकि, मैं प्रयोग करना चाहता था।
लाइब्रेरी की कुछ चीजों को जावा के विभिन्न संस्करणों में अलग तरह से काम करना चाहिए। सबसे महत्वपूर्ण उदाहरण नई स्ट्रीम एपीआई विधियां हैं जैसे takeWhile
, जो केवल जावा 9 में दिखाई देती है। मेरी लाइब्रेरी जावा 8 में भी इन विधियों का कार्यान्वयन प्रदान करती है, लेकिन जब आप स्वयं स्ट्रीम एपीआई का विस्तार करते हैं, तो आप कुछ प्रतिबंधों के तहत आते हैं, जिनका मैं यहां उल्लेख नहीं करता। मैं चाहता हूं कि जावा 9+ उपयोगकर्ताओं के पास मानक कार्यान्वयन तक पहुंच हो।
जावा 8 का उपयोग करते हुए संकलन जारी रखने की परियोजना के लिए, यह आमतौर पर प्रतिबिंब उपकरणों का उपयोग करके किया जाता है: हमें पता चलता है कि क्या मानक पुस्तकालय में एक संबंधित विधि है और यदि हां, तो हम इसे कहते हैं, और यदि नहीं, तो हम अपने कार्यान्वयन का उपयोग करते हैं। हालाँकि, मैंने MethodHandle API का उपयोग करने का निर्णय लिया, क्योंकि यह कम कॉल ओवरहेड की घोषणा करता है। आप MethodHandle
को पहले से प्राप्त कर सकते हैं और इसे स्थिर क्षेत्र में सहेज सकते हैं:
MethodHandles.Lookup lookup = MethodHandles.publicLookup(); MethodType type = MethodType.methodType(Stream.class, Predicate.class); MethodHandle method = null; try { method = lookup.findVirtual(Stream.class, "takeWhile", type); } catch (NoSuchMethodException | IllegalAccessException e) {
और फिर इसका उपयोग करें:
if (method != null) { return (Stream<T>)method.invokeExact(stream, predicate); } else {
यह सब अच्छा है, लेकिन यह बदसूरत लग रहा है। और सबसे महत्वपूर्ण बात यह है कि हर उस बिंदु पर जहां क्रियान्वयन में बदलाव संभव है, आपको ऐसी शर्तें लिखनी होंगी। एक ही इंटरफ़ेस के कार्यान्वयन के रूप में जावा 8 और जावा 9 की रणनीतियों को अलग करने के लिए थोड़ा वैकल्पिक दृष्टिकोण है। या, लाइब्रेरी के आकार को बचाने के लिए, बस जावा 8 के लिए एक अलग गैर-अंतिम वर्ग में सब कुछ लागू करें, और जावा 9 के लिए इनहेरिटर का विकल्प चुनें। यह कुछ इस तरह किया गया था:
तब उपयोग के बिंदुओं पर, आप केवल return Internals.VER_SPEC.takeWhile(stream, predicate)
लिख सकते return Internals.VER_SPEC.takeWhile(stream, predicate)
। मेथड हैंडल वाले सभी मैजिक अब केवल Java9Specific
क्लास में हैं। इस तरह, इस दृष्टिकोण ने एंड्रॉइड उपयोगकर्ताओं के लिए लाइब्रेरी को बचा लिया, जिन्होंने पहले शिकायत की थी कि यह सिद्धांत रूप में काम नहीं करता था। एंड्रॉइड वर्चुअल मशीन जावा नहीं है, यह जावा 7 विनिर्देश को भी लागू नहीं करता है। विशेष रूप से, invokeExact
जैसे पॉलीमोर्फिक हस्ताक्षर के साथ कोई विधियां नहीं हैं, और invokeExact
में इस कॉल की बहुत उपस्थिति सब कुछ तोड़ देती है। अब इन कॉलों को एक ऐसे वर्ग में रखा गया है, जो कभी भी आरंभिक नहीं होता है।
हालांकि, यह सब अभी भी बदसूरत है। एक सुंदर समाधान (कम से कम सिद्धांत में) मल्टी रिलीज़ जार का उपयोग करना है, जो जावा 9 ( जेईपी -238 ) के साथ आया था। ऐसा करने के लिए, कक्षाओं का हिस्सा जावा 9 के तहत संकलित किया जाना चाहिए और जार फ़ाइल के अंदर META-INF/versions/9
में रखी गई संकलित वर्ग फाइलें। इसके अलावा, आपको Multi-Release: true
लाइन जोड़ने की आवश्यकता है Multi-Release: true
यह प्रकट होने के लिए Multi-Release: true
है। तब जावा 8 सफलतापूर्वक इन सभी को नजरअंदाज कर देगा, और जावा 9 और नए वर्गों को वर्गों के बजाय समान नामों के साथ लोड करेगा जो सामान्य स्थान पर स्थित हैं।
पहली बार मैंने दो साल से अधिक समय पहले ऐसा करने की कोशिश की थी, जावा 9 की रिलीज से कुछ समय पहले। यह बहुत मुश्किल हो गया, और मैंने छोड़ दिया। जावा 9 से संकलक द्वारा परियोजना को संकलित करना भी मुश्किल था: कई मावेन प्लगइन्स बस बदले हुए आंतरिक एपीआई, परिवर्तित java.version
स्ट्रिंग java.version
या कुछ और के कारण टूट गए।
इस वर्ष एक नया प्रयास अधिक सफल रहा। प्लगइन्स के अधिकांश भाग अपडेट हो चुके हैं और नए जावा में काफी पर्याप्त रूप से काम करते हैं। पहला चरण मैंने पूरी विधानसभा को जावा 11 में अनुवादित किया। इसके लिए, प्लगइन संस्करणों को अपडेट करने के अलावा, मुझे निम्नलिखित कार्य करने थे:
अगला चरण परीक्षणों का अनुकूलन था। चूंकि Java 8 और Java 9 में लाइब्रेरी का व्यवहार स्पष्ट रूप से भिन्न है, इसलिए दोनों संस्करणों के लिए परीक्षण चलाना तर्कसंगत होगा। अब हम जावा 11 के तहत सब कुछ चला रहे हैं, इसलिए जावा 8-विशिष्ट कोड का परीक्षण नहीं किया गया है। यह एक काफी बड़ा और गैर-तुच्छ कोड है। इसे ठीक करने के लिए, मैंने एक कृत्रिम कलम बनाया:
static final VersionSpecific VER_SPEC = System.getProperty("java.version", "").compareTo("1.9") > 0 && !Boolean.getBoolean("one.util.streamex.emulateJava8") ? new Java9Specific() : new VersionSpecific();
अब बस पास करें -Done.util.streamex.emulateJava8=true
परीक्षण चलाते समय -Done.util.streamex.emulateJava8=true
,
परीक्षण करने के लिए जो आमतौर पर जावा 8 में काम करता है। अब argLine = -Done.util.streamex.emulateJava8=true
साथ maven-surefire-plugin
argLine = -Done.util.streamex.emulateJava8=true
maven-surefire-plugin
कॉन्फ़िगरेशन में एक नया <execution>
ब्लॉक जोड़ें, और परीक्षण दो बार पास होते हैं।
हालांकि, मैं कुल कवरेज परीक्षणों पर विचार करना चाहूंगा। मैं JaCoCo का उपयोग करता हूं, और यदि आप उसे कुछ नहीं बताते हैं, तो दूसरा रन पहले के परिणामों को लिख देगा। JaCoCo कैसे काम करता है? यह सबसे पहले रेडी prepare-agent
टारगेट चलाता है, जो argLine Maven प्रॉपर्टी को सेट करता है, -javaagent:blah-blah/.m2/org/jacoco/org.jacoco.agent/0.8.4/org.jacoco.agent-0.8.4-runtime.jar=destfile=blah-blah/myproject/target/jacoco.exec
जैसा कुछ साइन करता है -javaagent:blah-blah/.m2/org/jacoco/org.jacoco.agent/0.8.4/org.jacoco.agent-0.8.4-runtime.jar=destfile=blah-blah/myproject/target/jacoco.exec
। मैं चाहता हूं कि दो अलग-अलग निष्पादन फाइलें बनें। आप इसे इस तरह से हैक कर सकते हैं। prepare-agent
कॉन्फ़िगरेशन में destFile=${project.build.directory}
जोड़ें। रफ लेकिन प्रभावी है। अब argLine
blah-blah/myproject/target
में समाप्त हो जाएगा। हां, यह फाइल नहीं है, बल्कि एक डायरेक्टरी है। लेकिन हम परीक्षणों के प्रारंभ में पहले से ही फ़ाइल का नाम बदल सकते हैं। हम argLine = @{argLine}/jacoco_java8.exec -Done.util.streamex.emulateJava8=true
maven-surefire-plugin
argLine = @{argLine}/jacoco_java8.exec -Done.util.streamex.emulateJava8=true
और argLine = @{argLine}/jacoco_java8.exec -Done.util.streamex.emulateJava8=true
जावा रन के लिए argLine = @{argLine}/jacoco_java8.exec -Done.util.streamex.emulateJava8=true
argLine = @{argLine}/jacoco_java11.exec
को Java 11 के लिए सेट करते हैं। फिर merge
लक्ष्य का उपयोग करके इन दो फ़ाइलों को संयोजित करना आसान है, जो कि JaCoCo प्लगइन भी प्रदान करता है, और हमें समग्र कवरेज मिलता है।
अपडेट: टिप्पणियों में गॉडिन ने कहा कि यह अनावश्यक था, आप एक फ़ाइल पर लिख सकते हैं, और यह स्वचालित रूप से पिछले एक के साथ परिणाम को गोंद करेगा। अब मुझे यकीन नहीं है कि इस परिदृश्य ने मेरे लिए शुरुआत में काम क्यों नहीं किया।
खैर, हम मल्टी-रिलीज़ जार पर स्विच करने के लिए अच्छी तरह से तैयार हैं। मुझे यह करने के लिए कई सिफारिशें मिलीं। पहले एक बहु-मॉड्यूलर मावेन परियोजना के उपयोग का सुझाव दिया गया था। मुझे यह महसूस नहीं हुआ: यह परियोजना संरचना की एक बड़ी जटिलता है: उदाहरण के लिए, पाँच pom.xml हैं। जावा 9 में संकलित की जाने वाली फ़ाइलों की एक जोड़ी के खातिर मूर्ख बनाने के लिए लगता है कि यह ओवरकिल है। maven-antrun-plugin
माध्यम से एक और संकलन शुरू करने का सुझाव दिया। यहां मैंने केवल एक अंतिम उपाय के रूप में देखने का फैसला किया। यह स्पष्ट है कि मावेन में किसी भी समस्या को चींटी का उपयोग करके हल किया जा सकता है, लेकिन यह किसी भी तरह काफी अनाड़ी है। अंत में, मैंने एक तृतीय-पक्ष मल्टी-रिलीज़-जार-मावेन-प्लगइन प्लगइन का उपयोग करने के लिए एक सिफारिश देखी। यह पहले से ही स्वादिष्ट और सही लग रहा था।
प्लगइन src/main/java-mr/9
जैसी निर्देशिकाओं में जावा के नए संस्करणों के लिए विशिष्ट सोर्स कोड रखने की सलाह देता है, जो मैंने किया। मैंने अभी भी अधिकतम के वर्ग नामों में टकराव से बचने का फैसला किया है, इसलिए एकमात्र वर्ग (यहां तक कि इंटरफ़ेस) जो कि जावा 8 और जावा 9 दोनों में मौजूद है, मेरे पास यह है:
पुराना स्थिरांक एक नए स्थान पर चला गया, लेकिन वास्तव में और कुछ नहीं बदला। केवल अब Java9Specific
वर्ग बहुत सरल हो गया है: MethodHandle के साथ सभी स्क्वाट्स को प्रत्यक्ष विधि कॉल के साथ सफलतापूर्वक बदल दिया गया है।
प्लगइन निम्नलिखित बातें करने का वादा करता है:
- मानक
maven-compiler-plugin
बदलें और एक अलग लक्ष्य संस्करण के साथ दो चरणों में संकलित करें। - मानक
maven-jar-plugin
बदलें और संकलन परिणामों को सही पथों के साथ maven-jar-plugin
। Multi-Release: true
लाइन जोड़ें Multi-Release: true
MANIFEST.MF
लिए Multi-Release: true
।
इसे काम करने के लिए, इसने काफी कदम उठाए।
jar
से multi-release-jar
पैकेजिंग बदलें।
बिल्ड-एक्सटेंशन जोड़ें:
<build> <extensions> <extension> <groupId>pw.krejci</groupId> <artifactId>multi-release-jar-maven-plugin</artifactId> <version>0.1.5</version> </extension> </extensions> </build>
maven-compiler-plugin
से कॉपी कॉन्फ़िगरेशन। मेरे पास <source>1.8</source>
और <arg>-Xlint:all</arg>
की भावना में केवल डिफ़ॉल्ट संस्करण था
मुझे लगा कि maven-compiler-plugin
को अब हटाया जा सकता है, लेकिन यह पता चला है कि नया प्लगइन परीक्षणों के संकलन को प्रतिस्थापित नहीं करता है, इसलिए इसके लिए जावा संस्करण डिफ़ॉल्ट रूप से रीसेट किया गया था (1.5!) और -Xlint:all
तर्क गायब हो गए। इसलिए मुझे छोड़ना पड़ा।
स्रोत और दो प्लगइन्स के लिए लक्ष्य की नकल न करने के लिए, मुझे पता चला कि वे दोनों maven.compiler.source
और maven.compiler.target
के गुणों का सम्मान करते हैं। मैंने उन्हें स्थापित किया और प्लगइन सेटिंग्स से संस्करणों को हटा दिया। हालाँकि, यह अचानक पता चला कि maven-javadoc-plugin
maven-compiler-plugin
सेटिंग से source
का उपयोग मानक जावाडॉक के URL का पता लगाने के लिए करता है, जिसे मानक विधियों का संदर्भ देते समय लिंक किया जाना चाहिए। और अब वह maven.compiler.source
सम्मान नहीं करता है। इसलिए, मुझे maven-compiler-plugin
सेटिंग में <source>${maven.compiler.source}</source>
वापस करना पड़ा। सौभाग्य से, JavaDoc उत्पन्न करने के लिए किसी अन्य परिवर्तन की आवश्यकता नहीं थी। यह बहुत अच्छी तरह से जावा 8 स्रोतों से उत्पन्न हो सकता है, क्योंकि संस्करणों के साथ पूरा हिंडोला पुस्तकालय एपीआई को प्रभावित नहीं करता है।
maven-bundle-plugin
, जिसने मेरी लाइब्रेरी को ओएसजीआई कलाकृतियों में बदल दिया। उन्होंने बस packaging = multi-release-jar
साथ काम करने से इनकार कर दिया। सिद्धांत रूप में, मैंने उसे कभी पसंद नहीं किया। वह प्रकट करने के लिए अतिरिक्त लाइनों का एक सेट लिखता है, उसी समय छँटाई क्रम को खराब करता है और अधिक कचरा जोड़ता है। सौभाग्य से, यह पता चला कि हाथ से आपको जो कुछ भी ज़रूरत है उसे लिखकर इससे छुटकारा पाना मुश्किल नहीं है। केवल, ज़ाहिर है, maven-jar-plugin
, बल्कि नए में। multi-release-jar
प्लगइन का पूरा विन्यास अंततः इस तरह से बन गया (मैंने कुछ गुणों को project.package
तरह परिभाषित किया है)
<plugin> <groupId>pw.krejci</groupId> <artifactId>multi-release-jar-maven-plugin</artifactId> <version>0.1.5</version> <configuration> <compilerArgs><arg>-Xlint:all</arg></compilerArgs> <archive> <manifestEntries> <Automatic-Module-Name>${project.package}</Automatic-Module-Name> <Bundle-Name>${project.name}</Bundle-Name> <Bundle-Description>${project.description}</Bundle-Description> <Bundle-License>${license.url}</Bundle-License> <Bundle-ManifestVersion>2</Bundle-ManifestVersion> <Bundle-SymbolicName>${project.package}</Bundle-SymbolicName> <Bundle-Version>${project.version}</Bundle-Version> <Export-Package>${project.package};version="${project.version}"</Export-Package> </manifestEntries> </archive> </configuration> </plugin>
टेस्ट। अब हमारे पास one.util.streamex.emulateJava8
नहीं है, लेकिन आप क्लास-पथ परीक्षणों को संशोधित करके समान प्रभाव प्राप्त कर सकते हैं। अब विपरीत सच है: डिफ़ॉल्ट रूप से, पुस्तकालय जावा 8 मोड में काम करता है, और जावा 9 के लिए आपको लिखना होगा:
<classesDirectory>${basedir}/target/classes-9</classesDirectory> <additionalClasspathElements>${project.build.outputDirectory}</additionalClasspathElements> <argLine>@{argLine}/jacoco_java9.exec</argLine>
एक महत्वपूर्ण बिंदु: classes-9
को सामान्य श्रेणी की फाइलों से आगे जाना चाहिए, इसलिए हमें सामान्य लोगों को additionalClasspathElements
क्लैसपैथ एलीमेंट्स में स्थानांतरित करना होगा, जिन्हें बाद में जोड़ा जाता है।
सूत्रों का कहना है। मैं सोर्स-जार करने जा रहा हूं, और जावा 9 स्रोतों को इसमें पैक करना अच्छा होगा ताकि, उदाहरण के लिए, आईडीई में डिबगर उन्हें सही ढंग से प्रदर्शित कर सके। मैं डुप्लिकेट VerSpec
बारे में ज्यादा चिंतित नहीं VerSpec
, क्योंकि एक पंक्ति है, जिसे केवल आरंभीकरण के दौरान निष्पादित किया जाता है। मेरे लिए केवल जावा 8. से ही विकल्प छोड़ना ठीक है। हालाँकि, Java9Specific.java
लिए अच्छा होगा। यह अतिरिक्त स्रोत निर्देशिका को मैन्युअल रूप से जोड़कर किया जा सकता है:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <phase>test</phase> <goals><goal>add-source</goal></goals> <configuration> <sources> <source>src/main/java-mr/9</source> </sources> </configuration> </execution> </executions> </plugin>
विरूपण साक्ष्य एकत्र करने के बाद, मैंने इसे एक परीक्षण परियोजना से जोड़ा और इसे IntelliJ IDEA डिबगर में जांचा। सब कुछ खूबसूरती से काम करता है: परीक्षण परियोजना को चलाने के लिए उपयोग किए जाने वाले वर्चुअल मशीन के संस्करण के आधार पर, हम डिबगिंग करते समय एक अलग स्रोत में समाप्त होते हैं।
यह बहु-रिलीज-जार प्लगइन द्वारा ही किया जाना अच्छा होगा, इसलिए मैंने ऐसा सुझाव दिया।
JaCoCo। यह उसके साथ सबसे मुश्किल निकला, और मैं बाहर की मदद के बिना नहीं कर सकता था। तथ्य यह है कि जावा -8 और जावा -9 के लिए प्लग-इन पूरी तरह से उत्पन्न निष्पादन-फाइलें, सामान्य रूप से उन्हें एक फ़ाइल में चिपकाया जाता है, हालांकि, एक्सएमएल और एचटीएमएल में रिपोर्ट उत्पन्न करते समय, उन्होंने जावा -9 से स्रोतों की उपेक्षा की। स्रोत में घूमते हुए , मैंने देखा कि यह केवल project.getBuild().getOutputDirectory()
में मिली क्लास फ़ाइलों के लिए एक रिपोर्ट तैयार करता है। यह निर्देशिका, बेशक, प्रतिस्थापित की जा सकती है, लेकिन वास्तव में मेरे पास उनमें से दो हैं: classes
और classes-9
। सैद्धांतिक रूप से, आप सभी कक्षाओं को एक निर्देशिका में कॉपी कर सकते हैं, outputDirectory
बदल सकते हैं और outputDirectory
शुरू कर सकते हैं, और फिर outputDirectory
वापस बदल outputDirectory
हैं ताकि जेएआर विधानसभा को तोड़ न सकें। लेकिन यह पूरी तरह से बदसूरत लगता है। सामान्य तौर पर, मैंने अपनी परियोजना में इस समस्या के समाधान को अभी के लिए स्थगित करने का फैसला किया, लेकिन मैंने जैकोको के लोगों को लिखा कि क्लास फ़ाइलों के साथ कई निर्देशिकाओं को निर्दिष्ट करने में सक्षम होना अच्छा होगा।
मेरे आश्चर्य करने के लिए, कुछ ही घंटों बाद JaCoCo Godin के डेवलपर्स में से एक मेरे प्रोजेक्ट पर आया और एक पुल-रिक्वेस्ट लाया, जो समस्या का समाधान करता है। यह कैसे तय करता है? चींटी का उपयोग, बिल्कुल! यह पता चला है कि JaCoCo के लिए चींटी-प्लगइन अधिक उन्नत है और कई स्रोत निर्देशिकाओं और वर्ग फ़ाइलों के लिए सारांश रिपोर्ट उत्पन्न कर सकता है। उसे एक अलग merge
चरण की भी आवश्यकता नहीं थी, क्योंकि वह एक ही बार में कई निष्पादन-फाइलों को फीड कर सकता था। सामान्य तौर पर, चींटी को टाला नहीं जा सकता था, इसलिए ऐसा हो। मुख्य बात जो काम की है, और pom.xml केवल छह लाइनों से बढ़ी है।

मैंने भी दिलों में ट्वीट किया:
इसलिए मुझे एक बहुत ही काम करने वाला प्रोजेक्ट मिला जो एक सुंदर मल्टी-रिलीज़ जार का निर्माण करता है। उसी समय, कवरेज का प्रतिशत भी बढ़ गया, क्योंकि मैंने सभी प्रकार के catch (NoSuchMethodException | IllegalAccessException e)
को हटा दिया था जो जावा 9 में अप्राप्य थे। दुर्भाग्य से, यह प्रोजेक्ट संरचना IntelliJ IDEA के लिए समर्थित नहीं है, इसलिए मुझे POM आयात को छोड़ना पड़ा और IDE में प्रोजेक्ट को मैन्युअल रूप से कॉन्फ़िगर करना पड़ा। । मुझे उम्मीद है कि भविष्य में अभी भी एक मानक समाधान होगा जो सभी प्लगइन्स और टूल द्वारा स्वचालित रूप से समर्थित होगा।