웹사이트 검색

Java 종속성 주입 - DI 디자인 패턴 예제 자습서


Java 종속성 주입 디자인 패턴을 사용하면 하드 코딩된 종속성을 제거하고 응용 프로그램을 느슨하게 결합하고 확장 가능하며 유지 관리할 수 있습니다. 컴파일 시간에서 런타임으로 종속성 해결을 이동하기 위해 Java에서 종속성 주입을 구현할 수 있습니다.

자바 의존성 주입

Java 종속성 주입은 이론으로 이해하기 어려운 것 같으므로 간단한 예를 들어 종속성 주입 패턴을 사용하여 응용 프로그램에서 느슨한 결합 및 확장성을 달성하는 방법을 살펴보겠습니다. 이메일을 보내기 위해 EmailService를 사용하는 애플리케이션이 있다고 가정해 보겠습니다. 일반적으로 아래와 같이 구현합니다.

package com.journaldev.java.legacy;

public class EmailService {

	public void sendEmail(String message, String receiver){
		//logic to send email
		System.out.println("Email sent to "+receiver+ " with Message="+message);
	}
}

EmailService 클래스는 수신자 이메일 주소로 이메일 메시지를 보내는 로직을 보유합니다. 우리의 애플리케이션 코드는 아래와 같습니다.

package com.journaldev.java.legacy;

public class MyApplication {

	private EmailService email = new EmailService();
	
	public void processMessages(String msg, String rec){
		//do some msg validation, manipulation logic etc
		this.email.sendEmail(msg, rec);
	}
}

MyApplication 클래스를 사용하여 이메일 메시지를 보내는 클라이언트 코드는 다음과 같습니다.

package com.journaldev.java.legacy;

public class MyLegacyTest {

	public static void main(String[] args) {
		MyApplication app = new MyApplication();
		app.processMessages("Hi Pankaj", "pankaj@abc.com");
	}

}

언뜻 보기에 위의 구현에는 아무런 문제가 없어 보입니다. 그러나 위의 코드 논리에는 특정 제한 사항이 있습니다.

  • MyApplication 클래스는 이메일 서비스를 초기화한 후 사용하는 역할을 합니다. 이로 인해 하드 코딩된 종속성이 발생합니다. 나중에 다른 고급 이메일 서비스로 전환하려면 MyApplication 클래스에서 코드를 변경해야 합니다. 이로 인해 응용 프로그램을 확장하기 어렵고 전자 메일 서비스가 여러 클래스에서 사용되는 경우 훨씬 더 어려워집니다.
  • SMS나 Facebook 메시지와 같은 추가 메시징 기능을 제공하도록 응용 프로그램을 확장하려면 이를 위한 다른 응용 프로그램을 작성해야 합니다. 여기에는 애플리케이션 클래스와 클라이언트 클래스의 코드 변경도 포함됩니다.
  • 애플리케이션이 이메일 서비스 인스턴스를 직접 생성하기 때문에 애플리케이션을 테스트하는 것은 매우 어려울 것입니다. 테스트 클래스에서 이러한 개체를 조롱할 수 있는 방법이 없습니다.

이메일 서비스를 인수로 요구하는 생성자를 가짐으로써 MyApplication 클래스에서 이메일 서비스 인스턴스 생성을 제거할 수 있다고 주장할 수 있습니다.

package com.journaldev.java.legacy;

public class MyApplication {

	private EmailService email = null;
	
	public MyApplication(EmailService svc){
		this.email=svc;
	}
	
	public void processMessages(String msg, String rec){
		//do some msg validation, manipulation logic etc
		this.email.sendEmail(msg, rec);
	}
}

그러나 이 경우 클라이언트 응용 프로그램이나 테스트 클래스에 전자 메일 서비스를 초기화하도록 요청하는 것은 좋은 디자인 결정이 아닙니다. 이제 위 구현의 모든 문제를 해결하기 위해 자바 종속성 주입 패턴을 적용하는 방법을 살펴보겠습니다. Java의 종속성 주입에는 최소한 다음이 필요합니다.

  1. 서비스 구성 요소는 기본 클래스 또는 인터페이스로 설계되어야 합니다. 서비스에 대한 계약을 정의하는 인터페이스나 추상 클래스를 선호하는 것이 좋습니다.
  2. 소비자 클래스는 서비스 인터페이스 측면에서 작성해야 합니다.
  3. 서비스를 초기화한 다음 소비자 클래스를 초기화할 인젝터 클래스.

