שילוב גרובי ביישומי Java

1. הקדמה

במדריך זה נחקור את הטכניקות העדכניות ביותר לשילוב גרובי ביישום Java.

2. כמה מילים על גרובי

שפת התכנות גרובי היא שפת עוצמה, שפה דינמית אופציונלית. הוא נתמך על ידי קרן תוכנת Apache וקהילת גרובי, עם תרומות של יותר מ 200 מפתחים.

בעזרתו ניתן לבנות יישום שלם, ליצור מודול או ספרייה נוספת האינטראקציה עם קוד הג'אווה שלנו, או להריץ סקריפטים שהוערכו והורכבו בזמן אמת.

למידע נוסף, אנא קרא מבוא לשפה גרובית או עבור לתיעוד הרשמי.

3. תלות Maven

בזמן כתיבת שורות אלה המהדורה היציבה האחרונה היא 2.5.7, בעוד ש- Groovy 2.6 ו- 3.0 (שניהם התחילו בסתיו 17 ') עדיין נמצאים בשלב אלפא.

בדומה למגף האביב, אנחנו רק צריכים לכלול את גרובי-הכל pom כדי להוסיף את כל התלות אנו עשויים להזדקק, מבלי לדאוג לגרסאותיהם:

 org.codehaus.groovy groovy-all $ {groovy.version} pom 

4. אוסף משותף

לפני שנכנס לפרטים כיצד להגדיר את התצורה של מייבן, עלינו להבין עם מה אנו מתמודדים.

הקוד שלנו יכיל קבצי Java וגם קבצי Groovy. ל- Groovy לא תהיה שום בעיה למצוא את שיעורי Java, אבל מה אם נרצה שג'אווה תמצא שיעורים ושיטות Groovy?

הנה מגיע אוסף משותף להצלה!

אוסף משותף הוא תהליך שנועד לקמפל הן ג'אווה והן גרובי קבצים באותו פרויקט, בפקודה אחת של Maven.

עם אוסף משותף, מהדר גרובי:

  • לנתח את קבצי המקור
  • בהתאם ליישום, צור ערימות תואמות למהדר Java
  • הפעל את מהדר Java כדי להרכיב את הבדים יחד עם מקורות Java - כך שיעורי Java יכולים למצוא תלות ב- Groovy
  • אוסף את מקורות גרובי - כעת המקורות הגרובי שלנו יכולים למצוא את התלות בג'אווה שלהם

בהתאם לתוסף המיישם אותו, אנו עשויים להידרש להפריד את הקבצים לתיקיות ספציפיות או לומר למהדר היכן למצוא אותם.

ללא אוסף משותף, קובצי המקור של ג'אווה יורכבו כאילו הם מקורות גרובי. לפעמים זה עשוי לעבוד מכיוון שרוב התחביר של Java 1.7 תואם ל- Groovy, אך הסמנטיקה תהיה שונה.

5. תוספי מהדר Maven

ישנם כמה תוספי מהדר זמינים התומכים בהידור משותף, כל אחד עם נקודות החוזק והחולשה שלו.

השניים הנפוצים ביותר עם Maven הם Groovy-Eclipse Maven ו- GMaven +.

5.1. התוסף Groovy-Eclipse Maven

התוסף Groovy-Eclipse Maven מפשט את ההרכב המשותף על ידי הימנעות מייצור תלים, עדיין צעד חובה עבור מהדרים אחרים כמו GMaven+, אך הוא מציג כמה מוזרויות תצורה.

כדי לאפשר אחזור של חפצי המהדר החדשים ביותר, עלינו להוסיף את מאגר Maven Bintray:

  bintray Groovy Bintray //dl.bintray.com/groovy/maven אף פעם לא שקר 

ואז, בסעיף התוספים, אנו אומרים למהדר Maven באיזו גרסת מהדר גרובי עליו להשתמש.

למעשה, התוסף שנשתמש בו - תוסף המהדר Maven - לא ממש מהדר, אלא במקום האציל את העבודה ל גרובי-ליקוי-אצווה חפץ:

 maven-compiler-plugin 3.8.0 groovy-eclipse-compiler $ {java.version} $ {java.version} org.codehaus.groovy groovy-eclipse-compiler 3.3.0-01 org.codehaus.groovy groovy-eclipse-batch $ {groovy.version} -01 

ה גרובי-הכל גרסת התלות צריכה להתאים לגרסת המהדר.

לבסוף, עלינו להגדיר את הגילוי האוטומטי של המקור שלנו: כברירת מחדל, המהדר יבדוק לתיקיות כגון src / main / java ו src / main / groovy, אבל אם תיקיית Java שלנו ריקה, המהדר לא יחפש את המקורות הגרוביים שלנו.

אותו מנגנון תקף למבחנים שלנו.

כדי לאלץ את גילוי הקבצים, נוכל להוסיף כל קובץ src / main / java ו src / test / java, או פשוט להוסיף את גרובי-ליקוי-מהדר חיבור:

 org.codehaus.groovy groovy-eclipse-compiler 3.3.0-01 נכון 

ה סעיף הוא חובה לתת לתוסף להוסיף את שלב הבנייה הנוסף ואת היעדים, המכילים את שתי תיקיות המקור של Groovy.

5.2. תוסף GMavenPlus

לתוסף GMavenPlus יכול להיות שם הדומה לתוסף GMaven הישן, אך במקום ליצור תיקון בלבד, המחבר התאמץ לפשט ולנתק את המהדר מגרסת גרובי ספציפית.

לשם כך התוסף מפריד את עצמו מההנחיות הסטנדרטיות עבור תוספי מהדר.

מהדר GMavenPlus מוסיף תמיכה בתכונות שעדיין לא היו קיימות במהדרים אחרים באותה תקופה, כגון invokedynamic, קונסולת המעטפת האינטראקטיבית ו- Android.

מצד שני, זה מציג כמה סיבוכים:

  • זה משנה את ספריות המקור של מייבן להכיל גם את מקורות הג'אווה וגם את מקורות גרובי, אך לא את תאי הג'אווה
  • זה דורש מאיתנו לנהל תלויים אם לא נמחק אותם עם היעדים המתאימים

כדי להגדיר את הפרויקט שלנו, עלינו להוסיף את התוסף gmavenplus:

 org.codehaus.gmavenplus gmavenplus-plugin 1.7.0 בצע addSources addTestSources createStubs הידור createTestStubs compileTests removeStubs removeTestStubs org.codehaus.groovy groovy-all = 1.5.0 צריך לעבוד כאן -> 2.5.6 זמן ריצה 

כדי לאפשר בדיקה של תוסף זה, יצרנו קובץ פום שני בשם gmavenplus-pom.xml במדגם.

5.3. קומפילציה עם תוסף Eclipse-Maven

עכשיו, כשהכל מוגדר, סוף סוף נוכל לבנות את השיעורים שלנו.

בדוגמה שהבאנו, יצרנו יישום Java פשוט בתיקיית המקור src / main / java וכמה תסריטים גרובי ב src / main / groovy, שם נוכל ליצור שיעורים ותסריטים של גרובי.

בואו נבנה הכל עם תוסף Eclipse-Maven:

$ mvn clean compile ... [INFO] --- maven-compiler-plugin: 3.8.0: compile (default-compile) @ core-groovy-2 --- [INFO] זוהה שינויים - קומפילציה של המודול! [INFO] באמצעות מהדר Groovy-Eclipse כדי לאסוף קבצי Java ו- Groovy ...

הנה אנו רואים זאת גרובי מרכיב הכל.

5.4. קומפילציה עם GMavenPlus

GMavenPlus מראה כמה הבדלים:

$ mvn -f gmavenplus-pom.xml הידור נקי ... [INFO] --- gmavenplus-plugin: 1.7.0: createStubs (ברירת מחדל) @ core-groovy-2 --- [INFO] באמצעות Groovy 2.5.7 כדי לבצע createStubs. [INFO] נוצר 2 תלויים. [INFO] ... [INFO] --- תוסף maven-compiler: 3.8.1: קומפילציה (ברירת מחדל של הידור) @ core-groovy-2 --- [INFO] זוהה שינויים - קומפילציה של המודול! [INFO] קומפילציה של 3 קבצי מקור ל- XXX \ Baeldung \ TutorialsRepo \ core-groovy-2 \ target \ classes [INFO] ... [INFO] --- gmavenplus-plugin: 1.7.0: הידור (ברירת מחדל) @ core- groovy-2 --- [INFO] באמצעות Groovy 2.5.7 לביצוע קומפילציה. [INFO] הורכב 2 קבצים. [INFO] ... [INFO] --- gmavenplus-plugin: 1.7.0: removeStubs (ברירת מחדל) @ core-groovy-2 --- [INFO] ...

אנו מבחינים מיד כי GMavenPlus עובר את השלבים הנוספים של:

  1. יצירת גבעולים, אחד לכל קובץ גרובי
  2. קומפילציה של קבצי הג'אווה - stubs וקוד Java כאחד
  3. קומפילציה של קבצי גרובי

על ידי ייצור גבעולים, GMavenPlus יורש חולשה שגרמה לכאבי ראש רבים למפתחים בשנים האחרונות, בעבודה עם אוסף משותף.

בתרחיש האידיאלי, הכל יעבוד בסדר גמור, אך אם מציגים צעדים נוספים יש לנו גם יותר נקודות כישלון: למשל, הבנייה עלולה להיכשל לפני שתוכל לנקות את הבדלים.

אם זה קורה, גבעולים ישנים שנותרו סביב עשויים לבלבל את IDE שלנו, ואז יופיעו שגיאות אוסף כאשר אנו יודעים שהכל צריך להיות תקין.

רק מבנה נקי ימנע מציד מכשפות כואב וארוך.

5.5. תלות באריזה בקובץ הצנצנת

ל הפעל את התוכנית כצנצנת משורת הפקודה, הוספנו את תוסף maven-assembly, שיכלול את כל התלות הגרובית ב"צנצנת שמנה "ששמה עם התיקון המוגדר בנכס מתאר Ref:

 org.apache.maven.plugins maven-assembly-plugin 3.1.0 jar-with-dependencies com.baeldung.MyJointCompilationApp חבילת הרכבה להכנה 

לאחר השלמת האוסף נוכל להריץ את הקוד שלנו באמצעות פקודה זו:

$ java -jar target / core-groovy-2-1.0-SNAPSHOT-jar-with-dependencies.jar com.baeldung.MyJointCompilationApp

6. טעינת קוד גרובי בזמן אמת

אוסף Maven מאפשר לנו לכלול קבצי Groovy בפרויקט שלנו ולהתייחס לשיעורים ולשיטות שלהם מג'אווה.

אמנם, זה לא מספיק אם אנו רוצים לשנות את ההיגיון בזמן הריצה: האוסף פועל מחוץ לשלב זמן הריצה, אז אנחנו עדיין צריכים להפעיל מחדש את היישום שלנו כדי לראות את השינויים שלנו.

כדי לנצל את העוצמה הדינמית (והסיכונים) של גרובי, עלינו לבחון את הטכניקות הקיימות לטעינת הקבצים שלנו כאשר היישום שלנו כבר פועל.

6.1. GroovyClassLoader

כדי להשיג זאת, אנו זקוקים ל GroovyClassLoader, אשר יכול לנתח את קוד המקור בתבנית טקסט או קובץ וליצור את אובייקטים המחלקה שהתקבלו.

כאשר המקור הוא קובץ, תוצאת הקומפילציה נשמרת גם במטמון, כדי למנוע תקורה כשאנחנו שואלים את המטעין מספר מקרים מאותו מעמד.

סקריפט המגיע ישירות מ- חוּט האובייקט במקום זאת לא יישמר במטמוןלפיכך, קריאה לאותו סקריפט מספר פעמים עדיין עלולה לגרום לדליפות זיכרון.

GroovyClassLoader הוא הבסיס שעליו בנויות מערכות אינטגרציה אחרות.

היישום פשוט יחסית:

מטעין גמר פרטי GroovyClassLoader; כפול פרטי addWithGroovyClassLoader (int x, int y) זורק IllegalAccessException, InstantiationException, IOException {Class calcClass = loader.parseClass (קובץ חדש ("src / main / groovy / com / baeldung /", "CalcMath.groovy")); GroovyObject calc = (GroovyObject) calcClass.newInstance (); החזר (Double) calc.invokeMethod ("calcSum", אובייקט חדש [] {x, y}); } MyJointCompilationApp (ציבורי) {loader = GroovyClassLoader חדש (this.getClass (). getClassLoader ()); // ...} 

6.2. GroovyShell

מטעין סקריפט מעטפת לְנַתֵחַ() השיטה מקבלת מקורות בתבנית טקסט או קובץ מייצר מופע של ה- תַסרִיט מעמד.

מקרה זה יורש את לָרוּץ() שיטה מ תַסרִיט, שמבצע את כל הקובץ מלמעלה למטה ומחזיר את התוצאה שניתנה על ידי השורה האחרונה שבוצעה.

אם נרצה, נוכל גם להאריך תַסרִיט בקוד שלנו, ועוקפים את יישום ברירת המחדל כדי לקרוא ישירות לוגיקה הפנימית שלנו.

היישום להתקשר Script.run () נראה ככה:

כפול פרטי addWithGroovyShellRun (int x, int y) זורק IOException {Script script = shell.parse (קובץ חדש ("src / main / groovy / com / baeldung /", "CalcScript.groovy")); return (Double) script.run (); } ציבורי MyJointCompilationApp () {// ... shell = GroovyShell חדש (מטעין, מחייב חדש ()); // ...} 

שים לב כי לָרוּץ() אינו מקבל פרמטרים, ולכן נצטרך להוסיף לקובץ שלנו כמה משתנים גלובליים לאתחל אותם דרך ה- כריכה לְהִתְנַגֵד.

כאשר אובייקט זה מועבר ב GroovyShell אתחול, המשתנים משותפים עם כל תַסרִיט מקרים.

אם אנו מעדיפים שליטה פרטנית יותר, נוכל להשתמש בה InvokeMethod (), שיכולים לגשת לשיטות שלנו באמצעות השתקפות ולהעביר טיעונים ישירות.

בואו נסתכל על היישום הזה:

פגז GroovyShell הסופי הפרטי; כפול פרטי addWithGroovyShell (int x, int y) זורק IOException {Script script = shell.parse (קובץ חדש ("src / main / groovy / com / baeldung /", "CalcScript.groovy")); להחזיר (Double) script.invokeMethod ("calcSum", אובייקט חדש [] {x, y}); } MyJointCompilationApp ציבורי () {// ... shell = GroovyShell חדש (מטעין, מחייב חדש ()); // ...} 

מתחת לכיסויים, GroovyShell מסתמך על GroovyClassLoader להרכבה ולמטמון של הכיתות שהתקבלו, ולכן אותם כללים שהוסברו קודם חלים באותו אופן.

6.3. GroovyScriptEngine

ה GroovyScriptEngine הכיתה מיועדת במיוחד לאפליקציות אלו להסתמך על טעינה מחדש של סקריפט ותלותיו.

למרות שיש לנו את התכונות הנוספות הללו, ליישום יש רק כמה הבדלים קטנים:

מנוע GroovyScriptEngine הסופי הפרטי; ריק ריק addWithGroovyScriptEngine (int x, int y) זורק IllegalAccessException, InstantiationException, ResourceException, ScriptException {Class calcClass = engine.loadScriptByName ("CalcMath.groovy"); GroovyObject calc = calcClass.newInstance (); תוצאת אובייקט = calc.invokeMethod ("calcSum", אובייקט חדש [] {x, y}); LOG.info ("התוצאה של שיטת CalcMath.calcSum () היא {}", תוצאה); } ציבורי MyJointCompilationApp () {... URL url = null; נסה {url = new File ("src / main / groovy / com / baeldung /"). toURI (). toURL (); } לתפוס (MalformedURLException e) {LOG.error ("חריג בעת יצירת url", e); } מנוע = GroovyScriptEngine חדש (URL חדש [] {url}, this.getClass (). getClassLoader ()); engineFromFactory = חדש GroovyScriptEngineFactory (). getScriptEngine (); }

הפעם עלינו להגדיר את שורשי המקור, ואנו מתייחסים לתסריט רק עם שמו, שהוא קצת יותר נקי.

מסתכל בתוך loadScriptByName אנו יכולים לראות מיד את הצ'ק isSourceNewer שם המנוע בודק אם המקור שנמצא כעת במטמון עדיין תקף.

בכל פעם שהקובץ שלנו משתנה, GroovyScriptEngine יטען אוטומטית את הקובץ המסוים הזה ואת כל הכיתות תלוי בו.

למרות שמדובר בתכונה שימושית וחזקה, היא עלולה לגרום לתופעת לוואי מסוכנת מאוד: טעינה מחדש פעמים רבות של מספר עצום של קבצים תביא לתקורה של מעבד ללא אזהרה.

אם זה יקרה, ייתכן שנצטרך ליישם את מנגנון המטמון שלנו בכדי להתמודד עם בעיה זו.

6.4. GroovyScriptEngineFactory (JSR-223)

JSR-223 מספק א API סטנדרטי לשיחות מסגרות סקריפטים מאז Java 6.

היישום נראה דומה, אם כי אנו חוזרים לטעון דרך נתיבי קבצים מלאים:

גמר פרטי ScriptEngine engineFromFactory; בטל פרטי addWithEngineFactory (int x, int y) זורק IllegalAccessException, InstantiationException, javax.script.ScriptException, FileNotFoundException {Class calcClas = (Class) engineFromFactory.eval (FileReader חדש (קובץ חדש ("src / main / groovy / com / baeld) "," CalcMath.groovy "))); GroovyObject calc = (GroovyObject) calcClas.newInstance (); תוצאת אובייקט = calc.invokeMethod ("calcSum", אובייקט חדש [] {x, y}); LOG.info ("התוצאה של שיטת CalcMath.calcSum () היא {}", תוצאה); } MyJointCompilationApp ציבורי () {// ... engineFromFactory = חדש GroovyScriptEngineFactory (). getScriptEngine (); }

זה נהדר אם אנו משלבים את האפליקציה שלנו בכמה שפות סקריפטים, אבל מערך התכונות שלו מוגבל יותר. לדוגמה, זה לא תומך בטעינה מחדש של הכיתה. ככזה, אם אנו משתלבים רק עם גרובי, אולי עדיף לדבוק בגישות קודמות.

7. מלכודות של הידור דינמי

באמצעות כל אחת מהשיטות לעיל, נוכל ליצור יישום זה קורא סקריפטים או שיעורים מתיקיה ספציפית מחוץ לקובץ הצנצנת שלנו.

זה ייתן לנו את גמישות להוסיף תכונות חדשות בזמן שהמערכת פועלת (אלא אם כן אנו זקוקים לקוד חדש בחלק ג'אווה), ובכך להשיג איזשהו פיתוח מסירה רציפה.

אך היזהר מחרב פיפיות זו: כעת עלינו להגן על עצמנו בזהירות רבה מפני כשלים שעלולים להתרחש גם בזמן הידור וגם בזמן ריצה, בפועל להבטיח שהקוד שלנו ייכשל בבטחה.

8. מלכודות של הפעלת גרובי בפרויקט ג'אווה

8.1. ביצועים

כולנו יודעים שכאשר מערכת צריכה להיות מאוד ביצועית, יש לעקוב אחרי כמה כללי זהב.

שניים שעשויים להכביד יותר על הפרויקט שלנו הם:

  • להימנע מהשתקפות
  • צמצם את מספר ההוראות באמצעות קוד הצפייה

השתקפות, בפרט, היא פעולה יקרה עקב תהליך בדיקת המחלקה, השדות, השיטות, פרמטרי השיטה וכן הלאה.

אם ננתח את שיטות השיחות מג'אווה לגרובי, למשל, כאשר אנו מריצים את הדוגמה addWithCompiledClasses, ערימת הפעולה בין .calcSum והשורה הראשונה של שיטת גרובי בפועל נראית:

calcSum: 4, CalcScript (com.baeldung) addWithCompiledClasses: 43, MyJointCompilationApp (com.baeldung) addWithStaticCompiledClasses: 95, MyJointCompilationApp (com.baeldung) main: 117, App (com.baeldung)

מה שעולה בקנה אחד עם Java. אותו דבר קורה כאשר אנו מטילים את האובייקט שהוחזר על ידי המטען וקוראים לשיטתו.

עם זאת, זה מה ש להפעיל את המתודה שיחה עושה:

calcSum: 4, CalcScript (com.baeldung) invoke0: -1, NativeMethodAccessorImpl (sun.reflect) להפעיל: 62, NativeMethodAccessorImpl (sun.reflect) להפעיל: 43, DelegatingMethodAccessorImpl (sun.reflect) להפעיל: 498lang, שיטה (java) .reflect) להפעיל: 101, CachedMethod (org.codehaus.groovy.reflection) doMethodInvoke: 323, MetaMethod (groovy.lang) הפעל Method: 1217, MetaClassImpl (groovy.lang) invokeMethod: 1041, MetaClassImpl (groovyethod:) , MetaClassImpl (groovy.lang) הפעל שיטה: 44, GroovyObjectSupport (groovy.lang) הפעל שיטה: 77, סקריפט (groovy.lang) addWithGroovyShell: 52, MyJointCompilationApp (com.baeldung) addWithDynamicCompiledClasses: 99, MyJoint , MyJointCompilationApp (com.baeldung)

במקרה זה, אנו יכולים להעריך מה באמת עומד מאחורי כוחו של גרובי: MetaClass.

א MetaClass מגדיר את ההתנהגות של כל מעמד גרובי או Java כלשהו, ​​ולכן Groovy בוחן אותו בכל פעם שיש פעולה דינמית לביצוע על מנת למצוא את שיטת היעד או שדה. לאחר שנמצא, זרימת ההשתקפות הסטנדרטית מבצעת אותו.

שני כללי זהב שנשברו בשיטה אחת להפעיל!

אם אנחנו צריכים לעבוד עם מאות קבצי גרובי דינמיים, איך שאנחנו מכנים את השיטות שלנו, ישפיע על הביצועים העצום במערכת שלנו.

8.2. שיטה או נכס לא נמצאו

כאמור, אם נרצה לפרוס גרסאות חדשות של קבצי Groovy במחזור חיים של תקליטור, אנחנו צריכים התייחס אליהם כאילו היו API נפרד ממערכת הליבה שלנו.

משמעות הדבר היא הצבת מקום מספר רב של בדיקות כשלים והגבלות על עיצוב קוד כך שהיזם החדש שלנו הצטרף לא מפוצץ את מערכת הייצור בלחיצה שגויה.

דוגמאות לכל אחת מהן הן: בעל צינור CI ושימוש בשימוש בשיטה במקום מחיקה.

מה קורה אם לא נעשה זאת? אנו מקבלים חריגים איומים בגלל שיטות חסרות וספירות וטיעונים שגויים.

ואם אנו חושבים כי אוסף יציל אותנו, בואו נסתכל על השיטה calcSum2 () מהתסריטים הגרובי שלנו:

// שיטה זו תיכשל ב def def הריצה calcSum2 (x, y) {// DANGER! המשתנה "יומן" עשוי להיות לא מוגדר log.info "ביצוע $ x + $ y" // סכנה! שיטה זו אינה קיימת! calcSum3 () // סכנה! המשתנה הרשום "z" אינו מוגדר! log.info ("רישום משתנה לא מוגדר: $ z")}

על ידי עיון בקובץ כולו, אנו רואים מיד שתי בעיות: השיטה calcSum3 () והמשתנה z אינם מוגדרים בשום מקום.

עם זאת, התסריט נערך בהצלחה, ללא אפילו אזהרה אחת, הן באופן סטטי ב- Maven והן באופן דינמי ב- GroovyClassLoader.

זה ייכשל רק כשננסה להפעיל אותו.

האוסף הסטטי של Maven יציג שגיאה רק אם קוד ה- Java שלנו מתייחס ישירות אליו calcSum3 ()לאחר הליהוק של GroovyObject כמו שאנחנו עושים ב addWithCompiledClasses () שיטה, אבל זה עדיין לא יעיל אם נשתמש בהשתקפות במקום.

9. מסקנה

במאמר זה בחנו כיצד אנו יכולים לשלב את Groovy ביישום ה- Java שלנו, תוך בחינה של שיטות אינטגרציה שונות וכמה מהבעיות שאנו עלולים להיתקל בהן בשפות מעורבות.

כרגיל, את קוד המקור המשמש בדוגמאות ניתן למצוא ב- GitHub.


$config[zx-auto] not found$config[zx-overlay] not found