스프링 의존성 주입
이 튜토리얼은 주석 기반과 XML 기반 구성으로 스프링 의존성 주입 예제에 대한 자세한 정보를 제공합니다.
애플리케이션에 대한 JUnit 테스트 사례 예제도 제공합니다.
왜냐하면 쉬운 테스트 가능성이 의존성 주입의 주요 이점 중 하나이기 때문입니다.
아래 이미지와 같은 구조를 가진 spring-designpattern 메이븐 프로젝트를 만들었습니다.
각 컴포넌트를 하나씩 살펴 보겠습니다.
스프링 의존성 주입 - 메이븐 의존성
pom.xml 파일에 스프링 및 JUnit 메이븐 의존성을 추가했으며 최종 pom.xml 코드는 아래와 같습니다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.designpattern.spring</groupId>
<artifactId>spring-designpattern</artifactId>
<version>1.0-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>
스프링 프레임워크의 현재 안정 버전은 4.0.0.RELEASE 이고 JUnit 현재 버전은 4.8.1 입니다.
다른 버전을 사용할 경우 프로젝트에 약간의 변경이 필요할 수 있습니다.
프로젝트를 빌드하면 위의 이미지와 같이 전이적 의존성으로 인해 다른 jar 도 메이븐 의존성에 추가됩니다.
스프링 의존성 주입 - 서비스 클래스
사용자에게 이메일 메시지와 트위터 메시지를 보내고 싶다고 가정해 보겠습니다.
의존성 주입을 위해, 서비스에 대한 기본 클래스가 필요합니다.
그래서 메시지를 보내기 위한 단일 메서드 선언이 있는 MessageService 인터페이스가 있습니다.
package com.designpattern.spring.di.services;
public interface MessageService {
boolean sendMessage(String msg, String rec);
}
이제 이메일과 트위터 메시지를 보내는 실제 구현 클래스를 만듭니다.
package com.designpattern.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.designpattern.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.designpattern.spring.di.consumer;
import com.designpattern.spring.di.services.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyApplication {
private MessageService service;
// 생성자 기반 의존성 주입
// @Autowired
// public MyApplication(MessageService svc) {
// this.service = svc;
// }
// setter 기반 의존성 주입
@Autowired
public void setService(MessageService svc) {
this.service = svc;
}
public boolean processMessage(String msg, String rec) {
return this.service.sendMessage(msg, rec);
}
}
MyApplication 클래스에 대한 몇 가지 중요한 사항 :
@Component 주석이 클래스에 추가되어 스프링 프레임워크가 컴포넌트를 스캔 할 때 이 클래스가 컴포넌트로 취급됩니다.
@Component 주석은 클래스에만 적용 할 수 있으며 보존 정책은 Runtime 입니다.
주석 보존 정책에 익숙하지 않는 경우 자바 주석 자습서를 읽어 보세요.
@Autowired 주석은 자동 연결이 필요하다는 것을 스프링에게 알리는 데 사용합니다.
이것은 필드, 생성자 및 메소드에 적용될 수 있습니다.
이 주석을 사용하면 컴포넌트에서 생성자 기반, 필드 기반 또는 메서드 기반의 의존성 주입을 구현할 수 있습니다.
이 예제에서는, 메서드 기반 의존성 주입을 사용하고 있습니다.
생성자 메서드의 주석을 제거하여 생성자 기반 의존성 주입으로 전환 할 수 있습니다.
이제 주석 없이 유사한 클래스를 작성해 보겠습니다.
package com.designpattern.spring.di.consumer;
import com.designpattern.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);
}
}
서비스를 사용하는 간단한 애플리케이션입니다.
XML 기반 구성의 경우, 생성자 기반 스프링 의존성 주입 또는 메서드-기반 스프링 의존성 주입을 구현할 수 있습니다.
메서드-기반 및 setter-기반 주입 방식은 동일하며, 일부는 setter-기반이라 부르는 것을 선호하고 일부는 메서드-기반이라 부릅니다.
주석을 사용하여 스프링 의존성 주입 구성
주석 기반 구성의 경우, 실제 구현 빈을 컴포넌트 특성에 주입하는 데 사용할 Configurator 클래스를 작성해야 합니다.
package com.designpattern.spring.di.configuration;
import com.designpattern.spring.di.services.EmailService;
import com.designpattern.spring.di.services.MessageService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value={"com.designpattern.spring.di.consumer"})
public class DIConfiguration {
@Bean
public MessageService getMessageService() {
return new EmailService();
}
}
위의 클래스와 관련된 몇 가지 중요한 사항은 다음과 같습니다.
- @Configuration 주석은 스프링에 Configuration 클래스 임을 알리는 데 사용합니다.
- @ComponentScan 주석은 @Configuration 주석과 함께 사용하여 컴포넌트 클래스를 찾을 패키지를 지정합니다.
- @Bean 주석은 스프링 프레임워크에게 이 메서드를 빈 구현을 가져오는 데 사용해야 함을 알려줍니다.
주석 기반 스프링 의존성 주입 예제를 테스트하는 간단한 프로그램을 작성해 보겠습니다.
package com.designpattern.spring.di.test;
import com.designpattern.spring.di.configuration.DIConfiguration;
import com.designpattern.spring.di.consumer.MyApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ClientApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DIConfiguration.class);
MyApplication app = context.getBean(MyApplication.class);
app.processMessage("Hi Gildong", "Gildong@gmail.com");
context.close();
}
}
AnnotationConfigApplicationContext 는 AbstractApplicationContext 추상 클래스의 구현이며 주석 사용 시 서비스를 컴포넌트에 자동 연결하는 데 사용합니다.
AnnotationConfigApplicationContext 생성자는 인수로 Class 를 받으며 이 Class를 컴포넌트 클래스에 주입할 빈 구현을 가져 오는 데 사용합니다.
getBean(Class) 메서드는 Component 객체를 반환하고 객체를 자동 연결하기 위한 구성을 사용합니다.
Context 객체는 리소스를 많이 사용하므로, 작업이 끝나면 닫아야 합니다.
위의 프로그램을 실행하면, 아래 출력을 얻습니다.
Connected to the target VM, address: '127.0.0.1:1210', transport: 'socket'
9월 23, 2020 11:22:59 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6e8dacdf: startup date [Wed Sep 23 23:22:59 KST 2020]; root of context hierarchy
Email Sent to Gildong@gmail.com with Message = Hi Gildong
9월 23, 2020 11:23:00 오후 org.springframework.context.support.AbstractApplicationContext doClose
정보: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6e8dacdf: startup date [Wed Sep 23 23:22:59 KST 2020]; root of context hierarchy
Disconnected from the target VM, address: '127.0.0.1:1210', transport: 'socket'
Process finished with exit code 0
스프링 의존성 주입 XML 기반 구성
아래 데이터로 스프링 구성 파일을 생성할 것이며, 파일 이름은 무엇이든 상관 없습니다.
applicationContext.xml 코드는 다음과 같습니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<!--
<bean id="MyXMLApp" class="com.designpattern.spring.di.consumer.MyXMLApplication">
<constructor-arg>
<bean class="com.designpattern.spring.di.services.TwitterService" />
</constructor-arg>
</bean>
-->
<bean id="twitter" class="com.designpattern.spring.di.services.TwitterService"></bean>
<bean id="MyXMLApp" class="com.designpattern.spring.di.consumer.MyXMLApplication">
<property name="service" ref="twitter"></property>
</bean>
</beans>
위의 XML에는 생성자-기반 및 setter-기반 스프링 의존성 주입에 대한 구성이 있습니다.
MyXMLApplication 은 주입을 위해 setter 메서드를 사용하고 있으므로, 빈 구성에 주입을 위한 property 요소가 있습니다.
생성자 기반 주입의 경우, constructor-arg 요소를 사용해야 합니다.
구성 XML 파일은 소스 디렉토리에 있으므로, 빌드 후 classes 디렉토리에 나타납니다.
간단한 프로그램으로 XML 기반 구성을 사용하는 방법을 살펴 보겠습니다.
package com.designpattern.spring.di.test;
import com.designpattern.spring.di.consumer.MyXMLApplication;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ClientXMLApplication {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyXMLApplication app = context.getBean(MyXMLApplication.class);
app.processMessage("Hi Gildong", "Gildong@gmail.com");
context.close();
}
}
ClassPathXmlApplicationContext 는 구성 파일 위치를 제공하여 ApplicationContext 객체를 가져오는 데 사용합니다.
오버로드 된 생성자가 여러 개 있으며 구성 파일도 여러 개 제공 할 수 있습니다.
나머지 코드는 주석 기반 구성 테스트 프로그램과 유사하지만, 유일한 차이점은 구성 선택에 따라 ApplicationContext 객체를 얻는 방법입니다.
위의 프로그램을 실행하면, 다음과 같은 결과가 나옵니다.
Connected to the target VM, address: '127.0.0.1:1291', transport: 'socket'
9월 23, 2020 11:24:34 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3d24753a: startup date [Wed Sep 23 23:24:34 KST 2020]; root of context hierarchy
9월 23, 2020 11:24:34 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [applicationContext.xml]
Twitter message Sent to Gildong@gmail.com with Message = Hi Gildong
9월 23, 2020 11:24:34 오후 org.springframework.context.support.AbstractApplicationContext doClose
정보: Closing org.springframework.context.support.ClassPathXmlApplicationContext@3d24753a: startup date [Wed Sep 23 23:24:34 KST 2020]; root of context hierarchy
Disconnected from the target VM, address: '127.0.0.1:1291', transport: 'socket'
Process finished with exit code 0
출력 중 일부는 스프링 프레임워크가 작성한 것입니다.
스프링 프레임워크는 로깅 목적으로 log4j 를 사용하지만 구성하지 않았으므로, 출력이 콘솔에 기록됩니다.
스프링 의존성 주입 JUnit 테스트 사례
스프링 의존성 주입의 주요 이점 중 하나는 실제 서비스를 사용하는 대신 모의 서비스 클래스를 쉽게 사용할 수 있다는 것입니다.
그래서 위에서 배운 모든 것을 결합하고 스프링에 의존성 주입을 위해 단일 JUnit 4 테스트 클래스에 모든 것을 작성했습니다.
package com.designpattern.spring.di.test;
import com.designpattern.spring.di.consumer.MyApplication;
import com.designpattern.spring.di.services.MessageService;
import org.junit.After;
import org.junit.Assert;
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;
@Configuration
@ComponentScan(value="com.designpattern.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 Gildong", "Gildong@gmail.com"));
}
}
getMessageService() 메서드는 MessageService 모의 구현을 반환하므로 클래스에 @Configuration 및 @ComponentScan 주석을 추가합니다.
이것이 getMessageService() 에 @Bean 주석이 추가된 이유입니다.
주석으로 구성된 MyApplication 클래스를 테스트하고 있으므로, AnnotationConfigApplicationContext 를 사용하고 setUp() 메서드에서 객체를 생성하고 있습니다.
컨텍스트가 tearDown() 메서드에서 닫히고 있습니다.
test() 메서드 코드는 컨텍스트에서 컴포넌트 객체를 가져 와서 테스트합니다.
스프링 프레임워크가 어떻게 자동 연결을 수행하고 알려지지 않은 메서드를 호출하는지 궁금하십니까?이 작업은 런타임에 클래스의 동작을 분석하고 수정하는 데 사용할 수 있는 Java Reflection 을 많이 사용하여 수행합니다.
'스프링' 카테고리의 다른 글
스프링 - 디자인 패턴 - 의존성 주입 #2 (0) | 2020.09.24 |
---|---|
스프링 - 디자인 패턴 - 의존성 주입 #1 (0) | 2020.09.24 |
스프링 - 설계 철학 (0) | 2020.09.24 |
댓글