웹사이트 검색

스프링 의존성 주입


오늘은 스프링 의존성 주입에 대해 알아보겠습니다. Spring Framework 핵심 개념은 "Dependency Injection\ 및 "Aspect Oriented Programming\입니다. 저는 이전에 애플리케이션에서 이 프로세스를 자동화하는 Google Guice 프레임워크에 대해 쓴 적이 있습니다.

스프링 의존성 주입

스프링 종속성 주입 - Maven 종속성

pom.xml 파일에 Spring 및 JUnit maven 종속성을 추가했으며 최종 pom.xml 코드는 다음과 같습니다.

<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;
	}

}

이제 서비스가 준비되었으므로 서비스를 사용할 구성 요소 클래스로 이동할 수 있습니다.

스프링 종속성 주입 - 구성 요소 클래스

위의 서비스에 대한 소비자 클래스를 작성해 봅시다. 우리는 두 개의 소비자 클래스를 갖게 될 것입니다. 하나는 자동 연결을 위한 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 {

	//field-based dependency injection
	//@Autowired
	private MessageService service;
	
//	constructor-based dependency injection	
//	@Autowired
//	public MyApplication(MessageService svc){
//		this.service=svc;
//	}
	
	@Autowired
	public void setService(MessageService svc){
		this.service=svc;
	}
	
	public boolean processMessage(String msg, String rec){
		//some magic like validation, logging etc
		return this.service.sendMessage(msg, rec);
	}
}

MyApplication 클래스에 대한 몇 가지 중요한 사항:

  • @Component 주석이 클래스에 추가되어 Spring 프레임워크가 구성 요소를 스캔할 때 이 클래스가 구성 요소로 처리됩니다. 자바 주석 자습서.
  • @Autowired 주석은 자동 연결이 필요하다는 것을 Spring에 알리는 데 사용됩니다. 이는 필드, 생성자 및 메소드에 적용될 수 있습니다. 이 주석을 사용하면 구성 요소에서 생성자 기반, 필드 기반 또는 메서드 기반 종속성 주입을 구현할 수 있습니다.
  • 이 예에서는 메서드 기반 종속성 주입을 사용하고 있습니다. 생성자 메서드의 주석을 제거하여 생성자 기반 종속성 주입으로 전환할 수 있습니다.

이제 어노테이션 없이 유사한 클래스를 작성해 봅시다.

package com.journaldev.spring.di.consumer;

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

public class MyXMLApplication {

	private MessageService service;

	//constructor-based dependency injection
//	public MyXMLApplication(MessageService svc) {
//		this.service = svc;
//	}
	
	//setter-based dependency injection
	public void setService(MessageService svc){
		this.service=svc;
	}

	public boolean processMessage(String msg, String rec) {
		// some magic like validation, logging etc
		return this.service.sendMessage(msg, rec);
	}
}

서비스를 사용하는 간단한 애플리케이션 클래스입니다. XML 기반 구성의 경우 생성자 기반 스프링 종속성 주입 또는 메서드 기반 스프링 종속성 주입 구현을 사용할 수 있습니다. 메서드 기반 및 세터 기반 주입 방식은 동일합니다. 일부는 세터 기반이라고 부르고 일부는 메서드 기반이라고 합니다.

주석이 있는 Spring 종속성 주입 구성

주석 기반 구성의 경우 실제 구현 bean을 구성 요소 속성에 주입하는 데 사용할 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 주석은 구성 클래스임을 Spring에 알리는 데 사용됩니다.
  • @ComponentScan 주석은 @Configuration 주석과 함께 사용되어 구성 요소 클래스를 찾을 패키지를 지정합니다.
  • @Bean 어노테이션은 Spring 프레임워크가 이 메소드를 사용하여 Component 클래스에 삽입할 bean 구현을 가져오는 데 사용되어야 함을 알리는 데 사용됩니다.

주석 기반 Spring Dependency Injection 예제를 테스트하는 간단한 프로그램을 작성해 봅시다.

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", "pankaj@abc.com");
		
		//close the context
		context.close();
	}

}

AnnotationConfigApplicationContextAbstractApplicationContext 추상 클래스의 구현이며 주석이 사용될 때 서비스를 구성 요소에 자동 연결하는 데 사용됩니다. AnnotationConfigApplicationContext 생성자는 클래스를 구성 요소 클래스에 주입할 빈 구현을 가져오는 데 사용되는 인수로 사용합니다. getBean(Class) 메소드는 Component 객체를 반환하고 객체를 자동 연결하기 위한 구성을 사용합니다. 컨텍스트 개체는 리소스를 많이 사용하므로 사용이 끝나면 닫아야 합니다. 위의 프로그램을 실행하면 아래와 같은 결과를 얻습니다.

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 pankaj@abc.com 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

스프링 종속성 주입 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 메소드를 사용하고 있기 때문에 bean 구성에는 주입을 위한 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", "pankaj@abc.com");

		// close the context
		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 pankaj@abc.com 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 Framework에 의해 작성되었습니다. Spring Framework는 로깅 목적으로 log4j를 사용하고 구성하지 않았기 때문에 출력이 콘솔에 기록됩니다.

스프링 의존성 주입 JUnit 테스트 케이스

Spring에서 의존성 주입의 주요 이점 중 하나는 실제 서비스를 사용하는 것보다 모의 서비스 클래스를 갖는 것이 쉽다는 것입니다. 그래서 저는 위에서 배운 모든 것을 결합하고 봄의 종속성 주입을 위해 단일 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", "pankaj@abc.com"));
	}

}

getMessageService() 메서드가 MessageService 모의 구현을 반환하기 때문에 클래스에 @Configuration@ComponentScan 주석이 추가됩니다. 이것이 getMessageService()@Bean 주석이 달린 이유입니다. 주석으로 구성된 MyApplication 클래스를 테스트 중이므로 AnnotationConfigApplicationContext를 사용하고 setUp() 메소드에서 객체를 생성합니다. tearDown() 메서드에서 컨텍스트가 닫힙니다. test() 메서드 코드는 컨텍스트에서 구성 요소 개체를 가져오고 테스트합니다. Spring Framework가 어떻게 자동 연결을 수행하고 Spring Framework에 알려지지 않은 메소드를 호출하는지 궁금하십니까? 런타임에 클래스의 동작을 분석하고 수정하는 데 사용할 수 있는 Java Reflection을 많이 사용하면 됩니다.

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

위의 URL에서 샘플 Spring DI(Dependency Injection) 프로젝트를 다운로드하고 이를 가지고 놀면서 자세히 알아보세요.