JavaRush /בלוג Java /Random-HE /מ-Hello World ועד Spring Web MVC ומה הקשר של servlets לזה...
Viacheslav
רָמָה

מ-Hello World ועד Spring Web MVC ומה הקשר של servlets לזה

פורסם בקבוצה
מ-Hello World ועד Spring Web MVC ומה הקשר של servlets לזה - 1

מבוא

כידוע, ההצלחה של Java הגיעה דווקא הודות לאבולוציה של תוכנה השואפת להתחבר לרשת. לכן, ניקח את אפליקציית המסוף הרגילה " Hello World " כבסיס ונבין מה היא צריכה כדי להפוך לאפליקציית רשת מאפליקציית קונסולה. אז תחילה עליך ליצור פרויקט Java. מתכנתים הם אנשים עצלנים. בתקופה הפרהיסטורית, כשחלקם צדו ממותות, אחרים ישבו וניסו לא להתבלבל בכל מגוון ספריות ג'אווה ומבני ספריות. כדי שהמפתח יוכל לשלוט בתהליך יצירת האפליקציה, כדי שיוכל לכתוב בפשטות "אני רוצה ספרייה של גרסה 2 כזו או אחרת", הם הגיעו עם כלים מיוחדים - בניית מערכות. שני המפורסמים שבהם הם Maven ו- Gradle . למאמר זה נשתמש ב- Gradle. אם קודם לכן היינו צריכים ליצור את מבנה הספריות בעצמנו, כעת Gradle, באמצעות תוסף Gradle Init, מאפשר לנו ליצור פרויקט Java עם מבנה ספרייה ובסיס Main class בפקודה אחת: gradle init --type java-application פקודה זו מבצעת אתחול (init) עבור לנו יישום Java (Java-application) עם קונסולת Hello World. לאחר השלמתו, יופיע קובץ בספרייה - build.gradle . זהו סקריפט ה-build שלנו – כלומר, סקריפט מסוים ליצירת אפליקציה עם תיאור אילו פעולות צריך לבצע לשם כך. בואו נפתח אותו ונוסיף לו את השורה: jar.baseName = 'webproject' Gradle מאפשר לבצע פעולות שונות בפרויקט ופעולות אלו נקראות משימות . על ידי ביצוע פקודה (משימה) יווצר קובץ JAR gradle buildבספריית /build/libs . וכפי שניחשתם, שמו יהיה כעת webproject.jar . אבל אם נבצע java -jar ./build/libs/webproject.jar, נקבל שגיאה: no main manifest attribute. הסיבה לכך היא שעבור אפליקציית java צריך לצרף מניפסט מסוים - זה תיאור של איך לעבוד עם האפליקציה, איך לתפוס אותה. אז ה-JVM, שיפעיל את אפליקציית ה-java, יידע איזו מחלקה היא נקודת הכניסה לתוכנית ומידע אחר (לדוגמה, classpath). אם נסתכל מקרוב על התוכן של סקריפט ה-build, נראה את התוספים מחוברים. לדוגמה: apply plugin: 'java' אם נלך לדף Gradle Java Plugin , נוכל לראות שאנחנו יכולים להגדיר את המניפסט:
jar {
    manifest {
        attributes 'Main-Class': 'App'
    }
}
הכיתה הראשית, נקודת הכניסה לתוכנית, נוצרה עבורנו על ידי Gradle Init Plugin. וזה אפילו מצוין בפרמטר mainClassName. אבל זה לא התאים לנו, כי... הגדרה זו מתייחסת לפלאגין אחר, Gradle Application Plugin . אז יש לנו אפליקציית Java שמציגה את Hello World על המסך. יישום Java זה ארוז ב-JAR (Java ARchive). זה פשוט, מבוסס קונסולות, לא מעודכן. איך הופכים אותו לאפליקציית אינטרנט?
От Hello World до Spring Web MVC и при чём тут сервлеты - 2

