본문 바로가기
Back-end/Spring

[Spring] chapter03 스프링 프레임워크의 핵심 기능 알아보기

by na1-4an 2023. 7. 13.

아래 글은 스프링 프레임 워크 첫걸음 책을 기반하여 작성한 글입니다.

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

이 장에서는 chapter01에서 간단히 살펴본 DI와 AOP에 대해 자세히 다루겠다.

(1) 의존성 주입

DI(Dependency Injection)

: 의존하는 부분을 외부에서 주입하는 것이다.

 

(2) 관점 지향 프로그래밍

AOP(Aspect Oriented Programming)

: '횡단적 관심사'를 추출하고 이를 여러 곳에서 호출 할 수 있게 설정하여, 개발자가 '중심적 관심사'에만 집중해서 작성하는 구조이다.

여기서 횡단적 관심사, 중심적 관심사란?!

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

'AOP'를 회식에 비교하자면 '먹고 마시는 것'을 '중심적 관심사'라고하고 '회식비를 정산'하는 것을 '횡단적 관심사'라고 비유해보자. 누구나 가능하면 '먹고 마시는 것'만 하고 싶어할 것이다. 하지만 누군가는 정산하는 일을 해야하는데, 이 때 일을 편하게 해주는 역할을 하는 것이 AOP이다.


3-2 DI 컨테이너 알아보기

(1) 의존성

사용하는 객체 A 클래스, 사용되는 객체 B클래스가 있다.

만약 A에서 B 클래스의 인스턴스를 사용하려면 new 키워드로 인스턴스를 생성해 B의 메서드를 사용하면 된다.

만약 B 클래스에서 구현했던 메서드를 변경하면 그 영향으로 A클래스에서도 해당 메서드를 변경해야한다.

이러한 상황을 'A 클래스는 B 클래스에 의존한다'라고 한다.

의존에는 클래스 의존과 인터페이스 의존이 있다.

1️⃣ 클래스 의존(구현 의존)

위에서 설명한 상황과 같은 상황에서 설계가 바뀌어 새로운 C 클래스가 생겨 B가 아닌 C클래스의 메서드를 사용해야한다.

A에서는 B클래스의 인스턴스와 메서드 호출을 모두 지우고 C클래스에 대한 인스턴스를 새롭게 만들고, 메서드를 사용할 것이다.

