[스프링 IoC 컨테이너와 빈]_02 ApplicationContext와 다양한 빈 설정 방법

5 minute read

ApplicationContext와 다양한 빈 설정 방법

컨테이너

스프링 IoC 컨테이너의 역할
● 빈 인스턴스 생성
● 의존 관계 설정
● 빈 제공

AppcliationContext
● ClassPathXmlApplicationContext (XML)
● AnnotationConfigApplicationContext (Java)

빈 설정
● 빈 명세서 ● 빈에 대한 정의를 담고 있다.
○ 이름
○ 클래스
○ 스코프
○ 생성자 아규먼트 (constructor)
○ 프로퍼트 (setter)
○ ..

컴포넌트 스캔
● 설정 방법
○ XML 설정에서는 context:component-scan
○ 자바 설정에서 @ComponentScan
● 특정 패키지 이하의 모든 클래스 중에 @Component 애노테이션을 사용한 클래스를 빈으로 자동으로 등록 해 줌.


1. xml 파일의 bean 태그로 직접 빈 설정

고전적인 방식으로 현재 거의 쓰지 않는 방식이다.

  1. 프로젝트 생성

    [Spring Initializr] > [Dependencies] : web 선택

    Spring Boot 프로젝트로 생성해서 spring-boot-starter-web 의존성만 추가하면, 우리가 학습하는데 필요한 대부분의 의존성이 추가된다.

    이 3개가 우리가 주로 사용하는 모듈들이다.

    Maven: org.springframework:spring-beans:5.2.7.RELEASE
    Maven: org.springframework:spring-context:5.2.7.RELEASE
    Maven: org.springframework:spring-core:5.2.7.RELEASE
    
  2. BookService와 BookRepository 클래스 생성

  3. Application Context 를 이용해서 BookService와 BookRepository를 빈으로 등록

  4. main 클래스의 @SpringBootApplication 삭제, main 메서드 안의 run 도 삭제

  5. 고전적인 방식으로 스프링 빈 설정 파일을 만들어보자

    스프링 IoC 컨테이너는 빈 설정파일이 있어야 한다.

    [resources] > New : [XML Configuration File] > [Spring Config] > application.xml 파일 생성

  6. BookService와 BookRepository를 빈으로 직접 등록

    <bean id="bookService" class="dev.solar.springapplicationcontext.BookService"/>
    <bean id="bookRepository" class="dev.solar.springapplicationcontext.BookRepository"/>
    

    <bean> 태그로 빈을 생성

    id 네이밍 컨벤션 : 소문자로 시작하는 카멜케이스

    class : bean의 타입을 지정

    scope : 스코프를 지정

    * prototype : 매번 객체를 생성
    * request : request 당 객체를 생성
    * session : session 당 객체를 생성
    * singleton : 하나의 객체만 생성 (default)
    

    autowire : 모드를 지정

    * default : 기본값 (뒤에서 자세히 설명)
    * byType
    * byName
    * constructor
    * no
    

    지금은 빈을 등록만한 상태이기 때문에 BookService에서 BookRepository를 주입받지 못한다.

    따라서 빈설정 파일에서 주입시켜줘야 한다.

  7. BookService에 BookRepository를 주입

    <property> 태그 사용

    name : BookRepository의 setter에서 이름을 가져옴

    ref : 다른 빈을 참조하도록 해당 빈의 id를 값으로 설정

    <bean id="bookService" class="dev.solar.springapplicationcontext.BookService"/>
      <property name="bookRepository" ref="bookRepository" />
    </bean>
    
  8. 앞서 만든 빈을 사용하는 ApplicationContext를 만들어서 사용

    ClassPathXmlApplicationContext 로 ApplicationContext를 생성

    public class SpringapplicationcontextApplication {
       private static final Logger log = LoggerFactory.getLogger(SpringapplicationcontextApplication.class);
         
      public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
    		log.debug("생성된 빈의 이름 : {}", Arrays.toString(beanDefinitionNames));
      }
    }
    

    출력결과

    • 빈으로 등록되어있는 빈들의 이름을 확인

    bean등록

    getBean() 으로 빈을 가져올 수 있다. Object 타입으로 반환되므로 형변환 필요

    BookService bookService = (BookService) context.getBean("bookService");//Type cast
    

    null이 아니면 의존성 주입이 잘 된 것이므로 출력해서 확인해보자

    log.debug("의존성 주입이 되었는지 확인 : {}",bookService.bookRepository != null);//null이 아닌지 확인 -> true : 빈주입 성공
    

단점

  • 일일이 빈으로 등록하고 주입을 해줘야 한다.

    그래서 등장한 것이 context:component-scan이다.

2. xml 파일의 Context:component-scan 으로 빈 등록

  1. application.xml 파일에 context:component-scan 태그로 빈 스캔

    base-package : 스캐닝 대상이 될 패키지명 등록 (메인 패키지명 입력)

    <context:component-scan base-package="dev.solar.springapplicationcontext"/>
    

    application.xml 파일

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
        <context:component-scan base-package="dev.solar.springapplicationcontext"/>
    </beans>
       
    
  2. 어노테이션을 이용해서 빈 등록

    기본적으로 @Component 어노테이션을 사용해서 빈으로 등록할 수 있다.

    뿐만 아니라 @Component 어노테이션을 상속받은 @Service , @Repository 등의 어노테이션으로도 빈등록이 가능

    service어노테이션

    @Service
    public class BookService { ... }
    
    @Repository
    public class BookRepository {}
    

    이렇게만 하면 빈이 등록만 되고, 의존성 주입은 되지 않는다.

  3. 빈 주입

    @Autowired 또는 @Inject 어노테이션으로 빈을 주입받을 수 있다.

    @Inject 어노테이션은 별도의 의존성이 추가로 필요하므로 @Autowired 로 주입받자

    @Service
    public class BookService {
       
        @Autowired
        BookRepository bookRepository;
       
        public void setBookRepository(BookRepository bookRepository) {
            this.bookRepository = bookRepository;
        }
    }
    

    다시 실행해보면 빈이 생성되고, 주입도 성공한 것을 확인할 수 있다.

    빈생성 확인

