스프링 의존성 주입

오늘은 스프링 의존성 주입에 대해 알아볼 것입니다. 스프링 프레임워크의 핵심 개념은 “의존성 주입“과 “관점 지향 프로그래밍“입니다. 이전에 자바 의존성 주입 및 어떻게 Google Guice 프레임워크를 사용하여 이 프로세스를 자동화할 수 있는지에 대해 작성했습니다.

스프링 의존성 주입

이 자습서는 주석 기반 구성 및 XML 파일 기반 구성을 모두 사용하는 Spring Dependency Injection 예제에 대한 세부 정보를 제공하는 것을 목표로 합니다. 또한 의존성 주입의 주요 이점 중 하나인 쉬운 테스트 가능성 때문에 응용 프로그램에 대한 JUnit 테스트 케이스 예제도 제공하겠습니다. 아래 이미지와 같이 구조가 보이는 spring-dependency-injection 메이븐 프로젝트를 만들었습니다. 각 구성 요소를 하나씩 살펴 보겠습니다.

Spring Dependency Injection – Maven Dependencies

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 Framework 버전은 4.0.0.RELEASE이고 JUnit의 현재 버전은 4.8.1입니다. 다른 버전을 사용하는 경우 프로젝트에 약간의 변경이 필요할 수 있습니다. 프로젝트를 빌드하면 위 이미지와 같이 전이적 종속성 때문에 몇 가지 다른 jar 파일이도 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;
	}

}

이제 서비스가 준비되었으므로 서비스를 사용할 컴포넌트 클래스로 이동할 수 있습니다.

스프링 종속성 주입 – 컴포넌트 클래스

위 서비스에 대한 소비 클래스를 작성해 봅시다. 두 가지 소비 클래스가 있을 것입니다. 하나는 스프링 주석을 사용하여 자동 와이어링하고, 다른 하나는 주석이 없으며 와이어링 구성이 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 주석은 클래스에만 적용할 수 있으며 보존 정책은 실행 시간입니다. Annotation 보존 정책에 익숙하지 않다면 자바 어노테이션 튜토리얼을 읽을 것을 권장합니다.
  • @Autowired 주석은 스프링에 자동 연결이 필요함을 알리는 데 사용됩니다. 이것은 필드, 생성자 및 메서드에 적용할 수 있습니다. 이 주석을 사용하면 구성 요소에서 생성자 기반, 필드 기반 또는 메서드 기반 종속성 주입을 구현할 수 있습니다.
  • 우리 예제에서는 메서드 기반 종속성 주입을 사용합니다. 생성자 기반 종속성 주입으로 전환하려면 생성자 메서드의 주석을 제거할 수 있습니다.

이제 Annotation 없이 유사한 클래스를 작성해 봅시다.

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;
//	}
	
	//setter 기반 종속성 주입
	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();
	}

}

AnnotationConfigApplicationContextAbstractApplicationContext 추상 클래스의 구현체이며 주석이 사용될 때 구성 요소에 서비스를 자동으로 연결하는 데 사용됩니다. 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 Dependency Injection XML Based Configuration

아래 데이터로 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에는 생성자 및 setter를 기반으로 하는 Spring 종속성 주입의 구성이 모두 포함되어 있음에 유의하십시오. MyXMLApplication이 주입에 setter 메서드를 사용하고 있다면 빈 구성에는 주입을 위한 property 요소가 포함되어 있습니다. 생성자 기반 주입의 경우 constructor-arg 요소를 사용해야 합니다. 구성 XML 파일은 소스 디렉토리에 배치되어 빌드 후에 classes 디렉토리에 있게 됩니다. 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가 어떻게 의존성 주입을하고 Spring Framework에 알려지지 않은 메서드를 호출하는지 궁금하십니까? 이는 Java Reflection의 활용을 통해 런타임에서 클래스의 동작을 분석하고 수정할 수 있습니다.

Spring 의존성 주입 프로젝트 다운로드

위 URL에서 샘플 Spring 의존성 주입(DI) 프로젝트를 다운로드하여 더 많이 알아보세요.

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