백엔드 개발자 로드맵 따라가기 4. 백엔드 프레임워크 - Spring

박성수·2020년 11월 24일
16

1. 개요

스프링 프레임워크는 자바 개발시 개발자가 자유롭게 객체 지향적 설계를 하면서도 간결한 코딩, 코드 재사용 등의 필수 기능을 단순한 형태로 사용하기 위해 나온 POJO 기반의 자바 백엔드 프레임워크이다.

POJO(Plain Old Java Object), 처음 자바가 만들어졌을 당시의 자바가 추구했던 객체 지향 언어로서의 특징을 살리는 코딩 스타일로 회기하자는 의미에서 나온 용어이다. (※필자의 주관)

2. 스프링 핵심 개념

2-1. AOP (Aspect Oriented Programming)

관점 지향 프로그래밍이란, 쉽게 말해 관점에 따라 기능들을 모듈화 (공통된 기능을 하나의 단위로 묶는 것) 하여 코드 중복, 객체들 간의 관계 복잡도 증가 등을 해결하기 위한 개발 방법을 의미한다.

스프링 AOP는 프록시 기반으로 구현되었으며, 스프링 빈에만 적용 가능하다. 우리가 사용하는 @Controller, @Service, @Repository 등은 AOP의 구현체라 할 수 있다.

당연하게도 스프링에서는 필요에 따라 커스텀한 프록시 객체를 만들어서 사용할 수 있다.

성능 체크를 위해 메서드 수행 시간을 체크하는 기능을 추가한다고 가정해보자.

  • 스프링 AOP를 사용하지 않는 경우
public void testA() {
        // 메서드 성능 체크가 필요한 로직마다 StopWatch 로직을 구현해야 한다.
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        int sum = 0;
        for (Integer i : integerList) {
            sum += i;
        }
        System.out.println(sum);

        stopWatch.stop();
        System.out.println(stopWatch.prettyPrint());
}

public void testB(){
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        int mul = 0;
        for (Integer i : integerList) {
            mul *= i;
        }
        System.out.println(mul);

        stopWatch.stop();
        System.out.println(stopWatch.prettyPrint());
}
  • 스프링 AOP를 사용하는 경우
@PerfLogging // 어노테이션으로 코드 중복 없이 기능 추가 가능하다.
public void proxyTestA() {
        int sum = 0;
        for (Integer i : integerList) {
            sum += i;
        }
        System.out.println(sum);
}

@PerfLogging
public void proxyTestB(){
        int mul = 0;
        for (Integer i : integerList) {
            mul *= i;
        }
        System.out.println(mul);
}

프록시 객체란, 적용할 기능(타켓)의 주요 기능은 수정하지 않고 타겟의 전,후에 부가 기능을 추가한 객체를 의미한다.

2-1-1. 커스텀 어노테이션 생성

  1. 어노테이션 생성
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PerfLogging {
}
  1. Aspect 생성
@Component
@Aspect
public class PerfAspect {

    @Around("execution(* com.example.springdemo.proxy.ProxyService.*(..))")
    public Object logPerf(ProceedingJoinPoint joint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object process = joint.proceed();
        stopWatch.stop();
        System.out.println(stopWatch.prettyPrint());
        return process;
    }
}
  1. 적용
@PerfLogging // 어노테이션으로 코드 중복 없이 기능 추가 가능하다.
public void proxyTestA() {
        int sum = 0;
        for (Integer i : integerList) {
            sum += i;
        }
        System.out.println(sum);
}

2-1-2. AOP 주요 개념

  • Aspect : 흩어진 관심사를 모듈화하는 클래스
  • Target : Aspect 적용 대상 (메서드, 클래스 등)
  • Retention : 어노테이션 유효 기간 설정 (RUNTIME, CLASS, SOURCE)
  • Advice : Aspect를 언제 적용할지를 정의
    1. @Before : 메서드 실행 전 기능 수행
    2. @After : 메서드 실행 후 기능 수행
    3. @AfterRunning : 메서드 실행이 성공한 후 기능 수행
    4. @AfterThrowing : 메서드 실행이 실패한 후 기능 수행
    5. @Around : 메서드 실행 전,후 기능 수행 proceed()를 통해 시점을 제어할 수 있다.
  • JointPoint : 메서드 실행 시점을 제어
  • PointCut : Aspect가 적용될 대상을 선정

  • Weaving : Aspect가 지정된 객체에 대한 프록시 객체를 생성하는 과정

스프링 AOP에서는 CGLIB Proxy, JDK Dynamic Proxy를 이용한 Run-time Weaving 방식을 사용하여 RunTime시 프록시 객체가 생성된다.

2-2. PSA (Portable Service Abstract)

스프링 PSA란, 앞서 설명한 AOP 기술로 만들어진 여러 어노테이션들을 선언하는 것만으로도 별도의 추가 코드 없이 원하는 서비스를 제공하게되는데, 이렇게 내부적으로 서비스 코드가 추상화되어 숨겨진채로 개발 편의성을 제공해주는 것을 의미합니다.

즉 환경의 변화에 관계없이 일관된 방식의 기술로 접근 환경을 제공하려는 추상화 구조를 의미한다.

