본문 바로가기

백/spring boot

IoC/DI, AOP, PSA

IoC/DI

IoC (제어의 역전)

  • 정의

    객체의 생성과 관리 및 의존성 관리를 개발자가 아닌 스프링 프레임워크가 담당하는 디자인패턴
  • 효과

    객체 간의 결합도를 줄이고 코드를 유연하게 작성할 수 있어, 가독성을 높이고 코드 중복을 줄이며 유지 보수를 편하게 할 수 있게 해준다.
  • 기존에는 객체를 클래스 내부에서 생성하고 사용했다면, IoC를 적용하면 미리 생성해놓은 객체를 주입받아 사용하기만 하면 된다
  • Spring IoC 컨테이너의 생명주기

    Resource 로딩 -> Bean 설정 -> Bean 인스턴스화 -> 의존성 주입 -> Bean 초기화 콜백 -> Bean 사용 -> Bean 소멸 콜백 -> Resource 해제
  1. Resource 로딩설정 파일에 어떤 객체를 생성할지, 객체 간의 의존성을 어떻게 설정할지에 대한 정보가 포함되어 있어, 이를 기반으로 Bean 객체를 정의하게 된다.
  2. IoC 컨테이너가 사용할 설정 파일을 메모리에 로드한다.
  3. Bean 설정
  4. XML, Annotation에서 정의된 Bean 객체를 구성하는 메타데이터를 읽어들여 IoC 컨테이너가 사용할 수 있는 형태로 변환한다.
  5. Bean 인스턴스화
  6. Bean 설정 단계에서 변환한 정보를 기반으로 Bean 객체를 만든다.
  7. 의존성 주입
  8. IoC 컨테이너가 Bean 객체가 의존하는 다른 Bean 객체를 검색하고, 주입한다.
  9. Bean 초기화 콜백
  10. 스프링 컨테이너가 Bean을 생성한 후, 사용하기 전에 수행해야 할 추가 작업을 정의한 메서드를 호출한다.
  11. Bean 사용
  12. IoC 컨테이너가 빈을 사용한다.
  13. Bean 소멸 콜백
  14. 빈이 더 이상 필요하지 않을 때, IoC 컨테이너는 빈의 소멸 메소드를 호출하고 해당 빈을 제거한다.
  15. Resource 해제
  16. Ioc 컨테이너가 종료되어 빈들도 함께 소멸된다.

DI

  • 정의

    의존성을 외부에서 결정하고 주입시켜준다. IoC를 구현한 방법 중 하나이다.

<의존성을 외부에서 주입 받지 않은 경우>

public class Car {

	Tire tire;

	public Car() {
		tire = new KoreaTire();
		// tire = new AmericaTire();
	}
	
}
 

문제점 : Car 클래스와 Tire 클래스의 결합도가 강해서 코드의 확장성이 떨어진다.

모든 Car가 KoreaTire만 사용 가능, tire를 바꾸고 싶으면 Car 클래스의 생성자를 직접 수정해야 한다. 이를 해결하기 위해 의존성 주입을 사용한다.

  • 의존성 주입 방법 3가지
    public class Car {
    
        Tire tire;
    
        public Car(Tire tire) {
            this.tire = tire;
        }
    
    }
    
     
    Tire tire = new KoreaTire();
    
    **Car car = new Car(tire); // 생성자 주입**
    
     
    → 생성자를 통해 의존관계를 주입받는 방법2. Setter 주입
  • → 가장 권장되는 방법! 주입받는 객체를 final로 선언할 수 있어, 해당 객체가 변경되지 않도록 할 수 있다.
  • 1. 생성자 주입
    public class Car {
    
    	Tire tire;
    
    	public void setTire(Tire tire) {
    		this.tire = tire
    	}
    
    }
 
    Tire tire = new KoreaTire();
    
    Car car = new Car();
    
    car.setTire(tire); // setter 주입
 

→ setter 에서 tire 를 받아서 tire 필드를 설정

