3. 스프링 프레임워크의 핵심 기능

DAEILLIM·2023년 12월 2일
0
post-thumbnail

1. 의존성 주입

의존성 주입(DI, Dependency Injection)는 '의존하는 부분을 외부에서 주입하는 것'을 의미합니다. 지금은 이정도로 이해해도 충분합니다.


2. 의존성

자바에서 의존성은 여러 형태가 있습니다. 대표적으로 new 키워드로 클래스의 인스턴스를 생성하는 방법이 있습니다.

예를 들어, 어떤 프로그램에 '사용하는 객체' A 클래스와 '사용되는 객체' B 클래스가 있다고 가정하겠습니다. A 클래스에서 B 클래스를 사용하려면 new 키워드를 이용해 B 클래스의 인스턴스를 생성하고 B 클래스의 메서드를 사용하게 됩니다. 이때 B 클래스에서 구현했던 메서드를 변경하면 그 영향으로 A 클래스에서도 해당 메서드를 변경해야 합니다.

이런 관계를 'A 클래스는 B클래스에 의존한다'라고 합니다.

의존에는 클래스 의존(구현 의존)과 인터페이스 의존이 있습니다. 지금까지 설명한 new 키워드로 생성되는 클래스가 클래스 의존(구현 의존)입니다. 클래스 의존보다 인터페이스 의존이 다방면에서 훨씬 바람직합니다. 인터페이스 의존은 다음과 같은 특징이 있습니다.

  • 인터페이스는 참조를 받는 유형으로 사용할 수 있으므로 변수의 이름을 변경하지 않아도 됩니다.
  • 인터페이스가 선언된 메서드를 이용하면 클래스가 바뀌어도 메서드명을 변경하지 않아도 됩니다.

2.1 의존성 예제 1

Calculator인터페이스 안에는 calc 정수 두 개를 입력받는 메서드가 있습니다.

package ch03;

public interface Calculator {
    Integer calc(Integer x, Integer y);
}

Calculator 인터페이스의 구현 클래스로 AddCalc 는 두 정수의 합을 반환하고, SubCalc 는 두 정수의 차를 반환합니다.

package ch03;

public class AddCalc implements Calculator {
    @Override
    public Integer calc(Integer x, Integer y) {
        return x + y;
    }
}
package ch03;

public class SubCalc implements Calculator {
    @Override
    public Integer calc(Integer x, Integer y) {
        return x - y;
    }
}

Call 클래스에서 두 정수를 더하는 AddCalc 구현 클래스의 인스턴스를 생성하고 두 정수를 더할 수 있습니다.
이때 두 정수의 뺄셈을 계산하고 싶으면 인스턴스 생성 부분의 AddCalc 대신 SubCalc 클래스로 한 곳만 변경하면 됩니다.

package ch03;

public class Call {
    public static void main(String[] args) {
        Calculator calculator = new AddCalc(); // <- SubCalc() 로 변경만 하면 뺄셈을 수행
        Integer result = calculator.calc(10, 5);
        System.out.println("result = " + result); // result = 15
    }
}

하지만 '의존성 주입'을 사용하면 '사용하는 객체'의 클래스를 수정하지 않아도 됩니다. 지금까지는 인스턴스를 생성할 때 new 키워드를 사용했지만 인스턴스 생성과 같은 작업을 프레임워크에 맡길 수 있고 그 역할을 하는 것이 DI 컨테이너입니다. 스프링 프레임워크는 임의로구현한 클래슬르 인스턴스로 만들어주는 기능을 제공합니다. 즉 DI 컨테이너가 그 기능을 가지고 있습니다.

  • 의존하는 부분이란 '사용하는 객체'에 '사용되는 객체' 클래스가 작성된 상태입니다.(A 클래스에 new B(); )
  • 외부로부터 주입이란 '사용하는 객체' 클래스의 밖에서 '사용되는 객체' 인스턴스를 주입하는 것입니다.
image-20231201005250387

3. 다섯 가지 규칙

DI 컨테이너에 인스턴스 생성을 맡기고 다음의 규칙을 지키는 것으로 '사용하는 객체' 클래스를 전혀 수정할 필요가 없도록 만들 수 있습니다.

  1. 인터페이스를 이용하여 의존성을 만든다.
  2. 인터페이스를 명시적으로 생성하지 않는다.
  3. 어노테이션을 클래스에 부여한다.
  4. 스프링 프레임워크에서 인스턴스를 생성한다.
  5. 인스턴스를 이용하고 싶은 곳에 어노테이션을 부여한다.

