JAVA

IoC, DI, AOP 와 Spring

ohyujeong 2024. 5. 1. 14:33

의존성이란 무엇인가?

 

한 클래스가 다른 클래스의 메소드나 데이터를 사용하는 경우를 '의존한다.'라고 말한다.

 

코드레벨에서의 의존성

 

1. 생성자 의존성

  • 클래스가 다른 클래스의 인스턴스를 필요로 할 때, 생성자를 통해 직접 생성하는 경우
class Engine {
}

class Car {
    private Engine engine;

    public Car() {
        this.engine = new Engine(); // Engine 클래스에 대한 의존성
    }
}

 

!발생할 수 있는 단점

Car 객체 생성자에서 Engine 생성자를 new를 통해 직접 생성하고 있음

--> Car가 Engine에 직접적으로 의존하고 있고, 클래스 간에 너무 강하게 결합되어 있다.

--> Engine 클래스의 구현을 변경하거나, 다른 종류의 Engine을 사용할 때 Car 클래스 코드를 수정해야함

--> 결국 Car클래스 내부 코드를 Engine에 대한 변경이 있을 때마다 수정해야 하기 때문에 유연성이 낮아짐

 

2. 메소드 파라미터 의존성

  • 메소드 실행될 때, 필요한 객체를 파라미터로 전달 받음
class Printer {
    void print(Document document) { // Document 객체에 의존
        // 문서 출력 로직
    }
}

 

!발생할 수 있는 단점

  • 많은 파라미터를 요구할 수록, 직접 파라미터를 명시하기 때문에 호출이 복잡해짐
  • 동일한 의존성을 필요로 하는 메소드가 여러개 있을 때, 코드 중복이 많아질 수 있음

 

 

DI(Dependency Injection) - 의존성 주입

 

Why?

  • 클래스 간의 결합도를 낮춰서 객체의 독립성을 높이고, 코드의 유연성과 확장성을 높이기 위해서 사용

How?

  • 외부에서 의존성을 주입해서 클래스 간의 결합도를 낮춤 

interface Engine {
    void start();
}

class GasolineEngine implements Engine {
    public void start() {
        System.out.println("가솔린 엔진이 작동합니다.");
    }
}

class ElectricEngine implements Engine {
    public void start() {
        System.out.println("전기 엔진이 작동합니다.");
    }
}

class Car {
    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    void start() {
        engine.start();
    }
}

// 의존성 주입 예
public class Main {
    public static void main(String[] args) {
        Engine engine = new ElectricEngine(); // 여기서 사용할 엔진을 선택
        Car car = new Car(engine); // 선택한 엔진을 Car 객체에 주입
        car.start();
    }
}

 

  • Car 클래스가 직접적으로 Engine클래스에 영향을 받지 않고, Engine 인터페이스에 의존함
    • 이제 Car 내부에서 Engine 구현체를 생성하는 것이 아니라, 외부에서 생성된 Engine 구현체를 Car는 주입만 받아서 사용!
    • 실제 Engine 구현체를 무엇을 쓰는지에 대한 구체적인 것은 Car는 신경쓰지 않아도됨. Car 클래스 내부의 변경 없이 Engine의 종류만을 교체할 수 있다

IoC(Inversion of Control) - 제어의 역전

  • 프로그램의 실행 흐름을 개발자(사용자) 가 아니라, 외부 시스템, 프레임워크가 제함
    • 이 원칙을 구현하는 방법이 DI
      • 의존성 주입을 개발자가 직접 하는 게 아니라, 외부(프레임 워크 etc)에서 하게함으로써 통제권이 프레임워크로 넘어가는 것. 

 

Spring을 통한 IoC/DI

  • Bean
    • 스프링 IoC 컨테이너가 관리하는 객체
    • Service, Repository, Controller 등의 컴포넌트를 Bean으로 등록하여 사용
    • Bean들 사이의 의존성이 스프링에 의해 자동으로 관리됨

---> 이 Bean을 스프링 설정을 통해 정의함으로써, 스프링을 통한 IoC/ DI가 이뤄질 수 있음

// Engine.java
public interface Engine {
    void start();
}

// GasolineEngine.java
@Component  // 스프링의 관리 대상임을 선언
public class GasolineEngine implements Engine {
    @Override
    public void start() {
        System.out.println("가솔린 엔진이 시동됩니다.");
    }
}

// ElectricEngine.java
@Component  // 스프링의 관리 대상임을 선언
public class ElectricEngine implements Engine {
    @Override
    public void start() {
        System.out.println("전기 엔진이 시동됩니다.");
    }
}

// Car.java
@Component  // 스프링의 관리 대상임을 선언
public class Car {
    private Engine engine;

    @Autowired  // 스프링에게 Engine 타입의 객체를 주입하도록 지시
    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}

 

1. @Component

  • 스프링에게 해당 클래스들이 스프링 컨테이너에 의해 인스턴스화 되고 관리되어야 함을 알려줌
  • 해당 클래스의 객체를 스프링이 관리하는 Bean으로 생성하게 함

2. @Autowired

  • 객체를 생성할 때 필요한 의존성을 자동으로 주입하게 해줌
  • 예제 코드에서는 Engine 타입의 인스턴스가 필요하니까, 스프링이 자동으로 Engine 타입의 인스턴스 찾아서 주입해줌

