[Spring Framework] IoC? DI?

스프링을 공부하다 보면 IoC 컨테이너, DI에 대한 이야기를 많이 봤을 것이다. 이 개념들은 공부하다보면 계속 나오는 단어들이기 때문에 한번 제대로 짚고 가지 않으면 스프링에 대한 이해를 하기 힘들다. 이번 포스팅에서는 IoC, DI 뿐만 아니라 POJO, AOP, PSA도 정리를 해봤다. 스프링에 대한 기초 지식을 학습해 보자.

🍃 Spring Framework

스프링 삼각형

Spring Framework(이하 Spring)은 자바를 기반으로 만들어진 엔터프라이즈 경량급 프레임워크다. 다양한 라이브러리와 기능을 제공해 보다 쉽게 어플리케이션을 만들 수 있게 해주며, POJO와 3개의 핵심 개념(IoC / DI, AOP, PSA)으로 설명된다.

참고: 엔터프라이즈급 개발은 기업을 대상으로 하는 개발을 말하며, 경량급의 뜻은 스프링 자체가 가볍거나 작은 규모라는 뜻이 아니다. 불필요한 기능들을 제외했다는 면에서 무겁지 않다는 뜻이다.


☕ POJO(Plain Old Java Object)

스프링은 POJO(Plain Old Java Object) 기반의 프레임워크이다.
Plain Old Java Object

  • 오래된 방식의 순수한 자바 오브젝트 / 평범한 구식 자바 객체

마틴 파울러는 스프링 등장 전 널리 사용되던 EJB처럼 복잡하고 제한적인 기술보다는 자바의 단순 오브젝트를 이용해 비지니스 로직을 구현하는 편이 낫다고 생각했다. 따라서 왜 사람들이 사용하기를 꺼려하는지 의문을 가졌고, 그는 EJB처럼 그럴싸한 이름이 없어서 사람들이 사용을 주저하는 것이라고 결론을 내렸다. 거기서 탄생한 용어가 바로 POJO(Plain Old Java Object)다.

EJB(Enterprise Java Beans)
Sun사에서 엔터프라이즈 개발을 단순화하기위해 만들어낸 Java 스팩이다.
스프링이 등장하기 이전에는 EJB가 자바 엔터프라이즈 애플리케이션 개발 시장을 독점하고 있었다. 코드들이 EJB 기술에 지나치게 종속되어야 한다는 치명적인 단점이 있었다.

POJO의 조건

  • 특정 규약(contract)에 종속되지 않는다.
    • Java 언어와 꼭 필요한 API 외에 종속되지 않는다.
  • 특정 환경에 종속되지 않는다.
  • 객체지향 원리에 충실해야 한다.
    • 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트

POJO의 장점

  • 간결하고 깔끔하게 코드를 작성할 수 있다.
  • 간편하게 테스트를 할 수 있다. (특정 기술이나 환경에 종속적이지 않기 때문에)
  • 객체지향적인 설계를 자유롭게 적용할 수 있다.

🥪 IoC(Inversion of Control) 와 DI(Depedency Injection)

스프링은 IoC(Inversion of Control)와 DI(Depedency Injection)을 지원한다.

🍅 IoC(Inversion of Control)

IoC(Inversion of Control: 제어의 역전)은 객체를 생성, 제거 하는 제어권이 개발자가 아닌 프레임워크(Spring, IoC Container) 에게 위임 되는 것을 말한다.

IoC의 장점

  • 프로그램의 진행 흐름과 구체적인 구현을 분리시킬 수 있다.
  • 개발자는 비즈니스 로직에 집중할 수 있다.
  • 구현체 사이의 변경이 용이하다.
  • 객체 간 의존성이 낮아진다.

🥬 DI(Depedency Injection)

DI(Dependency Injection: 의존성 주입)은 객체간의 의존 관계를 직접 생성하는 것이 아닌 외부(Ioc Container)로 부터 주입받아 동적으로 의존관계가 성립되는 것을 말한다.

의존성 주입 방법에는 3가지가 있다.

  • 생성자 주입
  • Setter 주입
  • 필드 주입

스프링은 세가지 방법 중 생성자 주입 방식을 권장한다.
세가지 방법에 대한 자세한 설명은 생성자 주입 에서 확인해보자.

DI의 장점

  • 의존성이 줄어든다. (변경에 덜 취약해진다.)
  • 모의 객체를 주입할 수 있기 때문에 단위 테스트가 쉬워진다.
  • 가독성이 높아진다.
  • 재사용성이 높아진다.