3.1 규칙 1

'인터페이스를 이용하여 의존성을 만든다.'는 의존하는 부분에 인터페이스를 이용하는 것을 의미합니다.


3.2 규칙 2

'인터페이스를 명시적으로 생성하지 않는다.'는 인스턴스 생성에 new 키워드를 사용하지 않는다는 것을 의미합니다.


3.3 규칙 3, 4

'어노테이션을 클래스에 부여한다.'와 '스프링 프레임워크에서 인스턴스를 생성한다.'를 정리해 설명하면, 인스턴스를 생성하려는 클래스에 인스턴스 생성 어노테이션을 부여한다는 것입니다. 어노테이션에 대한 자세한 나용은 뒷부분에서 다루지만 여기서는 어떤 표식이라고 생각하세요. @Component를 어노테이션이라고 합니다.

클래스에 @Component 어노테이션 부여

image-20231201005250387

스프링 프레임워크는 시작할 때 대상 프로젝트의 모든 패키지를 스캔합니다. 이 기능을 컴포넌트 스캔(Component Scan)이라고 합니다.


컴포넌트 스캔의 실행

image-20231201005250387

컴포넌트 스캔 후 스프링 프레임워크는 인스턴스 생성 어노테이션이 부여된 클래스를 추출하고 추출한 클래스의 인스턴스를 생성합니다.


인스턴스를 생성할 대상 클래스를 추출

image-20231201005610518


대상 클래스를 인스턴스화

image-20231201005649392

인스턴스 생성 어노테이션은 용도별로 다음과 같습니다.

어노테이션개요
@Controller인스턴스 생성 지시. 스프링 MVC를 이용할 때 컨트롤러에 부여
@Service인스턴스 생성 지시. 트랜잭션 경계가 되는 도메인(서비스) 기능에 부여
@Repository인스턴스 생성 지시. 데이터베이스 액세스(리포지토리) 기능에 부여
@Component위 용도 이외의 클래스에 부여

3.5 규칙 5

'인스턴스를 이용하고 싶은 곳에 어노테이션을 부여한다.' 는 @Autowired 부여를 의미합니다.

@Autowired는 스프링 프레임워크에서 사용되는 어노테이션 중 하나로, 의존성 주입(Dependency Injection)을 자동으로 처리하기 위해 사용됩니다. 스프링은 IoC(제어의 역전) 컨테이너를 제공하며, @Autowired는 이 컨테이너를 통해 자동으로 의존성을 주입할 때 사용됩니다.

의존성 주입은 객체 지향 프로그래밍에서 한 객체가 다른 객체를 사용할 때, 이를 외부에서 주입해주는 디자인 패턴 중 하나입니다. 스프링에서는 주로 생성자 주입, 세터 주입, 필드 주입 등을 통해 의존성을 주입할 수 있습니다. @Autowired는 이 중에서 필드 주입을 간단하게 처리하는데 사용됩니다.


요약

  1. 스프링 프레임워크는 임의로 구현한 클래스를 인스턴스화하는 기능을 제공합니다.(DI 컨테이너)
  2. 스프링 프레임워크를 사용하는 어플리케이션은 인스턴스를 명시적으로 생성하지 않습니다.(new 키워드를 사용하지 않음)
  3. 정해진 어노테이션(@Component)을 클래스에 부여하는 것으로 스프링 프레임워크가 인스턴스를 생성합니다.
  4. 생성된 인스턴스를 사용하고 싶은 부분에서 필드를 준비하고 주석(@Autowired)을 부여하면 스프링 프레임워크가 인스턴스가 필요한 것으로 판단하고 인스턴스를 주입합니다.
  5. 인터페이스를 이용해서 의존성을 만들고 DI를 사용하여 '사용되는 객체' 클래스를 변경하는 경우 '사용하는 객체' 클래스의 수정 없이 변경할 수 있습니다.
  • @Component: 인스턴스를 생성하기 위한 어노테이션
  • @Autowired: 인스턴스를 주입하기 위한 어노테이션

3.6 @Component, @Autowired