Servlet API

כדי ש-Java תוכל לעבוד עם הרשת, מפרט בשם Servlet API הופיע עוד בימי קדם . מפרט זה הוא שמתאר אינטראקציה בין לקוח לשרת, קבלת הודעה מלקוח (לדוגמה, דפדפן) ושליחת תגובה (לדוגמה, עם טקסט של עמוד). מטבע הדברים, הרבה השתנה מאז, אבל הנקודה היא שכדי שאפליקציית Java תהפוך לאפליקציית אינטרנט, נעשה שימוש ב- Servlet API. כדי לא להעלות השערות מופרכות, בואו ניקח את המפרט הזה בדיוק: JSR-000340 JavaTM Servlet 3.1 . קודם כל, אנו מתעניינים ב"פרק 1: סקירה כללית ". הוא מתאר את המושגים הבסיסיים שעלינו להבין. ראשית, מהו סרבלט? הפרק " 1.1 מה זה Servlet? " אומר ש- Servlet הוא רכיב Java המנוהל על ידי קונטיינר ויוצר תוכן דינמי. כמו רכיבי Java אחרים, servlet הוא מחלקת Java המורכבת לתוך bytecode וניתן לטעון אותו לשרת אינטרנט באמצעות טכנולוגיית Java. חשוב ש-servlet ייצור אינטראקציה עם לקוח אינטרנט (לדוגמה, דפדפן) במסגרת פרדיגמת הבקשה/תגובה, המיושמת על ידי ה-Servlet Container. מסתבר שסרבלטים חיים באיזה מיכל של Servlet. מה זה? בפרק " 1.2 מה זה Servlet Container? " נאמר ש- Servlet Container הוא חלק משרת אינטרנט או שרת יישומים המספק שירותי רשת שדרכם נשלחות בקשות ונשלחות תגובות. מיכל Servlet ממש מנהל את מחזור החיים של servlets. כל מיכלי Servlet נדרשים לתמוך בפרוטוקול HTTP לכל הפחות, אך עשויים לתמוך באחרים. לדוגמה, HTTPS. חשוב גם שה-Servlet Container יוכל להטיל הגבלות הקשורות לאבטחה על הסביבה שבה מבוצעים servlets. חשוב גם שלפי " 10.6 Web Application Archive File " יש לארוז את יישום האינטרנט בקובץ WAR (Web ARchive). כלומר, עכשיו אנחנו צריכים להסיר את התוספים של הצנצנת והאפליקציה שלנו למשהו אחר. וזהו התוסף Gradle WAR . ובמקום jar.baseName ציין war.baseName כי מכיוון שאיננו משתמשים יותר בתוסף jar, הסרנו גם את הגדרות המניפסט. כשהשקנו את ה-JAR, היה צורך לומר ל-Java Virtual Machine (JVM) דרך המניפסט כיצד לעבוד עם האפליקציה שלנו. כי ה-JVM ניהל את זה. אפליקציית האינטרנט, ככל הנראה, מבוצעת על ידי שרת אינטרנט כלשהו. מסתבר שהוא צריך איכשהו להגיד לו איך לעבוד עם אפליקציית האינטרנט שלנו? ומסתבר שכן. ליישומי אינטרנט יש מניפסט מיוחד משלהם. זה נקרא Deployment Descriptor . חלק שלם מוקדש לו: " 14. מתאר פריסה ". יש קטע חשוב: " פרק 10:". זה מדבר על מהי יישום אינטרנט מנקודת המבט של Servlet API. לדוגמה, בפרק " 10.5 Directory Structure " מצוין היכן צריך להיות מתאר הפריסה: /WEB-INF/web.xmlהיכן למקם את WEB-INF? כפי שנאמר בתוסף Gradle WAR, הוא מוסיף פריסה חדשה : src/main/webappלכן בואו ניצור ספרייה כזו, בפנים ניצור ספריית WEB-INF ובפנים ניצור קובץ web.xml.חשוב שהספרייה נקרא WEB-INF, ולא META-INF! בוא נעתיק את זה מ-" 14.5.1 דוגמה בסיסית " לדוגמה XML:
От Hello World до Spring Web MVC и при чём тут сервлеты - 3
כפי שאנו יכולים לראות, מסמך XML משמש לתצורה. מסמך XML, על מנת להיחשב תקף (תקף), חייב להתאים ל"סכמה" כלשהי. אתה יכול לחשוב על זה כמעין ממשק למסמך XML. הסכימה מציינת אילו אלמנטים יכולים להיות במסמך XML, איזה סוג של נתונים יכול להגדיר את האלמנט, הסדר, הדרישה והיבטים אחרים. הדוגמה שהועתקה מהתיעוד מציינת את גרסה 2.5, אך אנו רוצים להשתמש בגרסה 3.1. באופן טבעי, המפרט השתנה ככל שהגרסאות השתנו ונוספו תכונות חדשות. לכן, עליך להשתמש בסכימה שונה מזו המשמשת עבור גרסה 2.5 (web-app_2_5.xsd). באיזו סכימה עלי להשתמש עבור גרסה 3.1? התיעוד יעזור לנו בכך, פרק " 14.3 Deployment Descriptor ", שאומר כלומר specification is available at http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd , עלינו להחליף את הקישור לסכימה ב-xsd שצוין בכל מקום, לא לשכוח לשנות אותו version="2.5"ל-3.1, וגם לשנות את מרחב השמות בכל מקום ( xmlns וב-xsi:schemaLocation). הם מציינים באיזה מרחב שמות נעבוד (בלשון המעטה, באילו שמות אלמנטים נוכל להשתמש). אם תפתח את קובץ הסכימה, ה-targetNamespace יכיל את אותו מרחב שמות שעלינו לציין:
От Hello World до Spring Web MVC и при чём тут сервлеты - 4
כזכור, בקובץ Manifest of the Jar כתבנו באיזה מחלקה אנחנו רוצים להשתמש. מה לעשות כאן? כאן עלינו לציין באיזו מחלקת servlet אנו רוצים להשתמש כאשר אנו מקבלים בקשה מלקוח אינטרנט. את התיאור ניתן לקרוא בפרק " 14.4 תרשים מתאר פריסה ". זה ייראה כך:
От Hello World до Spring Web MVC и при чём тут сервлеты - 5
הכל פשוט כאן. ה-serverlet מוכרז, ואז הוא ממופה לתבנית מסוימת. במקרה זה, ב-/app. כאשר התבנית מבוצעת, שיטת ה-servlet תבוצע. למען היופי, יש להעביר את מחלקת האפליקציה לחבילה, ולא לשכוח לתקן את תצורת ה-xml. אבל זה לא הכל. האפליקציה חייבת להיות servlet. מה זה אומר להיות סרבל? זה אומר שעלינו לרשת מ- HttpServlet . דוגמה ניתן לראות בפרק " 8.1.1 @WebServlet ". לפי זה, כיתת האפליקציה שלנו תיראה כך:
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class App extends HttpServlet {
    public String getGreeting() {
        return "Hello world.";
    }

	public void doGet(HttpServletRequest request, HttpServletResponse response) {
		response.setContentType("text/html");
		try {
			response.getWriter().println(getGreeting());
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}
}
אבל הפרויקט שלנו עדיין לא מוכן. מכיוון שאנו תלויים כעת בגרסה 3.1 של Servlet API. זה אומר שבסקריפט ה-build שלנו אנחנו צריכים לציין תלות ב-servlet API. ה-JVM צריך לדעת שמה שכתבת בקוד נכון וכיצד להשתמש בו. כזכור, המפרט הוא בעצם רק ממשקים שמתארים איך הכל צריך לעבוד. והיישום נמצא בצד שרת האינטרנט. לכן, ללא ה-API של Servlet תהיה מצא את הספרייה הנדרשת ב-Maven Central: javax.servlet-api . והוסף ערך לבלוק התלות . במאגר של מייבן, כפי שראית, כתוב מסופק. לפני השימוש בתלות, עליך לציין היקף. ל- Gradle אין היקף בשם "סופק", אבל יש לו היקף " קומפילציה בלבד ". לכן, נציין: providedCompile 'javax.servlet:javax.servlet-api:3.1.0' אוף, נראה שהכל בסדר? Gradle Build יבנה את הפרויקט שלנו לקובץ WAR. ומה עלינו לעשות עם זה הלאה? ראשית, אנחנו צריכים שרת אינטרנט. בגוגל אנו כותבים " רשימת שרת אינטרנט java list " ורואים רשימה של שרתי אינטרנט. בואו לבחור מתוך רשימה זו, למשל, TomCat . עבור לאתר Apache Tomcat , הורד את הגרסה העדכנית ביותר (כרגע גרסה 9) כארכיון zip (אם עבור Windows). פרק את זה לתוך ספרייה כלשהי. היי, יש לנו שרת אינטרנט. מספריית שרת האינטרנט בספריית המשנה bin , אנו מבצעים את Catalina משורת הפקודה ורואים את האפשרויות הזמינות. בא נעשה: catalina start. לכל שרת אינטרנט יש ספרייה ששרת האינטרנט עוקב אחריה. אם קובץ יישום אינטרנט מופיע שם, שרת האינטרנט מתחיל להתקין אותו. התקנה זו נקראת פריסה או פריסה . כן כן, זו הסיבה " מתאר פריסה ". כלומר, כיצד לפרוס נכון את האפליקציה. ב-Tomcat ספרייה זו היא אפליקציות אינטרנט . בוא נעתיק שם את המלחמה שעשינו באמצעות בנייה מדורגת. אחרי זה, ביומן נראה משהו כמו: Deployment of web application archive [tomcat\webapps\webproject.war] has finished in [время] ms כדי להבין אפילו יותר טוב, בספריית tomcat נערוך את הקובץ \conf\tomcat-users.xml, ונוסיף את השורות הבאות:
От Hello World до Spring Web MVC и при чём тут сервлеты - 6
כעת נפעיל מחדש את השרת (catalina stop, catalina start) ונעבור לכתובת. http://127.0.0.1:8080/manager כאן נראה את הנתיבים של כל האפליקציות. סביר להניח שה-webproject שלנו קיבל את הנתיב /webproject. מה זה הנתיב הזה? המפרט בפרק " 10.1 יישומי אינטרנט בתוך שרתי אינטרנט " קובע שיישום אינטרנט משויך לנתיב כלשהו בתוך היישום (במקרה זה, /webproject). כל הבקשות דרך נתיב זה ישויכות לאותו ServletContext. נתיב זה נקרא גם contextRoot . ולפי " 10.2 קשר ל-ServletContext ", מיכל ה-servlet מקשר בין יישום האינטרנט וה-ServletContext אחד לאחד. כלומר, לכל יישום אינטרנט יש ServletContext משלו. מהו ServletContext ? כפי שהמפרט קובע, ServletContext הוא אובייקט המספק ל-servlets "תצוגה של היישום " שבו הם פועלים. ההקשר של Servlet מתואר בפירוט רב יותר בפרק 4 של מפרט ה-API של Servlet. באופן מפתיע, ה-API של Servlet בגרסה 3.1 כבר לא דורשת ש-web.xml יהיה נוכח. לדוגמה, אתה יכול להגדיר servlet באמצעות הערות:
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/app2")
public class App2 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");
        response.getWriter().println("app2");
    }
}
מומלץ גם בנושא: " ראיון Java EE - JEE Servlet API (שאלות ותשובות) ". אז, יש לנו Servlet - הוא אחראי על איזו תגובה לתת ללקוח האינטרנט. יש לנו ServletContainer שמקבל בקשות מהמשתמש, תואם את הנתיב שאליו ניגשים לנתיב ל-servlet, ואם נמצא התאמה, מבצע את ה-Servlet. בסדר גמור. איזה מקום תופס האביב בתמונת העולם הזו ?