🍞 IoC 와 DI의 예시

Before

1
2
3
4
@Service
public class MemberService {
	private final MemberRepository memberRepository = new MemberRepository();
}
  • 클래스 내부에서 의존하는 객체를 new 키워드를 통해 개발자가 직접 생성했다.

After

1
2
3
4
5
6
7
8
9
@Service
public class MemberService {
	private final MemberRepository memberRepository;

	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
}
  • 클래스 내부에서 객체를 개발자가 직접 생성하는 것이 아닌, 제어권을 스프링에게 위임하여 스프링에게 객체 생성 및 생명주기를 맡겼다. → 제어의 역전
  • @Autowired를 통해 객체를 주입받았다. → 의존성 주입

🕵️ AOP(Aspect Oriented Programming)

스프링은 AOP(Aspect Oriented Programming)을 지원한다.

AOP

AOP(Aspect Oriented Programming: 관점 지향 프로그래밍)은 로깅, 보안, 트랜잭션과 같은 공통 기능을 분리하여 하나의 책임을 가지게 하는 프로그래밍 기법을 말한다.

AOP 장점

  • 중복 코드 제거: 로깅, 보안, 트랜잭션 처리와 같은 반복적인 코드들을 분리하여 하나의 공통 관심사로 처리함으로써 코드의 중복을 줄일 수 있다.
  • 모듈화: 핵심 비즈니스 로직과 보조적인 기능(관심사)을 분리함으로써 모듈화를 향상시키고 유지 보수성을 높인다.
  • 유연성: 특정 기능을 여러 클래스에 일일이 구현하지 않고, 원하는 지점에 쉽게 적용할 수 있다.
  • 강력한 유지 보수성: 로직과 관련 없는 코드가 분리되므로 코드가 변경될 때 영향 범위가 줄어들어 유지 보수가 용이하다.

AOP 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBeforeMethod(JoinPoint joinPoint) {
        System.out.println("Before Method: " + joinPoint.getSignature().getName());
    }

    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void logAfterMethod(JoinPoint joinPoint, Object result) {
        System.out.println("After Method: " + joinPoint.getSignature().getName() + ", Result: " + result);
    }
}

위 예시에서는 com.example.service 패키지 내의 모든 메서드 호출 전에 로그를 출력하고, 메서드가 실행된 후에 반환값을 로그에 기록한다.

AOP 관련 어노테이션에 대한 자세한 설명은 해당 글을 참고해주길 바란다.


🌷 PSA(Portable Sevice Abstraction)

스프링은 PSA(Portable Sevice Abstraction)을 지원한다.

PSA(Portable Sevice Abstraction: 일관성 있는 서비스 추상화)은 환경의 변화와 관계없이 일관된 방식의 기술 접근 환경을 제공하는 추상화 구조를 말한다.

PSA 장점

  • 기술 의존성 감소: 구체적인 기술에 의존하지 않고 스프링의 추상화 계층을 통해 다른 환경으로의 이식성을 높일 수 있다.
  • 일관성 있는 개발 경험: 트랜잭션 처리, 메시징, 데이터 액세스 등 다양한 서비스 접근 방식을 일관된 API로 제공하여 개발자의 학습 곡선을 줄여준다.
  • 유연한 환경 교체: 개발된 코드를 수정하지 않고도 로컬, 클라우드, 또는 다른 인프라로의 전환이 쉽다.
  • 표준화된 접근 방식: 개발자는 다양한 환경에서도 동일한 스프링의 API를 사용할 수 있어, 코드 재사용성이 높아진다.

PSA 예시

  • 데이터 액세스: 스프링은 JPA, MyBatis 등 여러 데이터 액세스 기술을 추상화하여 동일한 방식으로 접근할 수 있도록 한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    @Repository
    public class UserRepository {
    
      @Autowired
      private JdbcTemplate jdbcTemplate;
    
      public User findById(Long id) {
          return jdbcTemplate.queryForObject(
              "SELECT * FROM users WHERE id = ?",
              new Object[]{id},
              (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"))
          );
      }
    }
    

    이 예시는 스프링의 JdbcTemplate을 사용하여 데이터베이스에 접근하는데, 동일한 코드를 다른 데이터베이스 시스템으로도 쉽게 이식할 수 있다.

PSA는 트랜잭션 관리, 메시징, 데이터베이스 액세스 등 다양한 환경에서 일관된 방식의 추상화를 제공하여 코드의 이식성과 유지 보수성을 높여준다.


참고

Leave a comment