קשרי אינטרנט באביב

1. הקדמה

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

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

2. ההקשר של יישום האינטרנט השורש

לכל יישום אינטרנט של Spring יש הקשר יישומי משויך שקשור למחזור החיים שלו: ההקשר של יישום הרשת.

זוהי תכונה ישנה שקדמה ל- Spring Web MVC, כך שהיא אינה קשורה במיוחד לאף טכנולוגיית מסגרת אינטרנט.

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

ההקשר ביישום אינטרנט הוא תמיד מופע של WebApplicationContext. זה ממשק המרחיב ApplicationContext עם חוזה לגישה ל ServletContext.

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

2.1. ה ContextLoaderListener

ההקשר של יישום האינטרנט הבסיסי המתואר בסעיף הקודם מנוהל על ידי מאזין לכיתה org.springframework.web.context.ContextLoaderListener, שהוא חלק מה- קפיץ-רשת מודול.

כברירת מחדל, המאזין יטען הקשר של יישום XML מ- /WEB-INF/applicationContext.xml. עם זאת, ניתן לשנות את ברירות המחדל האלה. אנו יכולים להשתמש בהערות ג'אווה במקום ב- XML, למשל.

אנו יכולים להגדיר את המאזין הזה במתאר webapp (web.xml קובץ) או באופן פרוגרמטי בסביבות Servlet 3.x.

בחלקים הבאים נבחן בפירוט את כל אחת מהאפשרויות הללו.

2.2. באמצעות web.xml והקשר יישומי XML

כשמשתמש web.xml, אנו מגדירים את המאזין כרגיל:

  org.springframework.web.context.ContextLoaderListener 

אנו יכולים לציין מיקום חלופי של תצורת הקשר XML עם ה- contextConfigLocation פָּרָמֶטֶר:

 contextConfigLocation /WEB-INF/rootApplicationContext.xml 

