הטפסים התלויים בקיץ

היום נתעקב אחרי הטמעת תלות ב- Spring. העקרונות המרכזיים של מסגרת Spring הם "הטמעת תלות" ו"תכנות מונחה לאובייקט". כבר כתבתי קודם על הטמעת תלות ב-Java ואיך ניתן להשתמש במסגרת Google Guice כדי לאוטומטизציה של התהליך הזה ביישומינו.

הטמעת תלות ב-Spring

המדריך הזה מיועד לספק פרטים אודות דוגמת ה-Dependency Injection של Spring עם הגדרה מבוססת אנוטציות והגדרת קובץ XML. אני גם אספק דוגמה למקרה מבחן JUnit עבור היישום, מאחר ובדיקות קלות הן אחת מהיתרונות המרכזיים של ה-Dependency Injection. יצרתי את פרויקט ה-Maven בשם spring-dependency-injection שמבנהו נראה כמו התמונה למטה. בואו נבחן כל אחד מהרכיבים לפי סדרם.

התקנות Maven – Dependency Injection של Spring

I have added Spring and JUnit maven dependencies in pom.xml file, final pom.xml code is below.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.journaldev.spring</groupId>
	<artifactId>spring-dependency-injection</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.0.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

</project>

הגרסה היציבה הנוכחית של מסגרת ה-Spring היא 4.0.0.RELEASE והגרסה הנוכחית של JUnit היא 4.8.1, אם אתה משתמש בגרסאות אחרות ייתכן שיהיה סיכוי קטן שהפרויקט יצטרך שינויים מסוימים. אם תבנה את הפרויקט, תשים לב שמספר ג'ארים נוספים מתווספים להתקנו של Maven בשל תלותיות המעבר, בדומה לתמונה למעלה.

הטפסים על תלות בהזנקת קיץ – כיתות שירות

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

package com.journaldev.spring.di.services;

public interface MessageService {

	boolean sendMessage(String msg, String rec);
}

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

package com.journaldev.spring.di.services;

public class EmailService implements MessageService {

	public boolean sendMessage(String msg, String rec) {
		System.out.println("Email Sent to "+rec+ " with Message="+msg);
		return true;
	}

}
package com.journaldev.spring.di.services;

public class TwitterService implements MessageService {

	public boolean sendMessage(String msg, String rec) {
		System.out.println("Twitter message Sent to "+rec+ " with Message="+msg);
		return true;
	}

}

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

הטפסים על תלות בהזנקת קיץ – כיתות רכיב

נכתוב כיתת צרכן עבור השירותים שנמצאים למעלה. יהיו לנו שני כיתות צרכנים – אחת עם הערות Spring להזנקת תלות והשנייה בלעדי עם הגדרות הזנקת תלות בקובץ התצורה של XML.

package com.journaldev.spring.di.consumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

import com.journaldev.spring.di.services.MessageService;

@Component
public class MyApplication {

	// הזנקת תלות בהתבסס על שדה
	//@Autowired
	private MessageService service;
	
// הזנקת תלות בהתבסס על בנאי	
//	@Autowired
//	public MyApplication(MessageService svc){
//		this.service=svc;
//	}
	
	@Autowired
	public void setService(MessageService svc){
		this.service=svc;
	}
	
	public boolean processMessage(String msg, String rec){
		//קצת קסם כמו אימות, רישום וכו'
		return this.service.sendMessage(msg, rec);
	}
}

כמה נקודות חשובות אודות מחלקת MyApplication:

  • @Component הוספת ההערה מתווספת למחלקה, כך שכאשר מסגרת ה-Spring תסרוק עבור הרכיבים, המחלקה תיטול כרכיב. @Component ההערה יכולה להיחיל רק למחלקה ומדוד השמירה שלה הוא בזמן ריצה. אם אתה לא מכיר היטב את מדידת השמירה של ההערות, אני ממליץ לך לקרוא מדריך להערות Java.
  • @Autowired ההערה משמשת לעדכן את Spring שאוטובווירינג נדרש. זה יכול להיות מחובר לשדה, בנאי ושיטות. ההערה הזו מאפשרת לנו ליישם הזרקת תלות מבוססת בנאי, מבוססת שדה או מבוססת שיטה ברכיבים שלנו.
  • בדוגמה שלנו, אני משתמש בהזרקת תלות מבוססת שיטה. תוכל לבטל את ההערה בשורת הבנאי כדי לעבור להזרקת תלות מבוססת בנאי.

כעת בוא נכתוב מחלקה דומה בלעדי הערות.

package com.journaldev.spring.di.consumer;

import com.journaldev.spring.di.services.MessageService;

public class MyXMLApplication {

	private MessageService service;

	//הזרקת תלות מבוססת בנאי
//	public MyXMLApplication(MessageService svc) {
//		this.service = svc;
//	}
	
	//הזרקת תלות מבוססת סטר
	public void setService(MessageService svc){
		this.service=svc;
	}

	public boolean processMessage(String msg, String rec) {
		// מספר קסם כמו אימות, לוגים וכו'
		return this.service.sendMessage(msg, rec);
	}
}

A simple application class consuming the service. For XML based configuration, we can use implement either constructor-based spring dependency injection or method-based spring dependency injection. Note that method-based and setter-based injection approaches are same, it’s just that some prefer calling it setter-based and some call it method-based.

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

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

package com.journaldev.spring.di.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.journaldev.spring.di.services.EmailService;
import com.journaldev.spring.di.services.MessageService;

@Configuration
@ComponentScan(value={"com.journaldev.spring.di.consumer"})
public class DIConfiguration {

	@Bean
	public MessageService getMessageService(){
		return new EmailService();
	}
}