@Component@Autowired는 스프링 프레임워크에서 사용되는 어노테이션으로, 각각 다른 역할을 합니다.


  1. @Component:
    • @Component 어노테이션은 해당 클래스를 스프링의 컴포넌트 스캔의 대상으로 지정합니다.
    • 컴포넌트 스캔은 스프링이 프로젝트 내에서 자동으로 빈(Bean)을 찾아 등록하는 메커니즘 중 하나입니다.
    • 스프링 컨테이너는 @Component 어노테이션이 붙은 클래스를 검색하여 해당 클래스의 인스턴스를 빈으로 등록합니다.
@Component
public class MyComponent {
    // 클래스 내용
}

  1. @Autowired:
    • @Autowired 어노테이션은 스프링이 자동으로 빈을 주입할 때 사용됩니다.
    • @Autowired 어노테이션이 필드, 생성자, 메서드에 사용될 수 있으며, 주입하고자 하는 빈을 찾아서 자동으로 주입해줍니다.
    • 주입할 빈을 찾는 기준은 타입에 의해 이루어집니다.
@Component
public class MyComponentConsumer {

    private final MyComponent myComponent;

    @Autowired
    public MyComponentConsumer(MyComponent myComponent) {
        this.myComponent = myComponent;
    }

    // 나머지 코드
}

위의 예제에서 MyComponentConsumer 클래스의 생성자에 @Autowired 어노테이션이 붙어 있습니다. 이를 통해 스프링은 MyComponent 타입의 빈을 찾아서 자동으로 주입해줍니다.

요약하면, @Component는 클래스를 스프링 빈으로 등록하는 데 사용되고, @Autowired는 스프링이 자동으로 빈을 주입할 때 사용됩니다. 두 어노테이션을 함께 사용하여 스프링 애플리케이션을 효과적으로 구성할 수 있습니다.


3.7 @Component 사용

@Component 어노테이션은 클래스에 사용되는 일반적인 컴포넌트 스캔 대상을 지정하기 위한 어노테이션입니다. 주로 클래스 레벨에서 사용되지만, 실제로는 @Component 어노테이션을 확장한 다른 세부 어노테이션이 존재합니다. 이들은 특정 용도나 레이어에 맞추어 사용할 수 있습니다.


  1. @Repository:

    • 데이터 액세스 레이어에서 사용되는 어노테이션입니다.
      주로 데이터베이스와의 상호 작용을 담당하는 DAO(Data Access Object) 클래스에 사용됩니다.

      @Repository
      public class UserRepository {
          // 데이터 액세스 코드
      }
  1. @Service:

    • 비즈니스 로직을 처리하는 서비스 레이어에서 사용됩니다.

      @Service
      public class UserService {
          // 비즈니스 로직 코드
      }
  1. @Controller:

    • 스프링 MVC에서 컨트롤러 클래스를 나타내는 데 사용됩니다.

      @Controller
      public class MyController {
          // 컨트롤러 코드
      }

이러한 어노테이션들은 모두 @Component를 기반으로 하고 있습니다. 따라서 @Component를 포함한 이러한 어노테이션들은 모두 클래스에 적용되며, 스프링은 이들 어노테이션이 붙은 클래스를 스캔하여 빈으로 등록합니다.

결론적으로, @Component 어노테이션은 클래스에 주로 사용되지만, 특정한 역할을 수행하는 다른 어노테이션들을 사용함으로써 클래스를 더 명확하게 나타낼 수 있습니다.


3.8 @Autowired

@Autowired 어노테이션은 필드, 생성자, 메서드에 모두 사용할 수 있습니다. 다양한 방식으로 의존성 주입을 수행할 수 있도록 다양한 옵션을 제공합니다.

  1. 필드에 @Autowired 사용:

    • 필드 주입은 가장 간단한 형태로, 주입받을 빈을 선언된 필드에 직접 주입합니다.

      @Component
      public class MyComponentConsumer {
      
          @Autowired
          private MyComponent myComponent;
      
          // 나머지 코드
      }
  1. 생성자에 @Autowired 사용:

    • 생성자 주입은 주로 권장되는 방식으로, 빈을 주입받을 생성자를 선언합니다.

      @Component
      public class MyComponentConsumer {
      
          private final MyComponent myComponent;
      
          @Autowired
          public MyComponentConsumer(MyComponent myComponent) {
              this.myComponent = myComponent;
          }
      
          // 나머지 코드
      }
  1. 메서드에 @Autowired 사용:

    • 메서드 주입은 일반적으로 setter 메서드에 사용되며, 주입받을 빈을 설정하는 메서드에 @Autowired를 사용합니다.

      @Component
      public class MyComponentConsumer {
      
          private MyComponent myComponent;
      
          @Autowired
          public void setMyComponent(MyComponent myComponent) {
              this.myComponent = myComponent;
          }
      
          // 나머지 코드
      }