Java 종속성 주입 - 서비스 구성 요소

우리의 경우 서비스 구현을 위한 계약을 선언할 MessageService를 가질 수 있습니다.

package com.journaldev.java.dependencyinjection.service;

public interface MessageService {

	void sendMessage(String msg, String rec);
}

이제 위의 인터페이스를 구현하는 이메일 및 SMS 서비스가 있다고 가정해 보겠습니다.

package com.journaldev.java.dependencyinjection.service;

public class EmailServiceImpl implements MessageService {

	@Override
	public void sendMessage(String msg, String rec) {
		//logic to send email
		System.out.println("Email sent to "+rec+ " with Message="+msg);
	}

}
package com.journaldev.java.dependencyinjection.service;

public class SMSServiceImpl implements MessageService {

	@Override
	public void sendMessage(String msg, String rec) {
		//logic to send SMS
		System.out.println("SMS sent to "+rec+ " with Message="+msg);
	}

}

의존성 주입 자바 서비스가 준비되었으며 이제 소비자 클래스를 작성할 수 있습니다.

Java 종속성 주입 - 서비스 소비자

소비자 클래스에 대한 기본 인터페이스가 필요하지는 않지만 소비자 클래스에 대한 계약을 선언하는 Consumer 인터페이스가 있습니다.

package com.journaldev.java.dependencyinjection.consumer;

public interface Consumer {

	void processMessages(String msg, String rec);
}

내 소비자 클래스 구현은 다음과 같습니다.

package com.journaldev.java.dependencyinjection.consumer;

import com.journaldev.java.dependencyinjection.service.MessageService;

public class MyDIApplication implements Consumer{

	private MessageService service;
	
	public MyDIApplication(MessageService svc){
		this.service=svc;
	}
	
	@Override
	public void processMessages(String msg, String rec){
		//do some msg validation, manipulation logic etc
		this.service.sendMessage(msg, rec);
	}

}

애플리케이션 클래스는 서비스를 사용하고 있습니다. 더 나은 "관심의 분리\로 이어지는 서비스를 초기화하지 않습니다. 또한 서비스 인터페이스를 사용하면 MessageService를 조롱하여 애플리케이션을 쉽게 테스트하고 컴파일 시간이 아닌 런타임에 서비스를 바인딩할 수 있습니다. . 이제 우리는 서비스와 소비자 클래스를 초기화할 자바 종속성 인젝터 클래스를 작성할 준비가 되었습니다.

Java 종속성 주입 - 인젝터 클래스

Consumer 클래스를 반환하는 메서드 선언이 있는 MessageServiceInjector 인터페이스를 만들어 봅시다.

package com.journaldev.java.dependencyinjection.injector;

import com.journaldev.java.dependencyinjection.consumer.Consumer;

public interface MessageServiceInjector {

	public Consumer getConsumer();
}

이제 모든 서비스에 대해 아래와 같이 인젝터 클래스를 만들어야 합니다.

package com.journaldev.java.dependencyinjection.injector;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;

public class EmailServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		return new MyDIApplication(new EmailServiceImpl());
	}

}
package com.journaldev.java.dependencyinjection.injector;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.SMSServiceImpl;

public class SMSServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		return new MyDIApplication(new SMSServiceImpl());
	}

}

이제 클라이언트 애플리케이션이 간단한 프로그램으로 애플리케이션을 사용하는 방법을 살펴보겠습니다.

package com.journaldev.java.dependencyinjection.test;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.injector.EmailServiceInjector;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.injector.SMSServiceInjector;

public class MyMessageDITest {

	public static void main(String[] args) {
		String msg = "Hi Pankaj";
		String email = "pankaj@abc.com";
		String phone = "4088888888";
		MessageServiceInjector injector = null;
		Consumer app = null;
		
		//Send email
		injector = new EmailServiceInjector();
		app = injector.getConsumer();
		app.processMessages(msg, email);
		
		//Send SMS
		injector = new SMSServiceInjector();
		app = injector.getConsumer();
		app.processMessages(msg, phone);
	}

}

