حقن التبعية في Spring

اليوم سنلقي نظرة على إدخال التبعية في Spring. المفاهيم الأساسية لـ “إطار العمل Spring” هي “إدخال التبعية” و “البرمجة الموجهة للجوانب“. لقد كتبت في وقت سابق حول إدخال التبعية في جافا وكيف يمكننا استخدام إطار العمل Google Guice لتشغيل هذه العملية تلقائيًا في تطبيقاتنا.

إدخال التبعية في Spring

يهدف هذا البرنامج التعليمي إلى توفير تفاصيل حول مثال حقن الاعتماد في Spring باستخدام تكوين قائم على التعليقات وتكوين ملف XML. سأقدم أيضًا مثالًا على حالة اختبار JUnit للتطبيق، حيث أن سهولة الاختبار تعد واحدة من الفوائد الرئيسية لحقن الاعتماد. لقد قمت بإنشاء مشروع maven باسم spring-dependency-injection والذي يظهر هيكله كما هو موضح في الصورة أدناه. دعونا ننظر إلى كل من العناصر واحدة تلو الأخرى.

حقن الاعتماد في Spring – تبعيات Maven

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 التعليقة فقط على الصف وسياسة الاحتفاظ بها هي Runtime. إذا لم تكن مألوفًا بسياسة احتفاظ التعليقات، فأنصحك بقراءة دليل تعليقات جافا.
  • التعليقة @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.

تكوين حقن الإعتماد في الربيع باستخدام التعليقات

بالنسبة لتكوين البرمجة بناءً على التعليقات، نحتاج إلى كتابة فئة Configurator التي سيتم استخدامها لحقن تنفيذ الفعل الفعلي إلى خاصية المكون.

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 لإبلاغ الربيع بأنها فئة تكوين.
  • يُستخدم تعليق @ComponentScan مع تعليق @Configuration لتحديد الحزم للبحث عن فئات المكون.
  • يُستخدم تعليق @Bean لإبلاغ إطار الربيع بأنه يجب استخدام هذا الطريق للحصول على تنفيذ الفعل لحقنه في فئات المكون.

لنكتب برنامجًا بسيطًا لاختبار مثال حقن الإعتماد في الربيع بناءً على التعليقات الخاصة بنا.

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 فئة كمعامل سيتم استخدامها للحصول على تنفيذ الفاصلة الفعلية لحقنها في فئات المكونات. تعود طريقة 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 أعلاه يحتوي على تكوين لكل من حقن التبعية بناءً على البناء وحسب الضبط. نظرًا لأن MyXMLApplication تستخدم طريقة setter للحقن ، يحتوي تكوين البن مكون على عنصر 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

لاحظ أن بعض الإخراجات مكتوبة بواسطة إطار العمل Spring. نظرًا لأن إطار العمل Spring يستخدم log4j لأغراض التسجيل ولم أقم بتكوينه، فإن الإخراج يتم كتابته إلى وحدة التحكم.

حالة اختبار وحدة الاختبار لحقن الاعتماديات في Spring

أحد أهم فوائد حقن الاعتماديات في Spring هو سهولة وجود فئات خدمة مزيفة بدلاً من استخدام الخدمات الفعلية. لذا قمت بدمج كل ما تعلمته من الأعلى وكتبت كل شيء في فئة اختبار وحدة JUnit 4 واحدة لحقن الاعتماديات في Spring.

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() بالحصول على كائن المكون من السياق واختباره. هل تتساءل كيف يقوم إطار العمل سبرينغ بالتضمين التلقائي واستدعاء الطرق التي لا تعرفها إطار العمل سبرينغ. يتم ذلك باستخدام تعكس جافا بشكل كثيف يمكننا من خلاله تحليل وتعديل سلوكيات الفئات في وقت التشغيل.

تنزيل مشروع حقن الاعتماديات في سبرينغ

قم بتنزيل مشروع حقن الاعتماديات في سبرينغ العيني من الرابط أعلاه وتفاعل معه لتتعلم المزيد.

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