3. 필드 주입

    import org.springframework.beans.factory.annotation.Autowired;
    
    public class Car {
    
    	@Autowired
    	@Qualifier("koreaTire")
    	Tire tire;
    
    }
 
    import org.springframework.stereotype.Component;
    
    @Component
    @Qualifier("koreaTire")
    public class KoreaTire implements Tire {
        // ...
    }
 
    import org.springframework.stereotype.Component;
    
    @Component
    @Qualifier("americaTire")
    public class AmericaTire implements Tire {
        // ...
    }
 

→ Tire 구현체가 여러 개라면 어떤 빈을 주입받아야 하는지 알 수 없는 문제가 발생할 수 있는데, 이때 Qualifier를 사용해 빈을 지정해줄 수 있다


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

여러 모듈에 공통적으로 나타나는 로직에 해당하는 ‘횡단 관심사’(공통 관심 사항)을 각 모듈 별 핵심 로직에 해당하는 ‘핵심 관심 사항’에서 분리해 별도의 모듈로 관리하도록 하고, 이 횡단 관심사를 어디에 적용할지 선택하는 기능을 합한 하나의 모듈

간단히 말하면, 핵심 로직 사이에 부가 로직을 끼워넣는 작업이다.

  • 횡단 관심사를 Aspect라는 단위로 분리하고, 애플리케이션의 특정 지점(JoinPoint)에 적용하여 소스 코드의 중복을 줄인다
    • 주요 용어
      • Aspect : Advice + Pointcut으로, AoP에서 횡단관심사를 aspect로 모듈화한다.
      • 언제 어디서 무엇을 할 지 명시되어 있다
      • Advice : Target에 제공할 부가 작업을 담고 있는 모듈 (Aspect가 해야 하는 “작업”)
      • Target : Advice가 적용될 객체
      • Join Point : Advice를 적용할 수 있는 지점들 (method진입시점, 생성자 호출 시점 등 target 객체가 구현한 인터페이스의 모든 메서드)
      • Pointcut : JoinPoint 중 Advice가 적용되는 지점을 결정
  • 스프링이 제공하는 AoP 방법

    step 1) @Asepct 어노테이션을 붙여 Aspect를 나타내는 클래스임을 명시 + @Component 어노테이션으로 빈으로 등록하기step 3) Advice 설정
  • step 2) Pointcut 설정 (”execution(* 패키지명.interface명.메소드명)”)

ex) com.exmaple.demo 패키지 하위에 있는 메소드의 실행시간 체크하기

@Aspect
@Component
public class TimeTraceAop {

    @PointCut("execution(* com.example.demo..*(..)")  //com.exmaple.demo 패키지 하위에 있는 것에 다 적용해라
    private void Time(){}
    
    @Around("Time()") 
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{ //ProceedingJoinPoint : Advice가 적용되는 대상을 가리킨다
        long start=System.currentTimeMillis();
        System.out.println("Start : "+joinPoint.toString());
    
        Object result=joinPoint.proceed();  //메소드 실행
      
        long finish=System.currentTimeMillis();
        long timeMs=finish-start;
        System.out.println("Finish :"+joinPoint.toString() + timeMs + "ms" );
        }

    }
}
 
  • 메서드에 “언제” 로직을 주입하느냐에 따른 AoP annotation

    @Before : 대상 메서드가 실행되기 전에 Advice를 실행@Around : 대상 메서드 실행 전, 후 또는 예외 발생 시에 Advice를 실행@AfterThrowing : 대상 메서드에 예외가 발생했을 때 Advice를 실행
  • @AfterReturning : 대상 메서드가 “정상적으로 실행”되고 난 뒤 Advice를 실행 (exception 발생했을 때는 수행하지 않음)
  • @After : 대상 메소드 실행 후 Advice를 실행 (성공적으로 실행되었든, exception을 던졌든 상관없이 실행된다)

PSA

  • 다양한 기술들을 인터페이스를 이용해 추상화하여 개발자가 일관된 방식으로 코드를 작성할 수 있도록 하는 추상화 구조

OracleJdbcConnector, MariaDBJdbcConnector, SQLiteJdbcConnector와 같은 구현체에 직접적으로 연결해서 얻는 것이 아니라 JdbcConnector 인터페이스를 통해 간접적으로 연결되어 Connection 객체를 얻기 때문에 일관된 방식으로 기능을 사용할 수 있다.