주입할 빈을 설정하는 메서드에 @Autowired를 사용하는 경우, 메서드의 이름은 중요하지 않습니다. 메서드 이름에 따라 주입이 결정되는 것이 아니라, 메서드의 매개변수 타입을 기준으로 주입이 이루어집니다.

따라서 @Autowired는 필드, 생성자, 메서드 등 다양한 위치에서 사용할 수 있습니다. 선택적으로 사용할 수 있는데, 주로 생성자 주입을 권장하며 필드나 메서드 주입은 특별한 경우에 사용될 수 있습니다.


4. 어노테이션 역할 알아보기

어노테이션을 간단히 설명하면 다음 세 가지 항목이 됩니다.

  1. 어노테이션(Annotation)은 주석을 의미하는 영어 표현입니다.
  2. @xxx와 같은 형태로 작성합니다.
  3. 외부 소프트웨어에 필요한 처리 내용을 전달합니다.

어노테이션을 사용하면 에러를 출력하거나 프로그램의 동작을 변경하는 등 다양한 것을 할 수 있습니다.

image-20231201134154172

4.1 레이어별로 사용할 인스턴스 생성 어노테이션

어플리케이션을 만들 때는 레이어를 나누는 것이 좋습니다. 이때 레이어(Layer)는 '층'의 의미로, 계층 구조로 되어 있는 각 층을 의미합니다. 쉽게 이야기하면 복잡한 전체 내용을 한 번에 정리해 이해하지 말고 계층화하여 각 계층별로 대상의 의미를 이해하는 것입니다.

다음 세 레이어는 '도메인 주도 설계(Domain-Driven Desgin)'에서 설명한 내용입니다.

레이어내용
어플리케이션 레이어
(Application Layer)
클라이언트와의 데이터 입출력을 제어하는 레이어입니다.
@Controller
도메인 레이어
(Domain Layer)
어플리케이션의 중심이 되는 레이어로서 업무 처리를 수행하는 레이어입니다.
@Service
인프라스트럭처 레이어
(Infrastructire Layer)
데이터베이스에 대한 데이터 영속성(Persistence Context) 등을 담당하는 레이어입니다.
@Repository

이스턴스 생성 어노테이션은 레이어별로 구분됩니다.

어노테이션개요
@Controller어플리케이션 레이어의 컨트롤러에 부여
@Service도메인 레이어의 업무 처리에 부여
@Repository인프라 레이어의 데이터베이스 엑세스 처리에 부여

레이어별 인스턴스 생성 어노테이션 사용 예(주요 처리)


레이어별 인스턴스 생성 어노테이션 설명(보조 처리)

언노테이션개요
@Component@Controller, @Service, @Repository의 용도 이외의 인스턴스 생성 대상 클래스에 부여

image-20231201134154172


4.2 커스텀 어노테이션

직접 커스텀 어노테이션을 생성할 수 있습니다. 커스텀 어노테이션을 만들 때는 java.lang.Annotation 인터페이스를 상속하고 생성합니다. 또한 커스텀 어노테이션을 정의할 때는 전용 자바 파일을 생성할 필요가 있습니다.


5. 관점 지향 프로그래밍

관점 지향 프로그래밍(AOP, Aspect Oriented Programming)은 프로그램이 다음 두 가지 요소로 구성된다고 생각합니다.

  1. 중심적 관심사(Primary Concern)
    실현해야 할 기능을 나타내는 프로그램

    • 실현해야 하는 기능
  2. 횡단적 중심사(Crosscutting-Concerns)
    본질적인 기능은 아니지만 품질이나 유지보수 등의 관점에서 반드시 필요한 기능을 나타내는 프로그램

    • 예외 처리
    • 로그 정보 화면이나 파일 등으로 출력 처리
    • 데이터베이스의 트랜잭션 제어 등

