⚙️ Backend/스프링(Spring) Framework

[03] Spring 프레임워크의 이론적인 설명 - 스프링 웹 프로젝트

코너(Corner) 2021. 5. 31.
반응형


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

1-1. 프레임 워크란?

뼈대나 근간을 이루는 코드들의 묶음

프레임 워크의 최대한 장점은 개발에 필요한 구조를 이미 코드로 만들어 놓았기 때문에, 실력이 부족한 개발자라 하더라도 반쯤 완성한 상태에서 필요한 부분을 조립하는 형태의 개발이 가능하다. 회사의 입장에서는 프레임워크를 사용하면 일정한 품질이 보장되는 결과물을 얻을 수 잇고, 개발자의 입장에서는 완성된 구조에 자신이 맡은 코드를 개발해서 넣어주는 형태이므로 개발 시간을 단축할 수 있다.

스프링은 비교적 시작이 늦은 프로젝트 였지만, 가장 성공적인 '경량 프레임워크'이다.

1-2. 경량 프레임 워크란?

90년대 말에 복잡한 구동 환경과 하드웨어적인 구성이 필요한 프레임워크의 반대되는 개념으로 등장했다. 과거 J2EE 기술은 너무나 복잡하고 방대했기 때문에, 그 전체를 이해하고 개발하기에는 어려운 점이 많아서, 특정 기능을 위주로 간단한 jar 파일 등을 이용해서 모든 개발이 가능하도록 구성된 프레임워크이다.

1-3. 스프링의 눈에 띄는 변화

  • Spring 2.5버전: 어노테이션을 활용하는 설정을 도입하면서 편리한 설정과 개발이 가능하도록 지원.
  • Spring 3.0버전; 별도의 설정 없이도 Java 클래스만으로 설정 파일을 대신할 수 있게 지원.
  • Spring 4.0버전: 모바일 환경과 웹 환경에서 많이 사용되는 REST방식의 컨트롤러 지원.
  • Spring 5.0버전: Reactor를 이용한 Reactive 스타일의 개발 환경 지원.

2. 스프링의 주요 특징

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

2-1. POJO 기반의 구성

스프링의 성격 자체가 가벼운 프레임워크지만, 그 내부에는 객체 간의 관계를 구성할 수 있는 특징을 가지고 있다. 스프링은 다른 프레임워크들과 달리 이 관계를 구성할 때 별도의 API 등을 사용하지 않는 POJO(Plaing Old Java Object)를 구성만으로 가능하도록 제작되어 있다.

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

2-2. 의존성 주입(DI)와 스프링

빠지지 않는 얘기가 '의존성 주입'이라는 개념이다. 의존성 주입을 결합해서 생각해 보면 '어떤 객체가 필요한 객체를 외부에서 밀어 넣는다'는 의미가 된다. 그렇다면 다음은 '왜 외부에서 객체를 주입하는 방식'을 사용하는지에 대한 문제를 알아볼 필요가 있다.

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

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

2-3. AOP의 지원

좋은 개발 환경의 중요 원칙은 '개발자가 비지니스 로직에만 집중할 수 있게 한다'이다. 스프링은 프레임워크를 이용한 개발에도 이러한 반복적인 코드를 줄이고, 핵심 비지니스 로직에만 집중할 수 있는 방법을 제공한다.

스프링은 AOP를 AspectJ의 문법을 통해서 작성할 수 있는데, 이를 통해서 개발자는

1) 핵심 비지니스 로직에만 집중해서 코드를 개발할 수 있따.

2) 각 프로젝트마다 다른 관심사를 적용할 때 코드의 수정을 최소화 시킬 수 있다.

3) 원하는 관심사의 유지보수가 수월한 코드를 구성할 수 있다.

2-4. 트랜잭션의 지원

데이터베이스를 이용할 때 반드시 신경 써야 하는 부분은 하나의 업무가 여러 작업으로 이루어지는 경우의 트랜잭션 처리다. 스프링은 이런 트랜잭션의 관리를 어노테이션이나 xml로 설정할 수 있기 때문에 개발자가 매번 상황에 맞는 코드를 작성할 필요가 없도록 설계되었다.

3. 의존성 주입 테스트

간단하게 레스토랑 객체를 만들고 레스토랑에서 일하는 셰프 객체를 주입하는 예제를 작성한다.

스프링에서는 생성자를 이용한 주입과 setter메서드를 이용한 주입으로 의존성 주입을 구현한다. 설정 방식은 주로 XML 이나 어노테이션을 이용해서 처리한다. 예제는 Lombok을 이용해서 setter메서드를 자동으로 구현되도록 할 것이고, 스프링의 동작을 테스트할 것이므로 pom.xml에서 Lombok 라이브러리를 추가하고, spring-test 라이브러리를 이용한다.

3-1. pom.xml 파일을 수정해준다.

pom.xml

      <!-- Lombok -->
      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>1.18.0</version>
         <scope>1.2.17</scope>
      </dependency>

      <!-- Log4j -->
      <dependency>
         <groupId>log4j</groupId>
         <artifactId>log4j</artifactId>
         <version>1.2.17</version>
      </dependency>