이러한 경우 한두 군데라면 문제가 없겠지만 수정한 부분이 늘어나면 실수가 발생할 위험이 높아진다:(

2️⃣ 인터페이스 의존

인터페이스에 의존하는 인스턴스의 생성 및 메서드 호출

I 인터페이스가 있고 그것을 구현한 클래스 B가 있을 떄, 클래스 A에서 B의 메서드를 인터페이스를 사용하여 호출하는 상황이다.

인터페이스에 의존하는 '사용하는 객체' 클래스의 한 곳을 수정

새롭게 작성된 클래스 C를 호출해 methodX메서드를 호출하는 상황이다.

이런 식으로 인터페이스 의존을 사용하면 다음의 두 가지 장점이 있다.

  • 변수의 이름을 변경하지 않아도 된다.
  • 클래스가 바뀌어도 메서드명을 변경하지 않아도 된다.

 

(2)인터페이스에 의존하는 프로그램 만들기

// Call 클래스
package chapter03.use;
import chapter03.used.AddCalc;
import chapter03.used.Calculator;
public class Call {
    public static void main(String[] args){
        Calculator calculator = new AddCalc();
        Integer result = calculator.calc(10, 5);
        System.out.println("계산 결과는 (" + result + ")입니다.");
    }
}

위는 Call 클래스에서 Calculator 인터페이스를 사용하여 AddCalc의 메소드를 실행하는 코드이다. 해당 코드에서 SubCalc의 메소드를 실행하려면 아래와 같이 코드를 수정하면 된다.

//Call 클래스
package chapter03.use;
import chapter03.used.SubCalc;  //수정한 부분
import chapter03.used.Calculator;
public class Call {
    public static void main(String[] args){
        Calculator calculator = new SubCalc();  //수정한 부분
        Integer result = calculator.calc(10, 5);
        System.out.println("계산 결과는 (" + result + ")입니다.");
    }
}

 

(3) DI 컨테이너

: DI할 때 객체를 생성해서 주입하는 것을 도와주는 것, 인스턴스 생성과 같은 작업을 하는 것.

다음의 다섯 가지 규칙을 지켜, '사용하는 객체' 클래스를 전혀 수정할  필요가 없게끔 한다!

 

① 인터페이스를 이용하여 의존성을 만든다.

② 인스턴스를 명시적으로 생성하지 않는다.

→ new 키워드를 사용하지 않는다는 것을 의미한다.

③ 어노테이션을 클래스에 부여한다.

인스턴스를 생성하려는 클래스에 '인스턴스 생성 어노테이션'을 부여한다.

(어노테이션에 대해서는 3-3에서 자세히 다룰 것이다. 대충 어떤 표식정도로 생각하자.)

④ 스프링 프레임워크에서 인스턴스를 생성한다.

스프링 프레임 워크는 프로젝트의모든 패키지 안에 있는 클래스들을 스캔한다.(Component Scan 과정!)

    스캔 후 스프링 프레임워크는 '인스턴스 생성 어노테이션'이 부여된 클래스를 추출한다.

    추출 후 추출한 클래스의 인스턴스를 생성한다.

⑤ 인스턴스를 이용하고 싶은 곳에 어노테이션을 부여한다.

생성된 인스턴스를 이용하는 클래스에 참조를 받는 필드를 선언하고 필드에 @Autowired 어노테이션을 부여한다.

 

(4) DI 프로그램 만들기

//Greet 인터페이스
package com.example.demo.chapter03.used;

public interface Greet {
    void greeting();
}
//MorningGreet.java
package com.example.demo.chapter03.used;

import org.springframework.stereotype.Component;

@Component
public class MorningGreet implements Greet{
    @Override
    public void greeting(){
        System.out.println("---------------");
        System.out.println("Good morning~");
        System.out.println("---------------");
    }
}
//EveningGreet.java
package com.example.demo.chapter03.used;

public class EveningGreet implements Greet{
    @Override
    public void greeting() {
        System.out.println("---------------");
        System.out.println("Good evening~");
        System.out.println("---------------");
    }
}
//DemoApplication.java
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.example.demo.chapter03.used.Greet;

@SpringBootApplication	//스프링 부트 애플리케이션이라고 인식
public class DemoApplication {
	public static void main(String[] args) {
		// execute()를 호출하도록 작성
		SpringApplication.run(DemoApplication.class, args)
				.getBean(DemoApplication.class).execute();
	}

	// 주입하는 곳
	@Autowired
	Greet greet;

	// 실행 메서드
	private void execute(){
		greet.greeting();
	}

}

실행 결과

MoringGreet.java 파일에서 "@Componet" 어노테이션을 부여해주었기 때문에 "Good moring~"이 출력되는 것이다.


3-3 어노테이션 역할 알아보기

(1) 어노테이션

: (annotation) 외부 소프트웨어에 필요한 처리 내용을 전달한다.('@XXX' 형식)

소스파일 역할 외부 소프트웨어
@Override 오버라이드 메서드의 시그니처를 체크 자바 컴파일러
@Author 도움말 문서 생성 JavaDoc
@Component 인스턴스 생성 스프링 프레임 워크
@NotEmpty 입력란 체크 Validator
@Test 테스트 실행 JUnit

 

(2) <레이어 별로 사용할 인스턴스 생성 어노테이션>

- 도메인 주도 설계(DDD-Domain-Driven Design)의 레이어

1️⃣ Application Layer

     : 클라이언트와의 데이터 입출력을 제어.

      ➡️ @Controller: Application 레이어의 컨트롤러에 부여

2️⃣ DomainLayer

    : 애플리케이션의 중심 레이어. 업무처리를 수행.

      ➡️ @Service: Domain 레이어의 업무 처리에 부여

3️⃣ Infrastructure Layer

    : 데이터 베이스 관련 업무 수행.

      ➡️ @Repository: Infrastructure 레이어의 데이터 베이스 액세스 처리에 부여

 

+ 참고로 직접 커스텀 어노테이션을 만들 수도 있음.


3-4 AOP(관점지향 프로그래밍)의 기초 지식

(1) AOP 예제

AOP를 사용하여 '중심적 관심사'와 '횡단적 관심사'를 분리하여 프로그램을 만들 수 있다!!

 

🔍 AOP 고유 용어

- (1) Advice: 횡단적 관심사의 구현(→ 메서드)

    Advice의 종류 5가지!

      ① Before Advice    @Before

             : 중심적 관심사가 실행되기 '이전'에 횡단적 관심사를 실행

      ② After Returning Advice    @AfterReturning

             : 중심적 관심사가 '정상적으로 종료된 후'에 횡단적 관심사를 실행

      ③ After Throwing Advice    @AfterThrowing

             : 중심적 관심사로부터 '예외가 던져진 후'로 횡단적 관심사를 실행

      ④ After Advice    @After

             : 중심적 관심사의 '실행 후'에 횡단적 관심사를 실행(정상 종료나 예외 종료 상관 없이)

      ⑤ Around Advice    @Around

             : 중심적 관심사 호출 전 후에 횡단적 관심사를 실행

- (2) Aspect: advice를 정리한 것(→ 클래스)

- (3) JoinPoint: advice를 중심적 관심사에 적용한는 타이밍

- (4) Pointcut: advice를 삽입할 수 있는 위치

- (5) Interceptor: 처리의 제어를 인터셉트하기 위한 구조

- (6) Target: advice가 도입되는 대상

 

AOP의 구조

 

(2) 포인트컷 식

포인트컷은 앞서 언급한 것처럼 advice를 삽입할 수 있는 위치를 말한다.

삽입 위치를 지정하는 조건 방법에서 포인트컷 표현식을 사용한다.

포인트컷 표현식에는 여러가지가 있지만 본 절에서는 'execution'를 설명하겠다.

와일드 카드 내용
* (에스터리스크) 임의의 문자열이나 패키지 한 계층이나 메서드의 반환값을 나타냄.
. . (점 두 개) 패키지 메서드 인수를 나타냄
+ (플러스) 클래스명 뒤에 기술해 클래스와 그 서브클래스구현 클래스 모두를 나타냄
execution(반환형 패키지.클래스.메서드(인수))

 

(3) AOP 프로그램 만들기

(1) build.gradle 수정: AOP를 사용하기 위해 [build.gradle] 파일에 아래와 같이 추가한다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	implementation 'org.springframework.boot:spring-boot-starter-aop'	// AOP를 위해 추가한 줄
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

(2) AOP 클래스 생성

package com.example.demo.chapter03.aop;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SampleAspect {
}

(3) Before Advice 작성: 메서드 실행 전에 호출하는 Before Advice를 작성

    // com.example.demo.chapter03.used에서 클래스 이름이 Greet로 끝나는 클래스의 모든 메서드에 대해!!
    // 날짜와 메서드 이름을 표시하는 어드바이스
    @Before("execution(* com.example.demo.chapter03.used.*Greet.*(..))")
    public void beforeAdvice(JoinPoint joinPoint){
        //시작 부분 표시
        System.out.println("==== Before Advice ====");
        //날짜를 출력
        System.out.println(new SimpleDateFormat("yyyy/MM/dd").format(new java.util.Date()));
        //메서드 이름 출력
        System.out.println(String.format("Method: %s", joinPoint.getSignature().getName()));
        
    }

출력 결과

(4) After Advice 작성: 메서드를 실행한 후 호출되는 After Advice를 작성

    @After("execution(* com.example.demo.chapter03.used.*Greet.*(..))")
    public void afterAdvice(JoinPoint joinPoint){
        //시작 부분 표시
        System.out.println("==== After Advice ====");
        //날짜를 출력
        System.out.println(new SimpleDateFormat("yyyy/MM/dd").format(new java.util.Date()));
        //메서드 이름 출력
        System.out.println(String.format("Method: %s", joinPoint.getSignature().getName()));
    }

출력 결과

(5) Around Advice 작성: 메서드 실행 전후에 호출되는 Around Advice 작성

    @Around("execution(* com.example.demo.chapter03.used.*Greet.*(..))")
    public Object aroundAdivce(ProceedingJoinPoint joinPoint) throws Throwable{
        //시작 부분 표시
        System.out.println("==== Around Advice ====");
        System.out.println("▼▼▼▼ 처리 전 ▼▼▼▼");
        // 지정한 클래스의 메서드 실행
        Object result = joinPoint.proceed();
        System.out.println("▲▲▲▲ 처리 후 ▲▲▲▲");
        // 반환 값이 있는 경우 Object 타입의 반환값 반환.
        return result;
    }

출력 결과,, 아놔 한글 안먹히넹

 

(4) 스프링 프레임워크가 제공하는 AOP 기능

본 책에서는 @Transactional 어노테이션을 부여해 트랜젝션을 관리하는 기능을 소개한다.

더 자세한 내용은 '11-2 트랜잭션 관리 알아보기'에서 설명하도록 하겠다!

 


3-5 Spring Initializr 알아보기

https://start.spring.io/

(1) Spring Initializr의 이점

통합 개발 환경(IDE)에 의존하지 않고 프로젝트를 만들 수 있다.

(2) Spring Initializr 사용 방법

자신이 필요한 프로젝트를 구성하고 필요한 라이브러리를 ADD한다.

마지막엔 [GENERATE] 버튼을 클릭해 PC에 다운로드한다.

zip파일 압축을 풀고 InteliJ IDEA에서 파일을 열면 프로젝트가 임포트 된다.