AOP를 간단히 설명하면 공통 등의 '횡단적 관심사'를 추출하고 프로그램의 여러 곳에서 호출할 수 있게 설정함으로써 개발자는 실현해야 할 기능인 '중심적 관심사'에만 집중해서 개발하면 되는 구조입니다.


6. AOP(관점 지향 프로그래밍의 기초 지식)

데이터베이스 엑세스 처리에는 예외 발생 시 처리하는 내용이 반드시 포함되어야 합니다.
예외 처리를 하지 않으면 프로그램이 중지되고 자바의 경우 예외 처리를 프로그램에 포함하지 않으면 컴파일에 실패합니다.

image-20231201140527423

다수의 데이터베이스 엑세스 처리 코드를 작성하다 보면 예외 처리의 내용은 항상 동일하지만, 예외 처리는 필수이므로 항상 작성해야 합니다. 예외 처리를 포함하면 프로그램 코드가 증가하고 복잡해집니다. 구현하고 싶은 프로그램은 데이터베이스의 엑세스 처리이며 예외 처리는 구현하고 싶은 프로그램에 부수적인 내용이 됩니다.

코드의 복잡성을 줄이기 위해 '구현하고 싶은 프로그램=중심적 관심사', '부수적 프로그램=횡단적 관심사'로 분리하여 프로그램을 작성해야 합니다. 스프링 프레임워크에서 제공하는 AOP 기능을 활용하여 '중심적 관심사'와 '횡단적 관심사'를 분리하여 프로그램을 쉽게 만들 수 있습니다.

자세한 사용법을 설명하기 전, AOP의 고유 용어에 대해 설명합니다:

용어내용
어드바이스(Advice)횡단적 관심사의 구현(메서드). 로그 출력 및 트랜잭션 제어 등입니다.
애스펙트(Aspect)어드바이스를 정리한 것(클래스)입니다.
조인포인트(JoinPoint)어드바이스를 중심적 관심사에 적용하는 타이밍.
메서드(생성자) 실행 전, 메서드(생성자) 실행 후 등 실행되는 타이밍입니다.
포인트컷(PointCut)어드바이스를 삽입할 수 있는 위치.
예를 들어, 메서드 이름이 get으로 시작할 때만 처리하는 조건을 정의할 수 있습니다.
인터셉터(Interceptor)처리의 제어를 인터셉트하기 위한 구조 또는 프로그램입니다. 스프링 프레임워크에서는
인터셉트라는 매커니즘으로 어드바이스를 중심적 관심사에 추가한 것처럼 보이게 합니다.
타깃(Target)어드바이스가 도입되는 대상을 말합니다.

6.1 AOP의 구조

스프링 프레임워크에서는 '인터셉터'라는 메커니즘을 사용하여 횡단적 관심사(어드바이스)를 중심적 관심사(타깃)에 삽입하는 것처럼 보일 수 있습니다.

image-20231202134107218


6.2 어드바이스 종류

스프링 프레임워크가 제공하는 중심적 관심사에 적용하는 어드바이스는 실행 제어 내용별로 다섯 가지 종류가 있습니다. 각 어노테이션 사용 방법에 대해서는 정리하지 않고 넘어가겠습니다.

어드바이스내용어노테이션
Before Advice중심적 관심사가 실행되기 '이전'에 횡단적 관심사를 실행@Before
After Returning Advice중심적 관심사가 '정상적으로 종료된 후'에 횡단적 관심사를 실행@AfterReturning
After Throwing Advice중심적 관심사로부터 '예외가 던져진 후'로 횡단적 관심사를 실행@AfterThrowing
After Advice중심적 관심사의 '실행 후'에 횡단적 관심사를 실행
(정상 종료나 예외 종료 드의 결과와 상관없이 실행)
After
Around Advice중앙적 관심사 호출 전후에 횡단적 관심사를 실행@Around

6.3 AOP 공통 기능

스프링 프레임워크에서는 여러 가지 공통 기능을 AOP로 제공합니다.
가장 대표적으로, 트랜잭션을 관리하는 @Transactional 어노테이션은 클래스나 메서드에 부여하여 이용할 수 있습니다.