AOP(Aspect-Oriented Programming) - 관점 지향 프로그래밍

  • 모듈에서 공통적 -> 즉, 중복적으로 나타나는 부분을 분리하기 위한 방법
    • 횡단 관심사 - 공통 기능
    • 핵심 관심사 - 주요 기능

장점

  •  재사용성 증가 : 공통 기능을 하나의 장소에 모듈화하기 때문에 여러 부분에서 재사용 가능
  •  유지보수성 향상 :핵심로직과 공통 기능을 분리함으로써, 핵심로직이 공통기능의 변경 등에 영향 받지 않도록 함
  1. Aspect
    • 횡단 관심사(공통 부분)을 모듈화한 것 ex) 로깅, 보안, 트랜잭션 등
    • @Aspect -> 클래스가 횡단관심사를 모듈화한 것임을 선언
  2. Advice
    • pointcut에 언제, 무엇을 적용할지 정의한 메서드
    • @Before, @After , @AfterReturning, @AfterThrowing, @Around
  3. Join Point
    • Aspect 적용이 가능한 지점
    • 스프링 프레임워크가 관리하는 빈의 모든 메서드가 조인 포인트임(메소드 실행 시)
  4. Pointcut
    • JoinPoint 중에서 어떤 것이 Advice의 대상이 될 지 결정 -> 즉 자르는 지점!
    • JoinPoint 중 특정 조건에 맞는 경우를 필터링 해서 Advice 적용 
    • Advice가 적용된 메소드(PointCut) 지점에서는 JoinPoint 지점이 실행되기 전에 메소드를 호출 ex) 보안 검
  5. Target
    • 어드바이스가 적용되는 대상 객체(클래스)
  6. Weaving
    • 어드바이스를 Target의 코드에 적용하는 과정, 컴파일 타임, 로드 타임, 런타임 등에서 수행될 수 있다.

 

ex)

모든 가능한 JoinPoint 중 특정 조건 만족하는 시점(Pointcut)에 Advice 적용하는예

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

@Aspect
@Component
public class LoggingAspect {
    // Pointcut 정의: AccountService의 deposit 메소드에만 적용
    @Pointcut("execution(* AccountService.deposit(..))")
    public void depositPointcut() {}

    // Before Advice: deposit 메소드 실행 전에 로깅 실행
    @Before("depositPointcut()")
    public void logBeforeDeposit() {
        System.out.println("LoggingAspect: About to deposit money");
    }
}

 

  • Join Point: AccountService 클래스의 deposit, withdraw, getBalance 가능한 모든 메소드 실행 시점
  • Pointcut: depositPointcut 포인트컷은 deposit 메소드의 실행만을 대상으로 하여 이러한 조인 포인트 중 deposit 메소드 실행 시점만을 선택
  • Advice: @Before 어드바이스는 deposit 메소드가 실행되기 전에 로깅 실행

 

Spring AOP

 

  • 개발자가 JoinPoint 정의 X -> PointCut을 정의함으로써 스프링 AOP 가 자동으로 처리한 JoinPoint들 중 어느 지점에 Advice를 적용할 것인기 결정

 

@Controller, @Service같은 어노테이션들..

컨트롤러- 웹 요청을 수행한다는 공통 관심

서비스 - 비즈니스 로직을 수행한다는 공통 관심...

이라는 측면에서 관심사를 효율적으로 분리해서 AOP가 효과적으로 적용될 수 있게함

 

ex) @Controller 계층에서 사용자의 인증 및 권한 검사하는 Aspect를 정의할 수 있음

@Aspect
@Component
public class SecurityAspect {

    @Pointcut("within(@org.springframework.stereotype.Controller *) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void controllerMethods() {}

    @Before("controllerMethods()")
    public void checkAuthorization(JoinPoint joinPoint) throws Throwable {
        // 여기서 인증 및 권한 검사 로직을 구현
        System.out.println("Checking authorization for accessing: " + joinPoint.getSignature().toShortString());
    }
}

 

-> 모든 컨트롤러 계층에서  사용자의 인증 및 권한 검사라는 공통적인 부분(횡단 관심사)가 AOP를 통해 모듈로 분리됨!

  • Join Point: 스프링 Bean의 모든 메소드 실행 시점
  • Pointcut: 모든 조인포인트 중 @Controller 어노테이션이 붙은 클래스내의 메소드 중 @RequestMapping 어노테이션이 붙은 메소드만을 대상으로 함
  • Advice: @Before 어드바이스는 포인트컷에 정의 메소드가 실행되기 전에 실행

ex2) AOP를 통해 트랜잭션을 관리할 수 있음

 

1. @Transactional 어노테이션으로 범위 지정

@Service
public class MyService {
    @Transactional
    public void someTransactionalMethod() {
        // 데이터베이스 변경 로직
    }
}

 

2. 트랜잭션 Advice 설정 파일 구성

포인트컷 표현식으로 어느 메서드에 트랜잭션을 적용할 지 정의하고, 트랜잭션 어드바이스와 연결

<aop:config>
    <aop:pointcut id="transactionalMethods" expression="execution(* *.transactional*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionalMethods"/>
</aop:config>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

 

'JAVA' 카테고리의 다른 글

[이펙티브 자바] 객체 생성과 파괴  (0) 2024.07.13
싱글톤 패턴과 프록시 패턴  (1) 2024.05.01
자바의 제네릭(Generic)  (0) 2024.04.08
자바의 신 인사이트 정리  (0) 2024.03.21