자바 시리즈 6 - Spring의 등장, 특징

Taeseon Kim·2023년 4월 10일
1

멀리 돌아왔다.
드디어 이 시리즈의 가장 큰 목표, Spring이다. Spring이 등장한 배경을 알기 위해 이 시리즈를 기획했는데, 깊이 들어가다보니 EJB가 나왔고, EJB라는 것 자체가 그렇게 어려우면 안쓰면 될 텐데! 하고 더 파보다 보니 java의 등장과 발전 과정이 나왔기에, 시리즈를 기획할 수 밖에 없었다.

아무튼, 이번 포스팅의 주제는 Spring의 등장이다. 자료 조사 자체가 만만치 않았다. 여러 서적과 블로그, 당시 진행했던 강연과 같은 것들을 훑어 보고 이를 정리하다보니 많은 시간이 걸려 이 포스팅이 늦어지게 되었다.

먼저, 지난 포스팅 마무리 단계에서 나왔던 EJB를 사용하게 된 계기에 대해 말해보자면,

  1. 트랜잭션 관리
  2. 인증과 접근 제어
  3. EJB 인스턴스 풀링
  4. 세션 관리
  5. DB와의 커넥션 관리

위와 같은 로우 레벨 기술을 개발자들이 신경쓰지 않아도 될 수 있게, EJB 사양이 이를 대신 해주었던 것이다.

하지만 이를 위해 EJB의 요구 사항을 지켜야 했다. 간단한 DAO를 만들 때에도, 비즈니스 로직보다 EJB 종속 코드가 더 많아지게 되었다.
또한 EJB는 EJB 컨테이너 안에서만 동작하기에, 자동화 테스트는 불가능에 가까웠고, 간단한 코드 수정을 진행하기 위해 수정 - 빌드 - 배포 - 테스트 의 사이클을 반복해야 했다.
또한 벤더사(AWS, KT cloud와 같은 클라우드 플랫폼이라고 이해하면 된다.)마다 구현한 EJB 컨테이너 방식이 달라 분산 시스템을 구축하거나 이미 구축된 서버를 변경하는 것이 어려웠다.
가장 큰 문제로는, OOP 설계의 장점이 흐려지게 되었다. EJB 컨테이너에 종속되는 객체인 EJB 사양 준수를 위해 많은 클래스를 상속받아야만 했다. 이로 인해 OOP의 상속과 다형성의 이점을 거의 포기해야 했다.

결론적으로, 개발자들의 로우 레벨 개발에 대한 부담을 덜기 위해 나온 기술에 대한 종속 때문에 오히려 생산성이 감소하게 되었다.

POJO 개념의 등장

외국의 어떤 세 개발자는 이렇게 기술 종속적인 EJB대신 가장 기본적인 자바 객체에 비즈니스 로직을 개발하면 오히려 수많은 장점이 있다는 점을 2000년 경 컨퍼런스에서 발표했다.
그리고 EJB라는 기술 종속적 용어를 철학적으로 반대하는 개념인 POJO(Plain Old Java Object)라는 용어를 만들게 되었다.