트랜잭션 관리에는 @Transactional 어노테이션을 통해 데이터베이스 엑세스 처리 메서드가 정상 종료하면 트랜잭션을 커밋하고 예외가 발생하면 롤백합니다. @Transactional 어노테이션에 관한 자세한 설명은 '11장 트랜잭션 관리 알아보기'에서 다루도록 합니다.

image-20231202140428946


6.4 AOP 사고방식

예를 들어, 프로그램 개발 중에 동작 상황을 확인하기 위해 여러 크래스에 System.out.println을 사용해 '디버깅 로그'를 출력한다고 가정해 보겠습니다. 각 클래스의 메서드에 System.out.println을 입력해야 하기 때문에 이 작업은 생각만 해도 엄청나게 피곤한 작업이 될 것입니다.

또한 프로그램 작성이 완료됐을 때는 모든 '디버깅 로그'를 삭제해야 합니다. 이런 다수의 클래스에 공통으로 필요한 처리를 '횡단적 관심사'라고 앞에서 설명했습니다.

만일 여러 클래스의 메서드에 System.out.println 문을 자동으로 넣어주는 기능이 있고, 또한 필요가 없어졌을 때는 모두 자동으로 삭제해주는 기능이 있으면 편리할 것입니다. 이 같은 생각이 'AOP 사고방식'입니다.


6.5 AOP 주요 사항

  1. AOP에서는 프로그램을 2개의 요소인 중심적 관심사와 횡단적 관심사로 구성되어 있다고 생각합니다.
  2. 중심적 관심사란 구현해야 할 기능을 나타내는 비즈니스 로직을 말합니다.
  3. 횡단적 관심사란 본질적인 기능은 아니지만 품질이나 유지보수 등의 관점에서 꼭 필요한 기능을 나타내는 프로그램을 말합니다.
  4. AOP에서는 횡단적 관심사를 분리함으로써 기존 코드를 수정하지 않아도 프로그램 중에 특정 기능(공통 처리)을 추가할 수 있습니다.
  5. 스프링 프레임워크는 다양한 공통 기능을 AOP에서 제공합니다.

7. 메타 어노테이션

커스텀 어노테이션을 생성하는 경우에도 어노테이션을 사용합니다. 커스텀 어노테이션에 정의를 추가하는 어노테이션을 '메타 어노테이션(Meta Annotation)'이라고 합니다. 커스텀 어노테이션을 만들 때만 사용하는 특수 어노테이션입니다.

다음과 같은 4종류의 메타 어노테이션을 소개합니다:

  1. @Target
    커스텀 어노테이션이 무엇을 대상으로 하고 있는지 선언하기 위해 사용합니다. 어노테이션을 클래스에 부여할지, 메서드에 부여할지, 변수에 부여할지 등을 결정합니다. 어노테이션을 부여할 장소는 상수(constant)로 지정합니다.

    ElementType 요소추가할 대상
    ElementType.ANNOTATION_TYPE어노테이션
    ElementType.CONSTRUCTOR생성자
    ElementType.FIELD필드
    ElementType.METHOD메서드
    ElementType.PACKAGE패키지
    ElementType.PARMETER인수
    ElementType.TYPE클래스, 인터페이스(어노테이션, enum 포함)
  2. @Retention
    컴파일할 때나 프로그램을 실행할 때 '어노테이션'의 정보를 보관 및 유지하는 유효 범위를 결정하기 위해 사용합니다.
    @Retention은 유효 범유ㅣ별로 세 개의 상수를 제공합니다.

    상수내용
    SOURCE소스가 유효 범위입니다. 컴파일할 때 어노테이션 정보가 삭제됩니다.
    CLASS클래스 파일은 유효하지만 JVM에는 읽어 들이지 않습니다.(기본값)
    RUNTIME실행 중일 때 JVM에서 참조할 수 있는 가장 넓은 유효 범위입니다.
  3. @Documented
    지정된 어노테이션을 Javadoc API 문서를 출력할 때 표시되게 합니다.

  4. @Inherited
    지정한 어노테이션을 부여한 클래스를 상속하면 하위 클래스도 그 어노테이션을 부여한 것으로 설정합니다.

profile
필기하고, 타이핑하고, 말하면서 읽고, 코딩하고, 눈으로 읽고 오감으로 공부하기

0개의 댓글