예시1) DB 트랜잭션 관리 로직이 기본 JDBC(DatasourceTransactionManager)를 사용하냐, ORM(JpaTransactionManager)을 사용하냐에 따라 코드가 달라지지만 우리는 @Transactional 어노테이션을 사용시 내부 로직이 바뀌는 것을 고려하지 않아도 된다. @Transactional 이라는 하나의 추상화로 여러 서비스를 제공하게 해주기 때문이다.

예시2) 스프링 Web MVC는 기본적으로 Tomcat을 WAS로 사용한다. 하지만 스프링 WebFlux를 사용하면 Tomcat이 아닌 Netty 기반으로 동작하게 된다.

따라서 원래라면 구현 코드도 변경을 해야하지만 Spring Web 모듈에서 PSA를

스프링 WebFlux는 WebMVC와 달리 Reactive가 반영된 비동기 Non-Blocking 방식으로 성능 및 효율적이 자원 관리를 지원하는 모듈이다. 이에 대한 자세한 내용은 다른 포스트에서 다루겠다.

2-3. IOC / DI (Inversion Of Control / Dependency Injection)

스프링을 이용하여 개발함으로써 개발의 흐름을 프레임워크가 주도한다. 때문에 제어의 흐름이 개발자 -> 프레임워크로 바뀌었다고하여 IOC(제어의 역전)이라 부른다.

IOC 컨테이너는 스프링 컨테이너라고도 불리며, 객체의 생성, 제거, 관계 설정 등의 권한을 가진다. IOC를 담당하는 컨테이너는 BeanFactory와 ApplicationContext로 구성된다.

  • BeanFactory : 스프링 IOC 최상위 인터페이스이다. Bean 생성 및 라이프 사이클 관리 등의 역할을 한다.

  • ApplicationContext : BeanFactory를 상속 받아 확장한 것이고, 리소스 로딩, 이벤트 발행 등의 역할을 한다.

2-4. 스프링에서 DI 의존성 주입 방법

2-4-1. Field 주입

@Autowired
private SampleObject sampleObject;

매우 간단하지만 아래와 같은 단점으로 권장되지 않는 방법이다.

2-4-1-1. 불변성 위반

  • 필드 주입 객체는 final로 선언할 수 없다.

2-4-1-2. 순환 의존성 알수 없음

  • 순환 의존 현상이 발생하더라도 Exception을 안뱉으므로 알 수 없다.
    서로 끊임없이 참조하다 결국 StackOverFlowError로 죽는다.

2-4-1-3. DI 컨테이너의 결합성과 테스트 용이성 위배

  • 필요한 의존성을 전달하면 독립적으로 인스턴스화 가능해야 한다.
  • 하지만 필드 주입을 사용하면 필요한 의존성을 가진 클래스를 인스턴스화 할 수 없다.

2-4-1-4. 단일 책임의 원칙

  • 하나의 클래스에서 주입되는 클래스가 많아질수록 해당 클래스가 책임지는 상황이 많이 발생하게 되는데 이는 리팩토링이 되어야할 상황이다.
  • 이 때 필드 주입 방식은 비교적 간단한 코드로 의존성을 주입하므로 다른 방식에 비해서 무분별하게 의존성을 주입하기 쉽다.

2-4-2. Setter 주입

private SampleObject sampleObject;

@Autowired
public void setSampleObject(SampleObject sampleObject){
    this.sampleObject = sampleObject;
}

2-4-3. Construtor 주입 (권장되는 방법)

@Service
public class Sample(){
    private final SampleObject sampleObject;

    public Sample(SampleObject sampleObject){
        this.sampleObject = sampleObject;
    }
}

3. 스프링 모듈 구조

기본적으로 스프링은 root-context와 servlet-context가 생성된다. 이때, root-context에서 생성되는 빈들은 모든 영역에서 공유(사용)할 수 있고, servlet-context 에서 생성되는 빈들은 해당 컨텍스트에서만 사용할 수 있다. root 와 servlet에서 겹치는 빈이 생길 경우 servlet에서 생성한 빈을 사용하게 된다.

4. 스프링 기동 순서

  1. Web Application 실행 Tomcat(WAS)에 의해 web.xml이 loading된다.
  2. web.xml에 등록되어 있는 ContextLoaderListener(ServletContextListener 구현, ApplicationContext생성 역할)가 생성된다.
  3. 생성된 ContextLoader는 root-context.xml을 Loading한다.
  4. Root-context.xml에 등록되어 있는 Spring Container가 구동된다. 이때 DAO, VO 객체들이 생성된다.
  5. Client로부터 Request 요청이 온다.
  6. DispatcherServlet(Servlet)이 생성된다. ( DispatcherServlet은 Client의 요청을 Controller로 분기한다. – Handler-Mapping)
  7. DispatcherServlet은 servlet-context.xml을 Loadin한다. ( DispatcherSevlet은 독립된 Context를 구성하면서 서로 참조불가)
  8. 두번째 Spring Container가 구동되며 응답에 맞는 Controller들이 동작한다.
    이때 첫번째 Spring Container가 구동 시작된다.

5. 스프링 부트

profile
Java 백엔드 개발자입니다. 제가 생각하는 개발자로서 가져야하는 업무적인 기본 소양과 현업에서 가지는 고민들을 같이 공유하고 소통하려고 합니다.

1개의 댓글

comment-user-thumbnail
2021년 11월 23일

한번에 쭉 핵심개념을 정리하기 정말 좋네요 감사합니다

답글 달기