3-2. 예제 클래스 생성

3-2-1. 아래와 같이 패키지와 클래스 생성

img

3-2-2. Lombok의 setter를 생성하는 기능과 생성자, toString()등을 자동으로 생성하도록 @Data 어노테이션을 이용. 아래와 같이 작성된 코드가 의미하는 건 Restaurant 객체는 Chef 타입의 객체를 필요로 한다는 상황

@Component 는 스프링에게 해당 클래스가 스프링에서 관리해야 하는 대상임을 표시하는 어노테이션

@Setter는 자동으로 setChef()를 컴파일 시 생성!

img

3-3. XML을 이용하는 의존성 주입 설정

스프링에서 관리되는 객체를 '빈(Bean)'이라고 한다.

프로젝트 src 폴더 내에 root-context.xml은 스프링 프레임워크에서 관리해야 하는 객체(빈)를 설정하는 설정파일이다.

3-3-1. 누르고 'NamesSpaces'라는 탭에서 'context'항목을 체크한다.

img

3-3-2. 'Source'탭을 선택해서 아래의 코드를 추가한다.
<!-- ① -->
    <context:component-scan base-package="com.koreait.sample"></context:component-scan>
3-3-3. 'Bean Graph'탭을 선택해보면 Restaurant와 Cheft 객체가 설정된 것을 확인할 수 있다.

img


4. 스프링이 동작하면서 생기는 일

  1. 스프링 프레임워크가 시작되면 먼저 스프링이 사용하는 메모리 영역이 만들게 되는데 이를 컨텍스트라고 한다. 스프링에서는 ApplicationContext라는 이름의 객체가 만들어진다.
  2. 스프링은 자신이 객체를 생성하고 관리해야 하는 객체들에 대한 설정이 필요하다. 이에 대한 설정이 root-context.xml파일이다.
  3. root-context.xml에 설정되어 있는 <context:component-scan>태그의 내용을 통해서 com.koreait.sample패키지를 스캔하기 시작한다.
  4. 해당 패키지에 있는 클래스들 중에서 스프링이 사용하는 @Component라는 어노테이션이 존재하는 클래스의 인스턴스를 생성한다.
  5. Restaurant 객체는 Chef 객체가 필요하다는 어노테이션(@Autowired) 설정이 있으므로, 스프링은 Chef 객체의 레퍼런스를 Restaurant 객체에 주입한다.

위의 과정대로 동작하는지를 테스트하기 위해서는 직접 main 메서드를 만드는 방식도 있지만, 좀 더 간편하게 테스트 코드를 작성하는 방법을 사용하는 것이 좋다.

4-1. 테스트 코드를 통한 확인

  • 프로젝트 내 src/test/java 폴더 내에 com.koreait.sample.SampleTests클래스를 추가한다.

(SampleTests 클래스는 spring-test 모듈을 이용해서 간단하게 스프링을 가동시킨다. 이 때 Junit은 반드시 4.10 이상의 버전을 사용해야 한다!)

src/main/resources/ 폴더에 log4j.xmllog4jdbc.log4j2.properties두 파일을 추가한다.

log4j.xml (맥에서는 아래 코드를 사용해야 log4j 오류가 없었다.)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <!-- Appenders -->
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p: %c - %m%n" />
        </layout>
    </appender>

    <!-- Application Loggers -->
    <logger name="com.koreait.controller">
        <level value="info" />
    </logger>

    <!-- 3rdparty Loggers -->
    <logger name="org.springframework.core">
        <level value="info" />
    </logger>    

    <logger name="org.springframework.beans">
        <level value="info" />
    </logger>

    <logger name="org.springframework.context">
        <level value="info" />
    </logger>

    <logger name="org.springframework.web">
        <level value="info" />
    </logger>

    <logger name="jdbc.audit">
        <level value="warn"/>
    </logger>

    <logger name="jdbc.resultset">
        <level value="warn"/>
    </logger>

    <logger name="jdbc.connection">
        <level value="warn"/>
    </logger>

    <!-- Root Logger -->
    <root>
        <priority value="info" />
        <appender-ref ref="console" />
    </root>

</log4j:configuration>

log4jdbc.log4j2.properties

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

img

아래는 SampleTests.class 소스코드