application.xml 파일을 읽어들이긴 하지만 xml에 들어있는 component-scan 기능을 사용해서 설정한 패키지 하위에 존재하는 어노테이션들을 스캐닝하여 클래스들을 빈으로 생성해준다.

3. 자바 코드를 이용한 빈 설정

  1. 자바 설정 파일임을 알려주는 @Configuration 어노테이션을 사용하여 빈 설정파일을 생성

    메소드명 : bean의 id

    반환타입 : bean의 타입

  2. BookService에 직접 BookRepository 의존성을 주입할 수 있다.

    • setter을 이용해서 필요한 빈을 주입

    (1) 의존성 주입에 필요한 인스턴스는 메서드로 호출해서 가져와 넘김

    @Configuration
    public class ApplicationConfig {
       
      @Bean
      public BookRepository bookRepository() {
        return new BookRepository();
      }
       
      @Bean
      public BookService bookService() {
        BookService bookService = new BookService();
        bookService.setBookRepository(bookRepository()); //직접 의존성 주입
        return bookService;
      }
    }
    

    (2) 메서드 파라미터로 넘겨받아서 의존성 주입

    @Bean
    public BookService bookService(BookRepository bookRepository) {
      BookService bookService = new BookService();
      bookService.setBookRepository(bookRepository);
      return bookService;
    }
    

    (3) @Autowired 어노테이션으로 의존성 주입

    빈으로 등록만 하면 @Autowired 어노테이션 적용이 가능하다.

    직접 의존성 주입을 하던 코드 삭제

    // ApplicationConfig.java
    @Bean
    public BookService bookService(BookRepository bookRepository) {
      return new BookService();
    }
    

    @Autowired 어노테이션 추가

    public class BookService {
        @Autowired
        BookRepository bookRepository;
    }
    
    • 생성자를 통한 빈 주입
    @Configuration
    public class ApplicationConfig {
       
      @Bean
      public BookRepository bookRepository() {
        return new BookRepository();
      }
       
      @Bean
      public BookService bookService(BookRepository bookRepository) {
        return new BookService(bookRepository);
      }
    }
    
    public class BookService {
       
      BookRepository bookRepository;
       
      public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
      }
    }
    
  3. 앞서 만든 빈을 사용하는 ApplicationContext를 만들어서 사용

    AnnotationConfigApplicationContext 로 ApplicationContext를 생성

    지정한 클래스를 빈설정 파일로 사용한다.

    ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class);
    

    빈설정에서 @Bean으로 빈 정의를 읽어서 빈으로 등록하고 정의해준 대로 의존성 주입을 한다.

     public class SpringapplicationcontextApplication {
         private static final Logger log = LoggerFactory.getLogger(SpringapplicationcontextApplication.class);
        
         public static void main(String[] args) {
             ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class);
             String[] beanDefinitionNames = context.getBeanDefinitionNames();
             log.debug("생성된 빈의 이름 : {}", Arrays.toString(beanDefinitionNames));
             BookService bookService = (BookService) context.getBean("bookService");//Type cast
             log.debug("의존성 주입이 되었는지 확인 : {}",bookService.bookRepository != null);//null이 아닌지 확인 -> true : 빈주입 성공
         }
     }
    

    자바코드로 빈주입

단점

하지만 여전히 @Bean 어노테이션으로 일일이 지정하여 만드는 불편함이 있다.

xml에서 component scanning을 했던 것처럼 자바코드로도 스캐닝을 할 수 있다.

4. ComponentScan 어노테이션을 이용한 빈 설정

@ComponentScan(basePackageClasses = SpringapplicationcontextApplication.class)
  • basePackages = “path/of/package/class”

    문자열로 입력할 수 있다. (IDE가 좋으면 자동완성을 지원하긴 하지만 Type Safety1하지 않다.)

  • basePackageClasses = 클래스명

    좀 더 Type Safety한 방법

    이 어플리케이션(클래스)이 위치한 곳부터 Component Scanning을 진행한다.

    특정 어노테이션이 설정된 클래스들을 스캐닝해서 빈으로 알아서 등록해준다.

@Configuration
@ComponentScan(basePackageClasses = SpringapplicationcontextApplication.class)
public class ApplicationConfig {
}
@Repository
public class BookRepository {}
@Service
public class BookService {...}

이 방식이 스프링부트를 이용해 빈을 설정하는 방식에 가장 가까운 방법이다.

물론 스프링부트 프로젝트에서 ApplicationContext를 직접 만들어서 사용하고 있진 않다. 이것 또한 스프링이 알아서 생성해준다.

5. 스프링 부트에서의 빈 설정

@SpringBootApplication 을 붙여주면 스프링이 ApplicationContext를 알아서 생성해준다.

@SpringBootApplication
public class SpringapplicationcontextApplication {
  public static void main(String[] args) {
  }
}

@SpringBootApplication을 확인해보면 이미 @ComponentScan 어노테이션을 상속받고 있고, (@SpringBootConfiguration → ) @Configuration 이 붙어있는 것이다.

사실상 위 코드의 클래스가 빈 설정파일이 되는 것이다. (따라서 별도로 만들었던 ApplicationConfig.java 파일은 불필요하다.)

  1. 어떠한 오퍼레이션(또는 연산)도 정의되지 않은 결과를 내놓지 않는것, 즉, 예측불가능한 결과를 내지 않는것을 뜻한다.