본문 바로가기

Programming

[Spring Web] 스프링 특징 및 의존성 주입 테스트

스프링 프레임워크의 간략한 역사


스프링이 인기있는 프레임워크가 된 이유를 설명하기전에 프레임 워크란 무엇이가에 대해서 알아야합니다. 프레임워크란 단어를 나누자면 frame(틀,뼈대)+work(작업)으로 나눌수 있습니다. 즉 프레임 워크는 뼈대를 이루는 코드(작업)들의 묶음 이라고 설명할수가 있습니다. 그렇다면 프로그램을 개발하는데 왜 뼈대가 필요한지에 대해서 설명하겠습니다. 

 

IT 프로젝트에서 개발자는 각 개인의 역량에 따라 나오는 퀄리티 차이가 큰 분야입니다. 따라서 어떤 개발자를 구성하는냐에 따라 프로젝트 결과나 퀄리티가 큰 차이를 낳습니다. 이러한 문제점을 극복하기 위한 코드의 결과물이 프레임워크 입니다. 결과적으로 프레임워크를 이용을 한다는 것은 프로그램의 기본 흐름이나 구조를 정하고 모든 팀원의 이 구조 즉 Frame안에서 자신의 코드를 추가하는 방법(work)으로 개발을 하게 됩니다. 프레임워크의 최대 장점은 필요한 구조를 이미 코드로 만들어 놓아서 역량이 부족한 개발자라 하더라도 미리 만들어 놓은 상태에서 필요한 부분을 조립하는 형태의 개발이 가능하다는 점입니다. 그리고 회사의 입장에서는 프레임워크를 사용한다면 일정한 품질의 결과물을 얻을수 있고 개발자 입장에서는 완성된 구조에 자신이 맡은 코드를 개발해서 조립하는 형태이므로 개발 시간의 단축을 얻을수 있습니다. 

 

2000초반 다양한 프레임워크가 나왔지만 그중 가장 성공적인 경량 프레임워크가 스프링입니다. 

 

그렇다면 스프링의 특징을 살펴보겠습니다.

스프링 특징


  • POJO의 구성

  • 의존성 주입(DI)을 통한 객체 간의 관계 구성 

  • AOP(Aspect-Oriented-Programming) 지원 

  • 편리한 MVC 구조 

  • WAS의 종속적이지 않은 개발 환경 

 

1. POJO 기반의 구성 


스프링의 성격 자체는 가벼운 프레임워크이지만 그 내부에서는 객체 간의 관계를 구성할수 있는 특징을 가지고 있습니다. 스프링은 다른 프레임워크들과 달리 이 관계를 구성할 때 별도의 API등을 사용하지 않는 POJO의 구성만으로 가능하도록 설계되었습니다. 위 말을 쉽게 풀어서 설명하면 일반 Java 코드를 이용해 객체를 구성하는 방법을 그대로 Spring에 사용할수 있다는 점입니다. 이것이 중요한 이유는 개발자가 Java 코드로 테스트를 유연하게 할수있다는 장점을 가지고 있기 때문입니다. 

 

2. 의존성 주입(DI)을 통한 객체 간의 관계 구성 


스프링에 특징중 가장 큰 특징을 말하면 '의존성 주입' 니다. Spring에서 의존성이라는 것은 하나의 객체가 다른 객체 없이 제대로된 역활을 수행할수가 없다는 점입니다. 이것을 예를 들어서 이해하기 쉽게 풀어보겠습니다. 

 

의존성 주입 예시

 

맛집을 운영하는 음식점이라면 서빙을 담당하는 직원이 갑자기 하루 못나오는 상황이 있어도 서빙은 누구나 할수 있는 역활이기 때문 장사는 할수가 있지만 주방장에 아프다고 해서 일을 못나온다는 상황이 온다면 음식점에서 이 맛을 낼수가 있는 사람은 주방장이(주방장은 맛집 음식재료 구입 및 재료 손질하는 것까지 하는 역활)하기 때문에 그 음식을 만들어 낼수가 없어 장사를 하기가 매우 힘들어 질수 있습니다. 즉  맛집을 운영하는데 가장 영향력이 큰 행사하는 주방장이 아프다고 못나온다면 음식점을 운영할수가 없듯이 스프링 에서는 객체 A가 객체 B 없이 동작을 할수가 없는 상황을 의존적이라고 할수가 있습니다. 

 

 

 