POJO는 특정 규약, 환경, 기술, 프레임워크 따위에 의해 종속되지 않는 자바 객체이다.
POJO를 정리해보면 이렇다.

  1. 미리 정의된 클래스를 상속받지 않는 클래스
    public class Foo extends javax.servlet.http.HttpServlet {
    HttpServlet을 상속받기에, POJO라고 할 수 없다.
  2. 미리 정의된 인터페이스를 구현하지 않는 클래스
    public class Bar implements javax.ejb.EntityBean {
    EntityBean을 구현하는 객체이기에, POJO라고 할 수 없다.
  3. 어노테이션을 포함하지 않는 클래스
    @javax.persistence.Entity public class Baz {
    @Entity 어노테이션을 포함하기에, POJO라고 할 수 없다..만!

POJO는 사실 기술적 어려움으로 인해 실제 사용 시 어노테이션을 필요로 한다.
그리고 어노테이션은 그 자체로는 어떠한 기능도 수행하지 못하는 메타데이터이기 때문에, 위 두 가지 경우의 상속, 구현과 같이 기능에 직접적으로 연관되는 항목들보다는 POJO의 사상에 가깝다.
3번과 같은 객체는 어노테이션만 제거하면 POJO의 형태를 띄기에, 어노테이션을 붙이는 것까지는 POJO로 쳐주기도 한다.

이러한 POJO의 철학을 기반으로 EJB가 하는 역할을 대체하기 위해 나온 프레임워크가 바로 Spring이다.

드디어 Spring..

간단히 정리하자면, Spring은 경량 컨테이너로서 자바 객체(POJO)들을 관리해준다.
(물론 최근의 Spring은 더 이상 EJB와 비교해 경량이라고 말하기도 어렵지만, 애시당초 경량 컨테이너라고 표현한 것 자체가 성능보단 EJB Bean보다 제약과 구성 요소가 적다는 점에서 구조적 관점의 경량화라고 볼 수 있다.)
객체의 생성, 사용, 그리고 소멸과 같은 라이프사이클을 직접 관리해줌으로써 개발자가 직접 객체를 생성하고 관리하지 않아도 된다.

또한 이렇게 사용하는 객체를 프레임워크 자체에 종속되지 않게 작성하기 때문에, 개발자는 서비스에 맞는 OOP 방식의 객체를 만들 수 있으며, 유연한 자동화 테스트가 가능하다.
즉, EJB가 가진 프레임워크의 이점, Java의 객체 지향적 이점은 모두 가지면서, 프레임워크 없이 비즈니스 로직을 테스트할 수 있는 것이다.

여기저기 찾다보면 알 수 있는 Spring의 특징은 다음과 같다.

  1. POJO(Plain Old Java Object)
  2. AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)
  3. IoC(Inversion of Control, 제어 역전)
  4. DI(Dependency Injection, 의존성 주입)
  5. 객체 생명 주기 관리

POJO는 위에서 설명했으니 이해가 되었을 것이라 치고,

AOP는 뭐냐?

관점 지향 프로그래밍이라고 하는데, 잘 이해가 되지 않는 단어다.
그래서 알아보다가 정말 적절한 단어를 찾아냈는데, 횡단 관심사라는 단어이다.

예를 들어 우리가 위와 같이 입금, 출금, 이체 세 가지 기능을 구현했다고 치자. 이 때 이 세 가지 기능에는 모두 로그나 보안, 트랜잭션과 같은 공통의 기능을 하는 코드들이 중복으로 들어 간다.

위 그림과 같이 입금, 출금, 이체와 같은 비즈니스 로직을 종단 관심사, 즉 비즈니스 기능이라고 쳤을 때, 이 비즈니스 로직을 수행하면서 공통적으로 작동하는 코드들이나 구현 내용을 횡단 관심사라고 할 수 있다.
Spring에서는 @Aspect 어노테이션을 통해 AOP를 설정하여 횡단 관심사를 구성할 수 있다. 자세한 내용은 AOP 관련 예제가 나와 있으니 이를 검색해보자.

IoC, DI는 뭐냐?

제어 역전, 의존성 주입이라고 하는데, 이 또한 명확한 이해가 되지 않는다.
이 두 가지를 같이 설명하기에 너무 좋은 글이 있어 인용하려 한다.
IoC와 DI
이 블로그를 통해 완전히 이해할 수 있었는데 이 분의 글을 그대로 작성해보겠다.

의존성이란?

두 가지 개념을 설명하기에 앞서,
이 분과 마찬가지로 나도 '의존성이란 무엇인가?'라는 의문이 있었다.
실제로 개발을 할때, 우리는 build.gradle,pom.xml과 같은 빌드 정보 파일 내 적는 라이브러리와 프레임워크를 보고 의존성이라는 단어를 사용하곤 한다.

결국 이 서비스를 구동하기 위해서 해당 파일 내의 라이브러리와 프레임워크가 필요하다는 말을 '의존성'이라는 단어로 표현하는 것이다.
내가 만드는 서비스는 lombok이 필요하다.
를 반대로 하면

lombok은 내가 만드는 서비스에 필요한 것이다.
즉, lombok은 내가 만드는 서비스의 의존성이다.

라고 바꿀 수 있는 것이다.

public class Pizza {
    void eat() {
        System.out.println("피자를 먹었다. 냠냠");
    }
}
public class Person {
    void eat() {
        Pizza pizza = new Pizza();
        pizza.eat();
    }
}

사람이 지 혼자 피자를 만들어 먹는 코드이다.
위 코드에서 PersonPizza의 관계는 어떨까?
피자는 사람이 없어도 구동이 가능하다.
사람은 피자가 없으면 식사를 못한다. 즉 구동을 못한다. 왜냐면 사람이 먹는 행위, 즉 eat()메서드를 위해서는 Pizza 클래스가 필요하다.
또한 위 코드에서 다른 음식을 먹으려면 사람의 코드를 대폭 수정해야 한다.

위와 같은 상황을 사람은 피자에 강한 의존성을 가진다고 말한다.

그럼 사람이 굳이 피자 말고도 다른 음식을 먹게 해주려면 어떻게 해야 할까?
이럴때 쓰라고 interface라는 java 문법이 존재한다.

public class Pizza implements Food{
    @Override
    void eat() {
        System.out.println("피자를 먹었다. 냠냠");
    }
}
public class Hotdog implements Food{
    @Override
    void eat() {
        System.out.println("핫도그를 먹었다. 냠냠");
    }
}
public class Person {
    void eat(Food food) {
        food.eat();
    }
}

사람은 음식을 먹을 수 있다. 즉 음식이라는 카테고리 안에 있는 것이라면 eat()메서드에 넣어 사용할 수 있다.
이 코드의 경우 Pizza가 없어도 다른 Food를 구현하는 클래스를 가져올 수 있기 때문에 Pizza와 Person은 약한 의존성을 가진다.

그래서 DI는 뭔데?

Injection, 즉 주입이라는 단어가 약간 어려운데, 이를 전달(Pass)로 이해하면 좋을 것 같다.
위 코드를 그대로 가져온다면 Person은 eat을 위해 Food가 필요하다.
그렇다면 해당 코드에 의존성을 주입하는 것은,
Person에게 Food를 전달하는 것이라고 볼 수 있다.

그럼 사람에게 음식을 주는 방법은 어떤게 있을까?

  1. 먹고 싶어할 때 주기
  2. 먹고 싶어할 때를 대비해 오자마자 주기

이렇게 두 가지가 있다.

먼저 먹고 싶어할 때 주기는, 위의 코드와 같이 메서드의 파라미터를 통해 주입하는 방식이다.

public class Person {
    void eat(Food food) { // 먹을거야? 자 여기
        food.eat();
    }
}

이러면 eat()메서드가 실행될 때 Food가 주입되게 된다.

두 번째로 먹고 싶어할 때를 대비해 오자마자 주기는, 생성자를 이용한 주입이다.

public class Person {
    Food food;

    public Person(Food food) { // 어 왔어? 가지고 있다가 이따 배고프면 먹어
        this.food = food;
    }

    void eat() {
        food.eat();
    }
}

그럼 IoC는 뭔데?

제어 역전이다. 뭔소린지 몰라 쳐보니 위키 백과에는 이렇게 나와 있다.

프로그래머가 작성한 프로그램이 재사용 라이브러리의 흐름 제어를 받게 되는 소프트웨어 디자인 패턴

뭐라는지 1도 모르겠으니까 다른 걸 보자.

프레임워크가 정의한 인터페이스를 클라이언트 코드가 구현을 하고, 구현된 객체를 프레임워크에 전달(주입)하여 프레임워크가 제어를 가지게 하는 것

이를 쉽게 피자와 사람으로 치면

Food 인터페이스를 Pizza가 구현하고, Pizza를 Person에 주입하여 Person이 먹을 수 있게 하는 것

이라고 볼 수 있다.

그럼 결국 IoC의 개념과 DI 개념은 비슷한 개념인 것이다.
제어 역전이라는 것은,
기존 코드인

public class Pizza {
    void eat() {
        System.out.println("피자를 먹었다. 냠냠");
    }
}
public class Person {
    void eat() {
        Pizza pizza = new Pizza();
        pizza.eat();
    }
}

여기에서는 Person이 직접 피자를 만들어 먹는다. 즉 제어를 코드 내에서 진행하며, 코드를 짜는 개발자가 제어를 하는 것이다.

그럼

public class Person {
    Food food;

    public Person(Food food) {
        this.food = food;
    }

    void eat() {
        food.eat();
    }
}

여기에서는 사람이 직접 만들어 먹지 않는다. 누군가에 의해 음식이 전달되게 된다. 즉 음식이 무언가에 의해 생성되고 있다.(내가 안줬는데..)

결국 이 차이다. 생성과 같은 제어를 개발자인 내가 아닌 Spring이 해주고 있기에, 이를 Spring의 특징인 IoC, 제어 역전이라고 하는 것이다.

어쩌면 Spring의 특징이라고 표현한 1번부터 5번까지 중 3, 4, 5번은 이 정도의 접근으로 해결이 된 것 같다.
다음 포스팅에서는 Spring 내부 구조에 대해 정리 해보도록 하겠다.

profile
공부하여 이해가 된 것만 정리합니다.

1개의 댓글

comment-user-thumbnail
4일 전

IoC, AOP에 대해 찾아보던 중 발견한 글인데, 너무 잘 정리해주셔서 이해가 잘 갔어요!
포스팅 잘 읽었습니다 :)

답글 달기