package com.koreait.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 restaurant;

    @Test
    public void testExist() {
        assertNotNull(restaurant); // restaurant가 null이 아니어야만 테스트 성공

        log.info(restaurant);
        log.info("-==================");
        log.info(restaurant.getChef());

//        INFO : com.koreait.sample.SampleTests - Restaurant(chef=Chef(name=null))
//        INFO : com.koreait.sample.SampleTests - -==================
//        INFO : com.koreait.sample.SampleTests - Chef(name=null)

        /*
         * 1. 테스트 코드가 실행되기 위해서 스프링 프레임워크가 동작.
         * 2. 동작하는 과정에서 필요한 객체들이 스프링에 등록
         * 3. 의존성 주입이 필요한 객체는 자동으로 주입이 이루어짐
         */
    }
}
  • @Runwith : 현재 테스트 코드가 스프링을 실행하는 역할을 할 것이라고 표시한다.
  • @ContextConfigureation : 지정된 클래스나 문자열을 이용해서 필요한 객체들을 스프링 내에 객체로 등록한다.
    (흔히 스프링의 빈으로 등록된다고 표현한다.)
    이 어노테이션에서 사용하는 문자열을 classpath:file:을 이용할 수 있으므로, 이클립스에서 자동으로 생성된 root-context.xml의 경로를 지정할 수 있다.
  • @Log4j : Lombok을 이용해서 로그를 기록하는 Logger를 변수로 생성.
    별도의 Logger 객체의 선언이 없이도 Log4j 라이브러리와 설정이 존재한다면 바로 사용 가능하다.
  • @Autowired : 해당 인스턴스 변수가 스프링으로부터 자동으로 주입해달라는 표시. 스프링은 정상적으로 주입이 가능하다면 obj 변수에 Restaurant 타입의 객체를 주입한다.
  • @Test : JUnit 에서 테스트 대상을 표시하는 어노테이션. 해당 메소드를 선택하고 JUnitTest 기능을 실행한다.
  • assertNotNull() : restaurant 변수가 null이 아니어야만 테스트가 성공한다는 것을 의미한다.

4-2. 실행해보기

  • Run AS > JUnit Test

img

  • 주목할 점
  1. new Restaurant()와 같이 Restaurant 클래스에서 객체를 생성한 적이 없는데도 객체가 만들어 졌다는 점. 스프링은 관리가 필요한 객체를 어노테이션 등을 이용해서 객체를 생성하고 관리하는 일종의 '컨테이너'나 '팩토리'의 기능을 가지고 있다.
  2. Restaurant 클래스의 @Data 어노테이션으로 Lombok을 이용해서 여러 메소드가 만들어진 점.
    Lombok은 자동으로 getter/setter 등을 만들어 주는데 스프링은 생성자 주입 혹은 setter 주입을 이용해서 동작한다.
    Lombok을 통해서 getter/setter 등을 자동으로 생성하고 'onMethod' 속성을 이용해서 작성된 setter에 @Autowired 어노테이션을 추가한다.
  3. Restaurant 객체의 Chef 인스턴스 변수에 Chef 타입의 객체가 주입되어 있다는 점.
    스프링은 @Autowired와 같은 어노테이션을 이용해서 개발자가 직접 객체들과의 관계를 관리하지 않고, 자동으로 관리되도록 한다.

- 중요한 내용

  1. 테스트 코드가 실행되기 위해서 스프링 프레임워크가 동작
  2. 동작하는 과정에서 필요한 객체들이 스프링에 등록
  3. 의존성 주입이 필요한 객체는 자동으로 주입이 이루어졌다.

5. 코드에 사용된 어노테이션들

  • Lombok 관련

Lombok은 컴파일 시 흔하게 코드를 작성하는 기능들을 완성해주는 라이브러리.

@Setter : setter 메서드를 만들어줌. setter에는 3가지 속성(value, onMethod, onParam)을 부여해줄 수 있음.

@Data : Lombok에서 가장 자주 사용되는 어노테이션. @ToString, @EqualsAndHashCode, @Getter/Setter, @RequiredArgsConstructor를 모두 결합한 형태로 한 번에 자주 사용되는 모든 메소드들을 생성할 수 있따는 장점이 있음. 세부 설정이 필요 없을 때 @Data를 주로 이용한다.

@Log4j : 로그객체를 생성. 존재하지 않을 때는 @Log를 이용할 수 없다.

  • Spring 관련

@Component : 해당 클래스가 스프링에서 객체로 만들어서 관리하는 대상임을 명시하는 어노테이션.

현재 예제의 경우 @Component가 있는 클래스를 스프링이 읽어 주도록 @ComponentScan을 통해서 지정되어 있으므로 해당 패키지에 있는 클래스들을 조사하면서 @Component가 존재하는 클래스들을 객체로 생성해서 빈으로 관리하는 것.

@Autowired : 스프링 내부에서 자신이 특정한 객체에 의존적이므로 자신에게 해당 타입의 빈을 주입해주라는 표시.

스프링은 @Autowired 어노테이션을 보고 스프링 내부에 관리되는 객체 중에 적당한 것이 있는지를 확인하고, 자동으로 주입한다. 당연한 얘기지만, 필요한 객체가 존재하지 않으면 에러가 발생한다.

  • Test 관련

@ContextConfiguration : 스프링이 실행되면서 어떤 설정 정보를 읽어 들여야 하는지를 명시. 속성으로는 locations를 이용해서 문자열의 배열로 XML 설정 파일을 명시 할 수 있고, classes 속성으로 @configuration이 적용된 클래스를 지정해 줄 수도 있다.

@Runwith : 테스트 시 필요한 클래스를 지정. 스프링은 SpringJUnit4ClassRunner 클래스가 대상이 된다.

@Test : JUnit에서 해당 메서드가 JUnit 상에서 단위 테스트의 대상인지 알려준다.


반응형

댓글