스프링 에서는 위 예제와 같이 의존성을 해결하기 위해 의존성을 주입하는 해결책을 제시합니다. 다시 예제로 들어가서 사장님의 입장에서는 음식점을 운영하는데 주방장에게 너무 의존적이여서 못마땅해 할것입니다. 그래서 생각을 해낸것이 외부 업체에서 음식 재료며 손질까지 다해서 포장 배송까지 하는 대행업체와 계약을 하여 주방장이 없어도 그 누군가가 배송된 음식으로 조합하여 서빙을 할수 있도록 하여 주방장의 의존성을 약하게 하려고 할것입니다. 그렇다면 사장님의 입장에서는 대행업체를 사용하기때문에 편하고 음식점 운영에만 집중을 할수가 있다는 장점을 가지고 있습니다. 이처럼 사장님이 대행업체를 쓴다는 것은 주입(Injection) 한다고 합니다 주입은 외부에서 밀어넣은 것을 의미합니다. 코드에 이것을 대입하면 주입을 받는 입장에서는 어떤 객체인지 신경을 쓸 필요가 없습니다. 어떤 객체에 의존하든지 자신의 역활은 변하지 않기 때문입니다. 

 

스프링에서는 ApplicationContext라는 존재가 필요한 컨텍스트를 생성하고 객체들을 주입하는 구조로 설계되었습니다. 따라서 스프링 프레임워크를 이용하면 기존의 프로그래밍과 달리 객체와 객체를 분리해서 생성하고 이러한 객체들을 엮는 (Wiring) 작업을 하는 형태의 개발을 하게 됩니다. Spring에서는 ApplicationContext가 관리하는 객체들을 빈(Bean)이라고 하고 빈과 빈 사이의 의존관계를 XML 설정,어노테이션 설정,자바 설정 방식을 이용할수가 있습니다. 

3. AOP 지원


개발자에게 좋은 개발환경을 갖추는 것은 매우 중요합니다. 즉 개발자는 자신이 담당하는 비즈니스로직에만 집중하도록 환경을 구성하는건 프로젝트의 퀄리티를 결정할 만큼 중요한 일입니다. 이것을 이루기 위해 서는 몇가지 중요한 원칙이 있지만 가장 쉽게 할수 있는건 반복적인 코드 제거 입니다. Spring은 프레입워크를 이용한 개발에솓 이러한 반복적인 코드를 줄이고 핵심 비즈니스 로직에만 집중할수 있는 방법을 제공합니다.

 

대부분의 시스템이 공통적으로 가지고 있는 보안이나 로그,트렌잭션과 같이 비즈니스 로직은 아니지만 반드시 처리가 필요한 부분을 스프링에서는 횡단 관심사라고 합니다. Spring은 이러한 횡단 관심사를 분리해서 제작하는 것이 가능합니다. AOP(Aspect Oriented Programming)은 이러한 횡단 관심사를 모듈로 분리하는 프로그래밍 패러다임이라고 할수 있습니다. 

 

4. 트랜젝션의 지원


데이터베이스를 이용할 때 반드시 신경 서야하는 부분은 한의 업무가 여러 작업으로 이루어지는 트랜젝션 처리입니다. 이 트랜젝션처리는 상황에 따라서 복잡하게 구성이 될때가 있습니다. 그때마다 코드를 이용해서 처리하는 작업은 개발자에게는 버거운 작업입니다. Spring은 이런 트랜젝션의 관리를 어노테이션이나XML로 설정할수가 있기 떄문에개발자가 매번 상황에 맞는 코드를 작성할 필요가 없도록 설계가 되었습니다. 

 

의존성 주입 테스트


스프링의 가장 큰 특징중 하나인 의존성 주입에 대한 테스트를 해보려고 합니다. 앞에 있는 음식점(Restaurant) 객체를 만들고 음식점에서 일하는 주방장(Chef) 객체를 주입하는 예제를 작성해 보겠습니다. 이것을 의존성 주입을 코드로 이해 해보려고하는 용도이지 실제 개발할때 적용되지는 않습니다.

 

스프링에서는 의존성 주입을 하는 방법이 2가지가 있습니다. 

  • 생성자를 통한 의존성 주입

  • setter메서드를 이용한 주입 

이번 테스트에서는 Setter 메소드를 이용한 주입으로 의존성 주입을 테스트합니다. 설정 방법은 XML 이나 어노테이션을 통하여 처리하고 Lombok을 이용해서 setter 메소드를 자동으로 구현 되도록 할것입니다. 

 

가장 먼저 pom.xml 에서 Lombok 라이브러리를추가하고 Spring-test라이브러리를 이용합니다.

 

maven repository 

https://mvnrepository.com/artifact/org.projectlombok/lombok/1.18.8 

다음 메이븐 공식 repository에 Lombok 라이브러리를 pom.xml 파일에 안에 추가하고 저장을 합니다. 그리고 Junit 설정이 되어 있는지 확인하고 log4j 라이브러리는 1.2.15로 되어 있어 삭제를 해주시고 1.2.17버전을 추가하도록 합니다.(나중에 Lombok을 활요할 때 문제가 생길수 있으므로 버전 정보에 신경을 쓰는것이 중요합니다.)그 다음으로 Junit을 사용하기 위해서는 다음 라이브러리를 추가해야합니다. 

 

Junit추가

저는 Junit 4 라이브러리를 사용한다고 체크를 한후 Finish 했습니다. (pom.xml 에서 junit 4.7을 설정해서 버전에 맞추어 주었습니다. 다음 설정이 끝났다면 예제 클래스를 만들어 보겠습니다. 

 

 

Class 설정 

최소한의 코드로 의존성을 주입을 테스트해보기 위한 클래스 설계를 위 사진 처럼 해보았습니다. 

 

Chef 클래스

 

package com.exe.sample;

import org.springframework.stereotype.Component;

import lombok.Data;

@Component
@Data
public class Chef {

}

 

Restaurant클래스

 

package com.exe.sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import lombok.Data;
import lombok.Setter;

@Component
@Data
public class Restaurant {

	@Setter(onMethod_ = @Autowired)
	private Chef chef;
	
}

Restaurant클래스

Restaurant클래스에서는 Chef를 주입받도록 설계를 했습니다. Lombok의 setter를 생성하는 기능과 생성자,toString()을 자동적으로 생성하도록 해주는 @Data 어노테이션을 이용했습니다. 위에 있는 작성된 코드가 의미하는 것은 Restaurant 객체가 Chef 타입의 객체를 필요한 상황입니다. @Component는 스프링에게 해당 클래스가 스프링에석 관리해야하는 대상입을 표시하는 어노테이션이고 @Setter는 자동적으로 setChef()를 컴파일시 생성합니다. @Setter에서 사용된 onMethod속성은 생성되는 setChef()에 @Autowired 어노테이션을 추가하도록 합니다. Lombok으로 생성된 클래스에 대한 정보는 위 사진처럼 확인할수가 있습니다.  그 다음으로 해야하는 작업은 XML을 이용하여 의존성 주입을 설정하는것입니다. 

 

root-context.xml 경로

다음 보이는 root-context.xml 파일은 스프링프레임워크에서 관리해야하는 객체(이러한 객체를 스프링에서는 Bean이라고 표현합니다)를 설정하는 설정 파일입니다. root-context.xml을 클릭하면 아래쪽 NamedSpace텝에 context 항목을 추가합니다.

 

root-context.xml Namespace/BeansGraph

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.1.xsd">

	<!-- Root Context: defines shared resources visible to all other web components -->
	<context:component-scan base-package="com.exe.sample"/>


</beans>

 

Context 란에 체크를 했으면 Source 텝으로 돌아왔을경우 xmlns:context 가 추가 됨을 볼수가 있습니다. 

그리고 Bean 테그안에 context:component-scan base-package를 위 사진과 같이 설정합니다. 

Source 설정이 끝났다면 Bean Graph로 root-context.xml에 등록된 bean을 확인 합니다. 

 

 

root.context.xml Beans Graph

 

다음과 같이 설정하였다면 어떠한 과정으로 의존성이 주입이 되는지 살펴보겠습니다. 

전반적인 Spring 동작구도 

다음 작성한 코드가 실행하는 흐름은 위 그림과 같습니다. 

 

  • 스프링 프래임워크가 실행이 되면서 가장 먼저 스프링이 사용하는 메모리 공간을 생성하게 되는데 이를 컨텍스트라고 합니다. 스프링에서는 ApplicationContext라는 이름의 객체가 생성이 됩니다.

  • 스프링은 자신이 객체를 생성하고 관리해야 하는 객체들에 대한 설정이 필요합니다. 이것이 설계되는 파일이 root-context.xml 입니다. 

  • root-context.xml에 설정되어있는 <contextn-component-scan>태그의 내용을 통해서 'com.exe.sample 패키지를 스캔하기 시작합니다.

  • 해당 패키지에 엤는 클래스들중 스프링이 사용하는 @Component라는 어노테이션이 존재하는 클래스의 인스턴스를 생성합니다.

  • Restaurant 객체는 Chef객체가 필요하다는 어노테이션 @Autowired설정이 있으므로 스프링은 Chef객체의 레퍼런스를 Restaurant 객체에 주입합니다. 

 

테스트 코드를 통한 확인 


테스트코드 생성

프로젝트 내 'src/test/java' 폴더 내에 'com.exe.sample.SampleTests' 클래스를 생성합니다. 

SampleTests 클래스는 spring-test 모듈을 이용해서 간단하게 스프링을 가동시키기고 위에서 설명한 작업을 실행하게 합니다

 

package com.exe.sample;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class SampleTests {
	
	@Setter(onMethod_ = { @Autowired})
	private Restaurant restaunrant;
	
	@Test
	public void testExist() {
		assertNotNull(restaunrant);
		log.info(restaunrant);
		log.info("---------------------------");
		log.info(restaunrant.getChef());
	}
	
}

 

 

테스트코드는 현재 SampleTest파일이  스프링을 실행하는 역활을 할 것이라는것을 @Runwith으로 명시합니다. 그 다음으로 중요한 어노테이션은 @ContextConfiguration 어노테이션과 속성값인 문자열 설정입니다. @ContextConfiguration는 지정된 클래스나 문자열을 이용해서 필요한 객체들을 스프링 내에 등록을 하게 됩니다. @ContextConfiguration에 사용하는 문자열은 classpath:나 file:을 이용할수가 있으므로 여기서는 root-context.xml의 경로를 설정했습니다. (어노테이션에 대한 정리를 따로 페이지를 만들겠습니다.)

 

log4j.xml 

@Log4j는 Lombok을 이용해서 로그를 기록하는 Logger를 변수로 생성합니다. Logger 객체의 선언이 없어도 Log4j라이브러리와 설정이 존재한다면 바로 사용이 가능합니다. 기본적으로 Spring Legacy Project로 프로젝트를 생성하는 경우에는 이미 Log4j의 설정이 완료된 상태이기 때문에 @Log4j로 불러 온다면 사용이 가능합니다. 로그에 관련된 설정은 'src/main/resource'  'src/test/resources'에 별도로 존재합니다. 

마지막으로 AssertNotNull() restaurant 변수가 null이 아니여야만 테스트가 통과 할수 있다는것을 의미합니다. 

Run as -> Junit Test 를 실행해서 테스트 결과를 확인해 봅시다 

 

Junit 실행시 로그 

Junit 실행시 할경우 다음과 같은 로그가 출력이 됩니다. 여기서 주목할만한 부분은 다음과 같습니다.

 

  • new Restaurant() 와 같이 Restaurant 클래스에서 객체를 생성한 적이 없는데도 객체가 생성이 되었다는 것입니다. 여기서 스프링은 관리가 필요한 객체(Bean)르 어노테이션을 이용해서 객체를 생성하고 관리하는 '컨테이너' 기능을 가지고 있습니다. 

  • Restaurant 클래스의 @Data 어노테이션으로 Lombok을 이용해서 여러 메서드가 만들어지점은  Lombok은 자동으로 getter/setter등을 만들어 주는데 스프링에서는 생성자 주입 혹은 setter을 이용하여 동작을 합니다.

  • Restaurant 객체의 Chef 인스턴스 변수에 Chef타입의 객체가 주입이 되었다는 점입니다. 스프링은 @Autowired와 같은 어노테이션을 이용해 개발자가 직접 객체들을 관리하지 않도록 자동으로 관리 됩니다. 

위 테스트 코드에서는 다음과 같은 의미를 가집니다. 

  1. 테스트 코드가 실행되기 위해서 스프링 프래임워크가 동작하였고 

  2. 동작하는 과정에서 필요한 객체들이 스프링에 등록이 되었으며 

  3. 의존성 주입이 필요한 객체는 자동으로 주입이 되었습니다. 

읽어주셔서 감사합니다^^