[Section 2] Spring Framework의 핵심 개념

dohyoungK·2023년 6월 10일
0

Spring Framework의 핵심 개념

1) DI(Dependency Injection)

  • 스프링 컨테이너와 빈

    public class Main {
     	public static void main(String[] args) {
    			
    			// (1) 스프링 컨테이너 생성
    		    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfigurer.class);
    			
    			// (2) 빈 조회 
    		    ProductRepository productRepository = applicationContext.getBean("productRepository", ProductRepository.class);
    			  Menu menu = applicationContext.getBean("menu", Menu.class);
    		    Cart cart = applicationContext.getBean("cart", Cart.class);
    		    Order order = applicationContext.getBean("order", Order.class);
    
    			// (3) 의존성 주입
    		    OrderApp orderApp = new OrderApp(
                 productRepository,
                 menu,
                 cart,
                 order
    				        );
    	
    			// (4) 프로그램 실행 
       			orderApp.start();
     		}
         }

    (1) 스프링 컨테이너 생성

    • ApplicationContext 인터페이스를 일반적으로 스프링 컨테이너라 부른다. 정확하게는 ApplicationContext가 상속하고 있는 BeanFactory와 구분해 사용하지만 BeanFactory를 직접 사용하는 경우는 거의 없기 때문에 ApplicationContext를 스프링 컨테이너라 한다. 그리고 ApplicationContext는 BeanFactory의 빈을 관리하고 조회하는 기능뿐만 아니라 웹 애플리케이션을 개발하는 데 필요한 다양한 부가 기능을 함께 제공한다.

    • 다음으로 AnnotationConfigApplicationContext는 그 구현 객체로 AppConfigurer.class를 넘겨주고 스프링 컨테이너는 이렇게 넘겨받은 구성 정보를 가지고 호출되는 메서드의 이름을 기준으로 빈을 등록한다.

    (2) 빈 조회

    • 스프링 컨테이너가 관리하는 자바 객체를 스프링 빈이라 한다. 빈은 클래스의 등록 정보, getter/setter 메서드를 포함하며, 구성 정보(설정 메타 정보)를 통해 생성된다. 그리고 스프링 컨테이너가 빈을 생성하고 의존 관계를 연결해주면 컨테이너의 관리하에 있는 빈들을 getBean()을 통해 조회할 수 있다.

  • 스프링 컨테이너 = 싱글톤 컨테이너

    • 싱글톤 패턴

      : 클래스의 인스턴스가 단 한번만 생성되게 만들어 객체의 참조값을 공유할 수 있도록 하는 것

      public class AppConfigurer {
         private Cart cart = new Cart(productRepository(), menu());
         // 최초로 생성된 Cart 인스턴스를 private로 정의된 cart 변수에 할당해, 외부에서 추가적으로 인스턴스가 생성되는 것을 방지하고, cart() 메서드를 통해 항상 같은 인스턴스가 반환되도록 함
         public Cart cart() {
            return cart;
         }
         ...
      }
    • 스프링 컨테이너는 내부적으로 객체들을 싱글톤으로 관리하며 싱글톤 컨테이너 역할을 수행한다. 그리고 이렇게 싱글톤으로 객체를 생성 및 관리하는 기능을 싱글톤 레지스트리라 부른다.

  • 빈 생명주기와 범위

    • 스프링 컨테이너 생명 주기

      public class Main {
         public static void main(String[] args) {
            // 스프링 컨테이너는 크게 초기화와 종료라는 생명 주기를 가지고 있다.
         
            // (1) 컨테이너 초기화
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfigurer.class);
         
            // (2) 컨테이너 사용
            Cart cart = applicationContext.getBean("cart", Cart.class);
            ...
         
            // (3) 컨테이너 종료
            applicationContext.close();
         }
      }

      (1) 컨테이너 초기화

      : AnnotationConfigApplicationContext를 통해 객체를 생성함과 동시에 스프링 컨테이너를 초기화한다. 이때 스프링 컨테이너는 구성 정보를 기반으로 빈 객체를 생성, 의존 관계를 연결한다.

      (2) 컨테이너 사용

      : 초기화 작업이 완료된, 스프링 컨테이너의 관리하에 있는 빈 객체들을 getBean()을 통해 조회하여 사용할 수 있다.

      (3) 컨테이너 종료

      : 컨테이너 사용이 모두 끝나면 close()를 통해 종료시킬 수 있다.

    • 빈 객체 생명 주기

      : 스프링 컨테이너의 생명 주기와 비슷하게 (1) 빈 객체 생성 -> (2) 의존 관계 주입 -> (3) 초기화 -> (4) 소멸 의 생명 주기를 가진다.

  • 컴포넌트 스캔과 의존성 자동 주입

    • 컴포넌트 스캔

      : AppConfigurer 클래스와 같은 외부 구성 정보에 @Bean을 통해 수동으로 빈을 생성하고 의존 관계를 설정하는 번거로움을 해결하기 위해 @ComponentScan을 통해 자동으로 빈을 등록하고, @Component와 @Autowired를 통해 자동으로 의존 관계를 설정할 수 있다.

    • @Autowired

      : 의존 관계 자동 주입을 위한 @Autowired 애너테이션은 기본적으로 타입으로 빈을 조회한다. 그래서 해당 빈의 타입이 2개 이상인 경우 스프링은 어떤 구현 객체를 선택해야 할지 알 수 없어 오류가 발생한다.

      • 해결 방법

      1. @Autowired 필드명 매칭
        : 만약 2개 이상의 여러 빈이 존재한다면 필드명이나 매개변수명으로 빈을 매칭한다.
      2. @Qualifier 사용
        : 추가적인 구분자를 통해 의존 관계를 연결한다. @Qualifier이 붙여진 추가 구분자를 통해 매칭되는 빈이 있는지 탐색하고, 매칭되는 빈이 없다면 빈의 이름으로 조회를 진행한다.
      3. @Primary 사용
        : @Primary를 사용해 빈 객체간 우선순위를 설정한다. 보통 가장 많이 사용되는 인스턴스를 @Primary로 우선으로 해두고, 다른 인스턴스를 @Qualifier로 지정해 상황에 맞게 사용한다.