או יותר ממיקום אחד, מופרדים בפסיקים:

 contextConfigLocation /WEB-INF/context1.xml, /WEB-INF/context2.xml 

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

 contextConfigLocation /WEB-INF/*-context.xml 

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

2.3. באמצעות web.xml והקשר יישומי Java

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

אנו משתמשים ב- contextClass פרמטר כדי לספר למאזין איזה סוג הקשר צריך ליצור:

 contextClass org.springframework.web.context.support.AnnotationConfigWebApplicationContext 

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

אנו יכולים אם כן לרשום שיעור אחד או יותר עם הערות:

 contextConfigLocation com.baeldung.contexts.config.RootApplicationConfig, com.baeldung.contexts.config.NormalWebAppConfig 

לחלופין, נוכל להגיד להקשר לסרוק חבילה אחת או יותר:

 contextConfigLocation com.baeldung.bean.config 

וכמובן שנוכל לערבב ולהתאים בין שתי האפשרויות.

2.4. תצורה תכנותית עם סרוולט 3.x

גרסה 3 של Servlet API ביצעה תצורה דרך ה- web.xml הקובץ אופציונלי לחלוטין. ספריות יכולות לספק את שברי האינטרנט שלהם, שהם חלקים של תצורת XML שיכולים לרשום מאזינים, פילטרים, סרוולטים וכן הלאה.

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

ה קפיץ-רשת המודול עושה שימוש בתכונות אלה ומציע את ה- API שלו לרישום רכיבי היישום כשהוא מתחיל.

אביב סורק את מסלול הכיתה של היישום לאיתור מקרים של org.springframework.web.WebApplicationInitializer מעמד. זהו ממשק עם שיטה אחת, בטל onStartup (ServletContext servletContext) זורק ServletException, זה מופעל עם הפעלת היישום.

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

2.5. באמצעות Servlet 3.x ובהקשר יישומי XML

נתחיל בהקשר של XML, ממש כמו בסעיף 2.2.

אנו ניישם את האמור לעיל בהפעלה שיטה:

מחלקה ציבורית ApplicationInitializer מיישמת את WebApplicationInitializer {@Override public void onStartup (ServletContext servletContext) זורק ServletException {// ...}}

בואו נשבור את היישום שורה אחר שורה.

ראשית אנו יוצרים הקשר שורש. מכיוון שאנחנו רוצים להשתמש ב- XML, זה חייב להיות הקשר יישומי מבוסס XML, ומכיוון שאנחנו נמצאים בסביבת אינטרנט, הוא צריך ליישם WebApplicationContext גם כן.

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

XmlWebApplicationContext rootContext = XmlWebApplicationContext חדש ();

ואז, בשורה השנייה, אנו אומרים את ההקשר מאיפה לטעון את הגדרות השעועית שלו. שוב, setConfigLocations הוא האנלוגי הפרוגרמטי של contextConfigLocation פרמטר ב web.xml:

rootContext.setConfigLocations ("/ WEB-INF / rootApplicationContext.xml");

לבסוף, אנו יוצרים a ContextLoaderListener עם הקשר השורש ורשמו אותו במיכל ה- servlet. כמו שאנו יכולים לראות, ContextLoaderListener יש קונסטרוקטור מתאים שלוקח א WebApplicationContext והופך אותו לזמין ליישום:

servletContext.addListener (ContextLoaderListener חדש (rootContext));

2.6. באמצעות Servlet 3.x ובהקשר של יישומי Java

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

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

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

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

עלינו רק לומר לו כיצד לבנות את הקשר השורש:

AnnotationsBasedApplicationInitializer מרחיב ציבורי מרחיב AbstractContextLoaderInitializer {@Override מוגן WebApplicationContext createRootApplicationContext () {AnnotationConfigWebApplicationContext rootContext = AnnotationConfigWebApplicationContext (); rootContext.register (RootApplicationConfig.class); להחזיר rootContext; }}

כאן אנו יכולים לראות שאנחנו כבר לא צריכים לרשום את ה- ContextLoaderListener, אשר חוסך מאיתנו מעט קוד של boilerplate.

שימו לב גם לשימוש ב- להירשם שיטה ספציפית ל AnnotationConfigWebApplicationContext במקום הגנרי יותר setConfigLocations: על ידי הפעלתו, אנו יכולים לרשום אדם פרטי @תְצוּרָה בישרו שיעורים עם ההקשר, וכך נמנע מסריקת חבילה.

3. הקשרים של סרוולט דיספלטר

בואו נתמקד כעת בהקשר אחר של יישומים. הפעם נתייחס לתכונה הספציפית ל- Spring MVC, ולא לחלק מהתמיכה היישומית באינטרנט של Spring.

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

כל אחד DispatcherServlet יש הקשר יישומי משויך. שעועית המוגדרת בהקשרים כאלה מגדירה את ה- servlet ומגדירה אובייקטים של MVC כמו בקרים ומפתחי תצוגה.

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

3.1. באמצעות web.xml והקשר יישומי XML

DispatcherServlet מוכרז בדרך כלל ב web.xml עם שם ומיפוי:

 normal-webapp org.springframework.web.servlet.DispatcherServlet 1 normal-webapp / api / * 

אם לא צוין אחרת, שם ה- servlet משמש לקביעת קובץ ה- XML ​​לטעינה. בדוגמה שלנו נשתמש בקובץ WEB-INF / normal-webapp-servlet.xml.

אנו יכולים גם לציין נתיב אחד או יותר לקבצי XML, באופן דומה לזה ContextLoaderListener:

 ... contextConfigLocation /WEB-INF/normal/*.xml 

3.2. באמצעות web.xml והקשר יישומי Java

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

 normal-webapp-annotations org.springframework.web.servlet.DispatcherServlet contextClass org.springframework.web.context.support.AnnotationConfigWebApplicationContext contextConfigLocation com.baeldung.contexts.config.NormalWebAppConfig 1 

3.3. באמצעות Servlet 3.x ובהקשר יישומי XML

שוב, נסתכל על שתי שיטות שונות להכרזה תכניתית א DispatcherServletונחיל אחד על הקשר XML והשני על הקשר של Java.

אז בואו נתחיל עם כללי WebApplicationInitializer והקשר של יישום XML.

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

XmlWebApplicationContext normalWebAppContext = XmlWebApplicationContext חדש (); normalWebAppContext.setConfigLocation ("/ WEB-INF / normal-webapp-servlet.xml"); ServletRegistration.Dynamic normal = servletContext.addServlet ("normal-webapp", DispatcherServlet חדש (normalWebAppContext)); normal.setLoadOnStartup (1); normal.addMapping ("/ api / *");

אנו יכולים לצייר בקלות הקבלה בין הקוד לעיל לבין המקבילה web.xml אלמנטים תצורה.

3.4. באמצעות Servlet 3.x ובהקשר של יישומי Java

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

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

@Override מוגן WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext secureWebAppContext = חדש AnnotationConfigWebApplicationContext (); secureWebAppContext.register (SecureWebAppConfig.class); להחזיר secureWebAppContext; } מחרוזת מוגנת @Override [] getServletMappings () {להחזיר מחרוזת חדשה [] {"/ s / api / *"}; }

כאן אנו יכולים לראות שיטה ליצירת ההקשר המשויך לסרוולט, בדיוק כמו שראינו בעבר בהקשר הבסיסי. כמו כן, יש לנו שיטה לציין את מיפויי ה- servlet, כמו ב- web.xml.

4. קשרי הורים וילדים

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

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

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

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

ביישומי MVC פשוטים יותר, מספיק שיהיה הקשר יחיד המשויך לשרת ה- dispatch היחיד. אין צורך בפתרונות מורכבים מדי!

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

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

הערה: כאשר אנו מרחיבים AbstractDispatcherServletInitializer (ראה סעיף 3.4), אנו רושמים גם הקשר של יישום אינטרנט בסיסי וגם סרוולט יחיד.

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

למרבה המזל, ה createRootApplicationContext השיטה יכולה לחזור ריק. לפיכך, אנו יכולים לקבל אחת AbstractContextLoaderInitializer ורבים AbstractDispatcherServletInitializer יישומים שאינם יוצרים הקשר שורש. בתרחיש כזה, רצוי להזמין את המתחילים @להזמין בִּמְפוּרָשׁ.

כמו כן, שים לב לכך AbstractDispatcherServletInitializer רושם את ה- servlet בשם נתון (שולח) וכמובן, לא יכולים להיות לנו מספר סרוולטים באותו שם. אז עלינו לעקוף getServletName:

מחרוזת מוגנת @Override getServletName () {להחזיר "שיגור אחר"; }

5. א הקשר הורה וילד דוגמא

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

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

בואו נמשיך עם הדוגמה.

5.1. השירות המשותף

בדוגמה שלום שלום עלינו, הסתפקנו בשירות מברך פשוט יותר במקום בהתמדה:

חבילה com.baeldung.contexts.services; @ שירות בכיתה ציבורית GreeterService {@ ברכת ברכה פרטית למשאבים; ברכת מחרוזת ציבורית () {return greeting.getMessage (); }}

אנו נכריז על השירות בהקשר של יישום האינטרנט הבסיסי באמצעות סריקת רכיבים:

@Configuration @ ComponentScan (basePackages = {"com.baeldung.contexts.services"}) RootApplicationConfig בכיתה ציבורית {// ...}

אנו מעדיפים במקום זאת XML:

5.2. הבקרים

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

חבילה com.baeldung.contexts.normal; @Controller מחלקה ציבורית HelloWorldController {@ GreeterService פרטית אוטומטית greeterService; @RequestMapping (path = "/ welcome") ModelAndView הציבורי helloWorld () {String message = "

רגיל "+ greeterService.greet () +"

"; להחזיר ModelAndView חדש (" ברוך הבא "," הודעה ", הודעה);}} //" מאובטח "חבילת בקר com.baeldung.contexts.secure; מחרוזת הודעה ="

מאובטח "+ greeterService.greet () +"

";

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

5.3. ההקשרים של Dispatcher Servlet

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

// הקשר רגיל @Configuration @EnableWebMvc @ ComponentScan (basePackages = {"com.baeldung.contexts.normal"}) מחלקה ציבורית NormalWebAppConfig מיישם את WebMvcConfigurer {// ...} // הקשר "מאובטח" @ Configuration @ EnableWebMvc @ ComponentScan ( basePackages = {"com.baeldung.contexts.secure"}) מחלקה ציבורית SecureWebAppConfig מיישם את WebMvcConfigurer {// ...}

לחלופין, אם אנו מעדיפים זאת, ב- XML:

5.4. לשים את הכל ביחד

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

נגדיר AbstractContextLoaderInitializer לטעינת הקשר השורש:

@Override מוגן WebApplicationContext createRootApplicationContext () {AnnotationConfigWebApplicationContext rootContext = חדש AnnotationConfigWebApplicationContext (); rootContext.register (RootApplicationConfig.class); להחזיר rootContext; } 

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

@Override מוגן WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext normalWebAppContext = חדש AnnotationConfigWebApplicationContext (); normalWebAppContext.register (NormalWebAppConfig.class); להחזיר normalWebAppContext; } מחרוזת מוגנת @ Override [] getServletMappings () {להחזיר מחרוזת חדשה [] {"/ api / *"}; } מחרוזת מוגנת @Override getServletName () {להחזיר "שולח רגיל"; } 

ואז, "המאובטח", הטוען הקשר אחר וממופה לנתיב אחר:

@Override מוגן WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext secureWebAppContext = חדש AnnotationConfigWebApplicationContext (); secureWebAppContext.register (SecureWebAppConfig.class); להחזיר secureWebAppContext; } מחרוזת מוגנת @Override [] getServletMappings () {להחזיר מחרוזת חדשה [] {"/ s / api / *"}; } מחרוזת מוגנת @Override getServletName () {להחזיר "מאגר מאובטח"; }

וגמרנו! הרגע יישמנו את מה שנגענו בו בסעיפים הקודמים.

אנחנו יכולים לעשות את אותו הדבר גם עם web.xml, שוב רק על ידי שילוב החלקים עליהם דנו עד כה.

הגדר הקשר של יישום שורש:

  org.springframework.web.context.ContextLoaderListener 

הקשר משגר "רגיל":

 normal-webapp org.springframework.web.servlet.DispatcherServlet 1 normal-webapp / api / * 

ולבסוף, הקשר "מאובטח":

 מאובטח webapp org.springframework.web.servlet.DispatcherServlet 1 מאובטח webapp / s / api / * 

6. שילוב הקשרים מרובים

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

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

אפשרות אחת היא סריקת רכיבים, אותה אנו מסבירים במאמר אחר.

6.1. ייבוא ​​הקשר לשני

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

ייבוא ​​א @תְצוּרָה בכיתה בג'אווה:

@Configuration @Import (SomeOtherConfiguration.class) תצורה בכיתה ציבורית {...}

טוען סוג אחר של משאב, למשל, הגדרת הקשר XML ב- Java:

@Configuration @ImportResource ("classpath: basicConfigForPropertiesTwo.xml") תצורה בכיתה ציבורית config {...}

לבסוף, כולל קובץ XML בקובץ אחר:

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

7. יישומי רשת אתחול האביב

Spring Boot מגדיר באופן אוטומטי את רכיבי היישום, לכן, באופן כללי, יש פחות צורך לחשוב כיצד לארגן אותם.

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

יישומי רשת Spring Boot הפועלים במיכל מוטבע אינם מריצים כאלה WebApplicationInitializer לפי תכנון.

אם יהיה צורך בכך, נוכל לכתוב את אותו ההיגיון ב- a SpringBootServletInitializer או א ServletContextInitializer במקום זאת, בהתאם לאסטרטגיית הפריסה שנבחרה.

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

@Bean Servlet הציבורי myServlet () {...}

האובייקטים שהוגדרו כך ממופים על פי מוסכמות: פילטרים ממופים אוטומטית ל- / *, כלומר לכל בקשה. אם נרשום servlet יחיד, הוא ממופה ל /, אחרת כל servlet ממופה לשם שעועית שלו.

אם המוסכמות הנ"ל אינן עובדות עבורנו, אנו יכולים להגדיר א FilterRegistrationBean, סרבל רישום שעועית, אוֹ ServletListenerRegistrationBean במקום זאת. שיעורים אלה מאפשרים לנו לשלוט על ההיבטים המשובחים של הרישום.

8. מסקנות

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

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

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


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