כמה נקודות חשובות הקשורות למחלקה שמופיעה לעיל הן:

  • @Configuration הערה משמשת להודיע ל- Spring כי זו מחלקת הגדרה.
  • @ComponentScan הערה משמשת עם @Configuration כדי לציין את החבילות לחיפוש אחר מחלקות רכיבים.
  • @Bean הערה משמשת כדי להודיע למסגרת Spring כי שיטה זו צריכה לשמש לקבלת המימוש של הפועל להכנסה במחלקות רכיב.

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

package com.journaldev.spring.di.test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.journaldev.spring.di.configuration.DIConfiguration;
import com.journaldev.spring.di.consumer.MyApplication;

public class ClientApplication {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DIConfiguration.class);
		MyApplication app = context.getBean(MyApplication.class);
		
		app.processMessage("Hi Pankaj", "[email protected]");
		
		//סגירת ההקשר
		context.close();
	}

}

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

Dec 16, 2013 11:49:20 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3067ed13: startup date [Mon Dec 16 23:49:20 PST 2013]; root of context hierarchy
Email Sent to [email protected] with Message=Hi Pankaj
Dec 16, 2013 11:49:20 PM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3067ed13: startup date [Mon Dec 16 23:49:20 PST 2013]; root of context hierarchy

תלות מערכת Spring עם הגדרת XML

ניצור קובץ הגדרת Spring עם הנתונים הבאים, שם הקובץ יכול להיות כלשהו. קוד applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

<!-- 
	<bean id="MyXMLApp" class="com.journaldev.spring.di.consumer.MyXMLApplication">
		<constructor-arg>
			<bean class="com.journaldev.spring.di.services.TwitterService" />
		</constructor-arg>
	</bean>
-->
	<bean id="twitter" class="com.journaldev.spring.di.services.TwitterService"></bean>
	<bean id="MyXMLApp" class="com.journaldev.spring.di.consumer.MyXMLApplication">
		<property name="service" ref="twitter"></property>
	</bean>
</beans>

שימו לב שה-XML לעיל מכיל הגדרה עבור שני סוגי הזרימה של תלות מערכת Spring – על פי בני קונסטרוקטור ועל פי הגדרת מחלקת המערכת. מאחר ש-MyXMLApplication משתמש בשיטת הקביעה להזרקה, ההגדרות של הבון מכילות אלמנט property להזרקה. להזרקה על פי בני קונסטרוקטור, עלינו להשתמש באלמנט constructor-arg. קובץ ה-XML להגדרה מונח בתיקיית המקור, כך שהוא יהיה בתיקיית המחלקות לאחר הבנייה. בואו נראה כיצד להשתמש בהגדרת מבוססת XML עם תוכנית פשוטה.

package com.journaldev.spring.di.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.journaldev.spring.di.consumer.MyXMLApplication;

public class ClientXMLApplication {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		MyXMLApplication app = context.getBean(MyXMLApplication.class);

		app.processMessage("Hi Pankaj", "[email protected]");

		// סגור את ההקשר
		context.close();
	}

}

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

Dec 17, 2013 12:01:23 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4eeaabad: startup date [Tue Dec 17 00:01:23 PST 2013]; root of context hierarchy
Dec 17, 2013 12:01:23 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
Twitter message Sent to [email protected] with Message=Hi Pankaj
Dec 17, 2013 12:01:23 AM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4eeaabad: startup date [Tue Dec 17 00:01:23 PST 2013]; root of context hierarchy

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

בדיקת JUnit להתמקדות בהטמעה של ספרינג

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

package com.journaldev.spring.di.test;

import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.journaldev.spring.di.consumer.MyApplication;
import com.journaldev.spring.di.services.MessageService;

@Configuration
@ComponentScan(value="com.journaldev.spring.di.consumer")
public class MyApplicationTest {
	
	private AnnotationConfigApplicationContext context = null;

	@Bean
	public MessageService getMessageService() {
		return new MessageService(){

			public boolean sendMessage(String msg, String rec) {
				System.out.println("Mock Service");
				return true;
			}
			
		};
	}

	@Before
	public void setUp() throws Exception {
		context = new AnnotationConfigApplicationContext(MyApplicationTest.class);
	}
	
	@After
	public void tearDown() throws Exception {
		context.close();
	}

	@Test
	public void test() {
		MyApplication app = context.getBean(MyApplication.class);
		Assert.assertTrue(app.processMessage("Hi Pankaj", "[email protected]"));
	}

}

המחלקה מסומנת עם @Configuration ו־@ComponentScan מסיבה ש־getMessageService() מחזיר את המימוש המוטבע של MessageService. לכן getMessageService() מסומן עם האנוטציה @Bean. מאחר שאני בודק את מחלקת MyApplication שהוגדרה באמצעות אנוטציות, אני משתמש ב־AnnotationConfigApplicationContext ויוצר אובייקט שלו ב־setUp() מתודה. ההקשר נסגר ב־tearDown() מתודה. קוד המתודה test() פשוט מקבל את אובייקט הרכיב מההקשר ובודק אותו. האם אתה תהומה כיצד Spring Framework עושה את ה־autowiring וקורא למתודות שאינן ידועות ל־Spring Framework. זה נעשה עם שימוש כבד ב־Java Reflection שאנו יכולים להשתמש בו כדי לנתח ולשנות את ההתנהגויות של המחלקות בזמן ריצה.

הורד פרויקט הזרמת תלות Spring

הורד את פרויקט הזרמת התלות (DI) של Spring הדוגמא מהכתובת URL למעלה ושחק איתו כדי ללמוד עוד.

Source:
https://www.digitalocean.com/community/tutorials/spring-dependency-injection