2) AOP(Aspect Oriented Programming)

AOP란, 애플리케이션 개발의 과정에서 여러 객체에 공통적으로 적용할 수 있는 공통 관심 사항과 핵심 로직에 관련된 핵심 관심 사항분리시키는 프로그래밍

  • AOP와 프록시 객체

    : Spring Framework가 제공하는 AOP방식은 런타임 시에 프록시 객체를 생성해 공통 관심 기능을 적용하는 방식

    public interface Gugudan {
       // 추상 메서드 정의
       void calculate(int level, int count);
    }
    
    public class GugudanProxy implements Gugudan {
       private Gugudan delegator;
       
       public GugudanProxy(Gugudan delegator) {
          this.delegator = delegator;
       }
       
       @Override
       public void calculate(int level, int count) {
          long start = System.nanoTime();
          delegator.calculate(2,1); // 핵심 기능
          long end = System.nanoTime();
          System.out.printf("클래스명: = %s\n", delegator.getClass().getSimpleName());
          System.out.printf("실행 시간 = %d ms\n", (end - start));
          System.out.println("-------------------------------");
       }
    }

    GugudanProxy 클래스가 핵심 기능 로직을 가지고 있는 것이 아니라, 생성자로 받은 delegator 객체에게 핵심 기능을 위임한다. 그리고 시간 측정같은 공통 기능을 GugudanProxy 클래스에서 실행한다.

  • AOP의 핵심 개념

    • AOP란 공통 관심 사항과 핵심 관심 사항을 분리시켜 코드 중복을 제거하고, 코드 재사용성을 높이는 프로그래밍 방법론

    • AOP는 핵심 기능에 공통 기능을 삽입하는 것으로, 핵심 관심 사항 코드의 변경없이 공통 기능 구현을 추가 또는 변경 가능

      개념설명
      어드바이스(Advice)공통 관심 사항과 적용 시점을 정의
      조인포인트(Joinpoint)어드바이스가 적용될 수 있는 위치
      포인트컷(Pointcut)조인포인트의 부분 집합으로 공통 기능이 적용될 대상을 선정하는 방법
      위빙(Weaving)어드바이스를 핵심 기능 코드에 적용하는 것

      어드바이스 종류설명
      @Before타깃 객체의 메서드 호출 전에 공통 기능 실행
      @After예외 발생 여부에 관계없이 타깃 객체의 메서드 실행 후 공통 기능 실행
      @AfterReturning타깃 객체의 메서드가 예외 없이 실행되어 값을 반환한 경우 공통 기능 실행
      @AfterThrowing타깃 객체의 메서드 실행 중 예외가 발생한 경우 공통 기능 실행
      @Around타깃 객체의 메서드 실행 전과 후 또는 예외 발생 시 공통 기능 실행

  • AOP 구현

    1. 공통 기능을 제공하는 Aspect 구현 클래스 만들기
      @Aspect
      public class GugudanAspect {
         @Pointcut("execution(public void cal*(..))")
         private void targetMethod() {}
         
         @Around("targetMethod()")
         public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
            long start = System.nanoTime();
            
            try {
               Object result = joinPoint.proceed();
               return result;
            } finally {
               long end = System.nanoTime();
               Signature signature = joinPoint.getSignature();
               System.out.printf("%s.%s 메서드 호출!\n", joinPoint.getTarget().getClass().getSimpleName(), signatuer.getName());
               System.out.printf("실행 시간: %d ns", end - start);
            }
         }
      }
    2. 공통 기능을 정의한 Aspect 클래스를 사용하기 위해 Config 클래스를 통해 빈으로 등록
      @Configuration
      @EnableAspectJAutoProxy
      public class GugudanConfig {
         @Bean
         public GugudanAspect gugudanAspect() {
            return new GugudanAspect();
         }
         
         @Bean
         public Gugudan gugudan() {
            return new GugudanByForLoop();
         }
      }

0개의 댓글