보시다시피 우리의 애플리케이션 클래스는 서비스 사용에 대해서만 책임이 있습니다. 서비스 클래스는 인젝터에서 생성됩니다. 또한 페이스북 메시징을 허용하도록 응용 프로그램을 추가로 확장해야 하는 경우 서비스 클래스와 인젝터 클래스만 작성해야 합니다. 따라서 종속성 주입 구현은 하드 코딩된 종속성의 문제를 해결하고 응용 프로그램을 유연하고 확장하기 쉽게 만드는 데 도움이 되었습니다. 이제 인젝터와 서비스 클래스를 조롱하여 애플리케이션 클래스를 얼마나 쉽게 테스트할 수 있는지 살펴보겠습니다.

Java 종속성 주입 - 모의 인젝터 및 서비스가 포함된 JUnit 테스트 사례

package com.journaldev.java.dependencyinjection.test;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.service.MessageService;

public class MyDIApplicationJUnitTest {

	private MessageServiceInjector injector;
	@Before
	public void setUp(){
		//mock the injector with anonymous class
		injector = new MessageServiceInjector() {
			
			@Override
			public Consumer getConsumer() {
				//mock the message service
				return new MyDIApplication(new MessageService() {
					
					@Override
					public void sendMessage(String msg, String rec) {
						System.out.println("Mock Message Service implementation");
						
					}
				});
			}
		};
	}
	
	@Test
	public void test() {
		Consumer consumer = injector.getConsumer();
		consumer.processMessages("Hi Pankaj", "pankaj@abc.com");
	}
	
	@After
	public void tear(){
		injector = null;
	}

}

보시다시피 저는 익명 클래스를 사용하여 인젝터와 서비스 클래스를 모의하고 내 응용 프로그램 메서드를 쉽게 테스트할 수 있습니다. 위의 테스트 클래스에 JUnit 4를 사용하고 있으므로 테스트 클래스 위에서 실행 중인 경우 프로젝트 빌드 경로에 있는지 확인하십시오. 우리는 생성자를 사용하여 응용 프로그램 클래스에 종속성을 주입했습니다. 또 다른 방법은 setter 메서드를 사용하여 응용 프로그램 클래스에 종속성을 주입하는 것입니다. setter 메소드 종속성 주입을 위해 애플리케이션 클래스는 아래와 같이 구현됩니다.

package com.journaldev.java.dependencyinjection.consumer;

import com.journaldev.java.dependencyinjection.service.MessageService;

public class MyDIApplication implements Consumer{

	private MessageService service;
	
	public MyDIApplication(){}

	//setter dependency injection	
	public void setService(MessageService service) {
		this.service = service;
	}

	@Override
	public void processMessages(String msg, String rec){
		//do some msg validation, manipulation logic etc
		this.service.sendMessage(msg, rec);
	}

}
package com.journaldev.java.dependencyinjection.injector;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;

public class EmailServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		MyDIApplication app = new MyDIApplication();
		app.setService(new EmailServiceImpl());
		return app;
	}

}

세터 종속성 주입의 가장 좋은 예 중 하나는 자바 주석입니다. 필요한 것은 필드, 생성자 또는 setter 메서드에 주석을 달고 구성 xml 파일 또는 클래스에서 구성하는 것입니다.

Java 종속성 주입의 이점

Java에서 종속성 주입을 사용하면 다음과 같은 이점이 있습니다.

  • 관심사 분리
  • 종속성을 초기화하는 모든 작업이 인젝터 구성 요소에 의해 처리되기 때문에 애플리케이션 클래스의 상용구 코드 감소
  • 구성 가능한 구성 요소로 애플리케이션을 쉽게 확장 가능
  • 모의 개체를 사용하면 단위 테스트가 쉽습니다.

자바 의존성 주입의 단점

Java 종속성 주입에는 몇 가지 단점도 있습니다.

  • 과도하게 사용하면 변경 효과가 런타임에 알려지기 때문에 유지 관리 문제가 발생할 수 있습니다.
  • Java의 종속성 주입은 컴파일 시 발견될 수 있는 런타임 오류로 이어질 수 있는 서비스 클래스 종속성을 숨깁니다.

종속성 주입 프로젝트 다운로드

이것이 자바의 의존성 주입 패턴에 대한 전부입니다. 우리가 서비스를 통제할 때 그것을 알고 사용하는 것이 좋습니다.