Spring Web MVC

נהדר, יש לנו יישום אינטרנט. עכשיו אנחנו צריכים לחבר את אביב. איך אנחנו יכולים לעשות את זה? ראשית, עליך להבין כיצד לחבר נכון את Spring לפרויקט שלך. מסתבר שקודם לכן ניתן היה לעשות זאת בהתאם לתיעוד פרויקט פלטפורמת אביב , אך כעת " הפלטפורמה תגיע לסוף חייה הנתמכים ב-9 באפריל 2019 ", כלומר, לא כדאי להשתמש בו, כי בקרוב לא תהיה תמיכה בו. הדרך היחידה לצאת היא " מומלץ למשתמשי הפלטפורמה להתחיל להשתמש בניהול התלות של Spring Boot ". לכן, בואו נעבור לתיעוד Spring Boot . הרשו לי להבהיר שאיננו משתמשים ב-Spring Boot עצמו, אלא רק בניהול תלות מ-Spring Boot. כלומר, פרויקט Spring Boot יכול לספק ידע לגבי אילו גרסאות של ספריות להשתמש (כולל Spring MVC). שם נמצא 3.2. שימוש בניהול התלות של Spring Boot במנותק . על פי התיעוד, הוסף את הדברים הבאים לסקריפט הבנייה:
plugins {
    id 'org.springframework.boot' version '2.0.4.RELEASE' apply false
}
apply plugin: 'io.spring.dependency-management'
ו
dependencyManagement {
    imports {
        mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
    }
}
כפי שאתה יכול לראות, ציינו apply false, כלומר. אנחנו לא משתמשים ב-Spring Boot עצמו, אבל אנחנו משתמשים בניהול תלות משם. ניהול תלות זה נקרא גם BOM - " Bill Of Materials ". כעת אנו מוכנים לחבר את פרויקט Spring Web MVC עצמו. Spring Web MVC הוא חלק מפרויקט Spring Framework ואנו מעוניינים בקטע " Web Servlet ". בואו נוסיף את התלות לסקריפט הבנייה: compile 'org.springframework:spring-webmvc'. כפי שאנו יכולים לראות, אנו מגדירים scope compile, כי שרת האינטרנט לא מספק לנו את Spring. הפרויקט שלנו נאלץ לכלול את ספריית אביב בתוך עצמו. לאחר מכן, חשוב לנו לקרוא את הסעיף " 1.2. DispatcherServlet ", בו נאמר ש- Spring MVC בנוי סביב תבנית " בקר קדמי ", שבו יש איזשהו סרבלט מרכזי המספק תצורה והאצלה לרכיבים אחרים . ניתן לתרגם את המשלח כשולח. אז, קודם כל, ב-web.xml אנו מצהירים:
От Hello World до Spring Web MVC и при чём тут сервлеты - 7
כפי שאנו יכולים לראות, זהו למעשה Listener רגיל המוגדר במפרט Servlet API. ליתר דיוק, זהו ServletContextListener, כלומר, הוא מופעל לאתחל את ה-Servlet Context עבור אפליקציית האינטרנט שלנו. לאחר מכן, עליך לציין הגדרה שתגיד ל-Spring היכן ממוקמת תצורת ה-xml המיוחדת שלו עם ההגדרות:
От Hello World до Spring Web MVC и при чём тут сервлеты - 8
כפי שניתן לראות, זוהי רק הגדרה רגילה המאוחסנת ברמת Servlet Context, אך אשר תשמש את Spring בעת אתחול ההקשר של היישום. כעת עליך להכריז, במקום כל ה-servlets, על שדר אחד בודד שמפיץ את כל שאר הבקשות.
От Hello World до Spring Web MVC и при чём тут сервлеты - 9
ואין כאן קסם. אם נסתכל, זה HttpServlet, בדיוק שבו Spring עושה הרבה דברים שהופכים אותו למסגרת. כל מה שנותר הוא לתאם (מפה) תבנית כתובת URL ספציפית עם ה-servlet:
От Hello World до Spring Web MVC и при чём тут сервлеты - 10
הכל כמו שעשינו קודם. עכשיו בואו ניצור משהו ששרת האינטרנט שלנו צריך להציג. לדוגמה, בואו ניצור תת-ספריית דפים ב-WEB-INF שלנו, ויהיה קובץ hello.jsp. התוכן יכול להיות הפרימיטיבי ביותר. לדוגמה, בתוך תגי html יש תגית h1 עם הטקסט " Hello World ". ואל תשכח ליצור את הקובץ applicationContext.xmlשציינו קודם לכן. ניקח דוגמה מתיעוד האביב: " 1.10.3. זיהוי אוטומטי של מחלקות ורישום הגדרות שעועית ".
От Hello World до Spring Web MVC и при чём тут сервлеты - 11
כי אנו מאפשרים זיהוי אוטומטי בדרך זו, כעת נוכל ליצור 2 מחלקות (הן ייחשבו ל-Spring Beans עקב השימוש בהערות Spring מיוחדות), אשר Spring תיצור כעת בעצמה ותתאים אישית את האפליקציה שלנו בעזרתן:
  1. תצורת אינטרנט למשל תצורה בסגנון Java:

    @Configuration
    @EnableWebMvc
    public class WebConfig implements WebMvcConfigurer {
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            registry.jsp("/WEB-INF/pages/", ".jsp");
        }
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable();
        }
    }

    דוגמה זו מתוארת בתיעוד Spring Framework: " 1.11. MVC Config ".

    כאן אנו רושמים ViewResolver, שיעזור לקבוע היכן ממוקמים דפי ה-jsp. השיטה השנייה מבטיחה ש"שרת ברירת המחדל " מופעל.

    אתה יכול לקרוא עוד על כך כאן: " מהו הצורך והשימוש ב-default-servlet-handler ".

  2. בקר HelloController לתיאור מיפוי של בקשות ל-JSP ספציפי

    @Controller
    public class HelloController {
        @GetMapping("/hello")
        public String handle(Model model) {
            return "hello";
        }
    }

    כאן השתמשנו בהערת @Controller המתוארת בתיעוד בפרק " 1.4. בקרים מוערים ".

כעת, כאשר היישום שלנו נפרס, כאשר אנו שולחים בקשה /webproject/hello(כאשר /webproject הוא שורש ההקשר), ה-DispatcherServlet יעובד תחילה. הוא, בתור השולח הראשי, יקבע שאנחנו /* תואמים את הבקשה הנוכחית, מה שאומר שה-DispatcherServlet חייב לעשות משהו. לאחר מכן הוא יעבור על כל המיפויים שהוא מוצא. הוא יראה שיש HelloController עם שיטת handle שממפה ל-/hello ויפעיל אותה. שיטה זו תחזיר את הטקסט "שלום". הטקסט הזה יתקבל על ידי ה-ViewResolver, שיגיד לשרת היכן לחפש את קבצי ה-jsp שצריכים להיות מוצגים ללקוח. כך, הלקוח יקבל בסופו של דבר את הדף היקר מאוד.

סיכום

אני מקווה שיהיה ברור מהמאמר שהמילה "הקשר" לא מפחידה. המפרט הזה מתגלה כמועיל מאוד. והתיעוד הוא ידידנו, לא האויב שלנו. אני מקווה שיהיה ברור על מה מבוסס Spring, איך הוא מתחבר ומה הקשר לזה של Servlet API.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION