ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [1-ch2 스프링 특징] 의존성 주입 테스트 (POJO, AOP, DI)
    Back-End/Spring Legacy 2022. 8. 11. 12:54

     

    스프링 프레임워크에 대한 이론적인 부분을 살펴보려고 한다. 

    이번 포스팅에서 알아볼 목표는 아래와 같다.

    1. 스프링 프레임워크를 이용한 '의존성 주입'에 대한 이해와 테스트
    2. 스프링에서 xml을 이용하는 객체 관리 방법
    3. 스프링의 테스트 환경 구척

     

     

    스프링이 인기 있는 프레임워크가 된 이유는 '뼈대나 근간을 이루는 코드들의 묶음'이라고 할 수 있다.

     

    스프링의 주요 특징이라고 하면 주로 다음과 같은 점을 들 수 있다.

     

    • POJO 기반의 구성
    • 의존성 주입(DI)를 통한 객체 간의 관계 구성
    • AOP(Aspect-Oriented-Programming)지원
    • 편리한 MVC 구조
    • WAS의 종속적이지 않은 개발환경

     

     

    POJO 기반의 구성

     

    스프링의 성격 자체가 가벼운(light-weight) 프레임워크지만, 그 내부에는 객체 간의 관계를 구성할 수 있는 특징을 가지고 있다. 스프링은 다른 프레임워크들과 달리 이 관계를 구성할 때 별도의 API등을 사용하지 않는 POJO(Plain Old Java Object)의 구성만으로 기능하도록 제작되어있다. 쉽게 말해 일반적인 Java 코드를 이용해 객체를 구성하는 방식을 그대로 스프링에서 사용할 수 있는 것이다.

     

    이것이 중요한 이유는 코드를 개발할 때 개발자가 특정한 라이브러리나 컨테이너의 기술에 종속적이지 않다는 것을 의미하기 때문이다. 개발자는 가장 일반적인 형태로 코드를 작성하고 실행할 수 있기 때문에 생산성에서도 유리하고, 코드에 대한 테스트 작업 역시 좀 더 유연하게 할 수 있다는 장점이 생긴다. 

     


     

     

     

    ★ POJO란? 

     

    객체 지향적인 원리에 충실하면서 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다. 

     

     

    ★ POJO 특징

     

    1. 특정 규약에 종속되지 않는다.

      자바언어와 꼭 필요한 API외에는 종속되지 말아야한다. EJB2와 같이 특정 규약을 따라 만들게 하는 경우는 대부분 규약에서 제시하는 특정 클래스를 상속하도록 요구한다. 그럴 경우 자바의 단일 상속 제한 때문에 더이상 해당 클래스에 객체지향적인 설계 기법을 적용하기가 어려워지는 문제가 발생한다.

     

    더보기

    API는 Application Programming Interface(애플리케이션 프로그램 인터페이스)의 줄임말입니다. API의 맥락에서 애플리케이션이라는 단어는 고유한 기능을 가진 모든 소프트웨어를 나타냅니다. 인터페이스는 두 애플리케이션 간의 서비스 계약이라고 할 수 있습니다. 이 계약은 요청과 응답을 사용하여 두 애플리케이션이 서로 통신하는 방법을 정의합니다. API 문서에는 개발자가 이러한 요청과 응답을 구성하는 방법에 대한 정보가 들어 있습니다.

    출처 : https://aws.amazon.com/ko/what-is/api/

     

     

    2. 특정 환경에 종속되지 않는다.

      POJO는 환경에 독립적이야한다. 특히 비즈니스 로직을 다목 있는 POJO 클래스는 웹이라는 환경 정보나 웹 기술을 담고 있는 클래스나 인터페이스를 사용해서는 안된다. 설령 나중에는 웹 컨트롤러와 연결돼서 사용될 것이 분명하더라도 직접적으로 웹이라는 환경으로 제한해버리는 오브젝트나 API에 의존해서는 안된다. 

     

     

    3. 객체 지향적 원리에 충실해야한다.

      POJO는 객체지향적인 자바언어의 기본에 충실하게 만들어져야한다. 자바 언어 문법을 사용했다고 해서 자동적으로 객체지향 프로그래밍과 객체지향 설계가 적용됐다고 볼 수는 없다. 

     

     

     

    의존성 주입(DI)과 스프링

     

    스프링에 대해 얘기를 할 때 빠지지 않는 개념이 바로 '의존성 주입'이다. 

    의존성(Dependency)라는 것은 하나의 객체가 다른 객체 없이 제대로 된 역할을 할 수 없다는 것을 의미한다. 예를들어) 음식점에서 서빙을 담당하는 직원이 갑자기 하루 못나오는 상황이 있어도 장사를 할 수 있지만, 주방장에게 문제가 생겨 못 나오게 되면 장사를 할 수 없는 일이 발생한다. 의존성은 이처럼 하나의 객체가 다른 객체의 상태에 따라 영향을 받는 것을 의미한다. 흔히 A라는 객체가 B 객체 없이 동작이 불가능한 상황을 'A가 B에 의존적이다.'라고 표현을 한다.

     

    '주입(Injection)'은 말 그대로 외부에서 '밀어 넣은 것'을 의미한다. 외부에서 주입하는 것과 그렇지 않은 것을 이해하기 위해 또 한번 음식점을 예로 들어보자. 어떤 음식점의 경우 매일 가게를 열기 전 직접 재료를 구하기 위해 시장을 가지만, 프랜차이즈 식당은 본사가 트럭 등을 이용해 식재료를 공급한다. 이 두가지 방식의 차이는 필요한 객체를 얻기 위해 주체가 능동적인지 수동적인지에 대한 문제이다. 

     

    의존성과 주입을 결합해서 생각해보면 '어떤 객체가 필요한 객체를 외부에서 밀어 넣는다'라는 의미가 된다. 그렇다면 why? 왜 사용하는가를 알아보자.

     

    위의 음식점 예에서 직접 재료를 사지 않고, 대행업체에서 배송해 주는 것을 사용하는 경우 장점은 '편리하다'이다. '장사만 집중할 수 있다'같은 장점이 있다. 이를 코드에 대입해서 보면 '주입 받는 입장에서는 어떤 객체인지 신경 쓸 필요가 없다' , '어떤 객체에 의존하든 자신의 역할은 변하지 않는다.' 와 같은 의미로 볼 수 있다. 

    즉) 선언만 하면 스프링에서 알아서 내가 필요한 객체를 넣어준다는 것! 

     

    스프링은 이러한 구조를 만드는 데 적합한 구조로 되어있다. 'ApplicationContext'라는 존재가 필요한 객체들을 생성하고, 필요한 객체들을 주입하는 역할을 해주는 구조이다. 따라서 스프링을 이용하면 개발자들은 기존의 프로그래밍과 달리 객체와 객체를 분리해서 생성하고, 이러한 객체를 엮는(wiring)작업을 하는 형태의 개발을 하게 된다. ApplicationContext가 관리하는 객체들을 '빈(Bean)'이라는 용어로 부르고, 빈과 빈 사이의 의존관계를 처리하는 방식으로  xml설정, 어노테이션 설정, java설정 방식을 이용할 수 있다. 

     

     

     

    AOP의 지원

     

    좋은 개발환경의 중요 원칙은 ' 개발자가 비즈니스 로직에만 집중할 수 있게 한다.' 이다. 이 목표를 이루기 위해서 몇 가지 중요한 원칙이 있지만, 가장 쉽게 생각할 수 있는 것이 '반복적인 코드의 제거'라고 할 수 있다. 대부분의 시스템이 공통으로 가지고 있는 보안이나 로그, 트랜잭션과 같이 비즈니스 로직은 아니지만, 반드시 처리가 필요한 부분을 스프링에서는 '횡단 관심사(cross-concern)'라고 한다. 스프링은 이러한 횡단 관심사를 분리해서 작업하는 것이 가능하다. AOP(Aspect Oriented Programming)은 이러한 횡단 관심사를 모듈로 분리하는 프로그래밍의 패러다임이다. 

     

     

    ★ AOP란? 

     

    AOP는 관점지향프로그래밍이라 불린다.

    관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 묘듈화하겠다는 것이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶은 것이다예를들어 핵심적인 관점은 결국 우리가 적용하고자 하는 핵심 비즈니스 로직이 된다또한 부가적인 관점은 핵심 로직을 실행하기 위해서 행해지는 데이터베이스 연결, 로깅, 파일 입출력을 예로 들 수 있다AOP에서 각 관점을 기준으로 로직을 모듈화한다는 것은 코드들을 부분적으로 나눠서 모듈화 하겠다는 의미이다. 이때 , 소스 코드상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는데 이것을  흩어진 관심사(Crosscutting Concerns)라 부른다.

     

     

     

     

    스프링은 AOP를 AspectJ의 문법을 통해서 작성할 수 있는데 이를 통해 아래 3가지를 할 수 있다. 

     

    1) 핵심 비즈니스 로직에만 집중해서 코드를 개발할 수 있게 되었다.
    2) 각 프로젝트마다 다른 관심사를 적용할 때 코드의 수정을 최소화시킬 수 있다.
    3) 원하는 관심사의 유지보수가 수월한 코드를 구성할 수 있다. 

     

     

     


     

    의존성 주입 테스트 - 설정

     

    스프링에서는 생성자를 이용한 주입과 setter 메서드를 이용한 주입으로 의존성 주입을 구현한다. 설정 방식은 주로 xml이나 어노테이션을 이용해서 처리한다. 

     

     

    1.  junit 라이브러리의 버전을 변경한다.

     

     

     

    2. spring-test 라이브러리를 추가한다. 

    (spring-webmvc 밑쪽에 추가하면 된다.)

     

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${org.springframework-version}</version>
    </dependency>

     

     

     

    의존성 주입 테스트 -  객체 클래스 생성

     

     

    기본 설정 작업을 해줬다면 의존성 주입을 위한 테스트를 해보자.

     

    3. src/main/java에 org.zerock.sample 패키지를 생성하고 Chef와 Restaurant 클래스를 각각 생성한다.

     

     

    org.zerock.sample.Chef.java

    package org.zerock.sample;
    
    import org.springframework.stereotype.Component;
    
    import lombok.Data;
    
    @Component // 스프링 자체에서 객체로 가지고 관리해야겠다고 인식함.
    @Data //lombok의 setter 생성 기능, toString()등 자동으로 생성하도록 하는 어노테이션
    public class Chef {
    
    }

     

    org.zerock.sample.Restaurant.java

    package org.zerock.sample;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import lombok.Data;
    import lombok.Setter;
    
    @Component
    @Data
    public class Restaurant {
    
    	//Autowired : 이 객체를 주입해주시겠나요, 연결해주시겠나요
    	@Setter(onMethod_ = @Autowired)
    	private Chef chef;
    
    }

     

     

    위의  2 코드가 의미하는 것은 Restaurant 객체는 Chef 타입 객체를 필요로 한다는 상황이다.

    @Component는 스프링에게 해당 클래스가 스프링에서 관리해야 하는 대상임을 표시하는 어노테이션이고, @Setter는 자동으로 setChef()를 컴파일 시 생성한다. 

     

    @Setter에서 사용된 onMethod 속성은 생성되는 setChef()에 @Autowired 어노테이션을 추가한다. 

     

     

     

     


     

    의존성 주입을 위한 방법은 아래와 같은 3가지가 있다. 

     

    1. setter 주입

    2. 생성자 주입 

    3. 필드 주입

     

    setter주입은 위의 Restaurant 클래스에서 사용한 거와 같이 onMethod_를 이용하는 것이고 

    필드 주입은 @Autowired만 작성하면 되는 것이다. 하지만 이 방법은 그리 선호하진 않는다고 한다. 

     

    요즘 인기 있는 방법으로 생성자 주입이 있는데, 

    @RequiredArgsConstructor 어노테이션을 추가하고 변수 생성 시 final 예약어를 넣어주면 다.@RequiredArgsConstructor은 변수 중 final이 붙은 변수를 찾아서 객체 주입을 해주는 것! 

     

     

     

     

    xml을 이용하는 의존성 주입 설정

     

    스프링은 클래스에서 객체를 생성하고 객체들의 의존성에 대한 처리 작업까지 내부에서 모든 것이 처리된다. 스프링에서 관리되는 객체를 흔히 '빈(bean)'이라고 하는데, 이에대한 설정은 XML과 Java를 이용해서 처리할 수 있다. 

     

    기존의 시스템은 아직 xml을 선호하고 있지만 java를 이용하는 설정도 많이 사용되고 있다. 내가 정리할 포스팅에선 xml을 사용할 예정이니 참고! 

     

    그럼 본격적으로 xml을 이용해 스프링에서 관리해야 하는 객체들을 처리하기 위한 방법을 제시해볼까한다.

     

     

     

    포르젝트 src 폴더 내에 'root-context.xml'은 스프링 프레임워크에서 관리해야 하는 객체를 설정하는 설정 파일이다. root-contxt.xml을 클릭하면 아래쪽에 'NameSpaces'라는 탭이 보이게 되는데 이때 'context'라는 항목을 체크한다. 

     

     

     

     

     

    source 탭에서 아래 코드를 추가한다.

    <?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-4.3.xsd">
    	
    	<!-- Root Context: defines shared resources visible to all other web components -->
    	<context:component-scan base-package="org.zerock.sample"></context:component-scan>
    		
    </beans>
    <context:component-scan base-package="org.zerock.sample"></context:component-scan>

     

    위의 문장을 추가해주면 되는 것. 이 의미는 org.zerock.sample 패키지에 있는 component들을 스캔한다는 의미이다. 

    xml을 저장하고 'Bean Graph' 탭을 선택해보면 2개의 객체가 설정된 것을 확인할 수 있다. 

     

     

     

     

    스프링 동작 이해하기

     

     

     

    1. 스프링 프레임워크가 시작되면 먼저 스프링이 사용하는 메모리 영역을 만들게 되는데 이를 컨텍스트(Context)라고 한다. 스프링에서는 ApplicationContext라는 객체가 만들어진다.

     

    더보기

    ApplicationContext를 스프링 컨테이너라고 한다. ApplicationContext BeanFactory 인터페이스의 하위 인터페이스이다. 즉, ApplicationContext BeanFactory에 부가기능을 추가한 것이다.

     

     

    2. 스프링은 자신이 객체를 생성하고 관리해야 하는 객체들에 대한 설정이 필요한데 이에 대한 설정은 root-context.xml 파일이다. 

     

    3. root-contxt.xml에 설정되어 있는 <contxt:component-scan> 태그를 통해서 'org.zerock.sample' 패키지를 스캔하기 시작한다.

     

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

     

    5. Restaurant 객체는 Chef 객체가 필요하다는 어노테이션(@Autowired) 설정이 있으므로, 스프링은 Chef 객체의 래퍼런스를 Restaurant 객체에 주입한다. 

     

     

     

    위의 5가지 항목을 읽어보면서 스프링의 동작 과정에 대해 이해할 수 있게되었다.  그럼 동작 과정을 테스트하기 위한 테스트 코드를 작성해보자. 

     

     

     

     

    테스트 코드 

     

    src/text/java 폴더 내에 'org.zerock.sample.SampleTests' 클래스를 추가한다.

     

    package org.zerock.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.extern.log4j.Log4j2;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
    @Log4j2
    public class SampleTests {
    
    	@Autowired
    	private Restaurant restaurant;
    	
    	
    	//JUnit 같은 경우는 test 코드를 만들어보는게 좋다.
    	@Test
    	public void testExist() {
    		assertNotNull(restaurant);
    		
    		log.info(restaurant);
    		log.info("----------------------");
    		log.info(restaurant.getChef());
    	}
    }

     

     

    위의 테스트 코드는 우선 현재 테스트 코드가 스프링을 실행하는 역할을 할 것이라는 것을 @Runwith 어노테이션으로 표시한다. 다음 @ContextConfiguration 어노테이션과 속성값인 문자열은 지정된 클래스나 문자열을 이용해 필요한 객체들을 스프링 내에 객체로 등록한다. ( 흔히 스프링의 빈으로 등록된다 표현한다.) @ContextConfiguration에 사용되는 문자열을 'classpath:'나 'file:'을 이용할 수 있으므로, 이클립스에서 자동으로 생성된 root-contxt.xml의 경로를 지정할 수 있다. 

     

     

    @Autowired는 해당 인스턴스 변수가 스프링으로부터 자동으로 주입해 달라는 표시이고, 스프링은 정상적으로 주입이 가능하다면 obj 변수에 Restaurant 타입의 객체를 주입하게 된다. testExist()에 선언되어 있는 @Test는 JUnit에서 테스트 대상을 표시하는 어노테이션이다. assertNotNull()은 restaurant 변수가 null이 아니어야만 테스트가 성공하는 것을 의미하다. 테스트 작업은 프로젝트 초기 설정해두고 사용하는 습관을 갖는게 좋다.

     

    'Run As > Junit Test'를 실행해 테스트 결과를 확인한다. 

     

     

     


     

    결과에 대해 주목해야 하는 부분은 다음과 같다. 

     

    1. new Restaurant()와 같이 Restaurant 클래스에서 객체를 생성한 적이 없는데도 객체가 만들어졌다는 점.! 

    2. Restaurant 클래스의 @Data 어노테이션으로 Lombok을 이용해 여러 메서드가 만들어진 점

    3. Restaurant 객체의 Chef 인스턴스 변수(멤버변수)에 Chef 타입의 객체가 주입되어 있다는 점. 

     

     

     

    코드에 사용된 어노테이션

     

     

     

     

     

    ㅇㅇ

    반응형

    댓글

Designed by Tistory.