육감적 코딩

[Spring] 2. ApplicationContext와 다양한 빈 설정 방법 본문

정리/Spring

[Spring] 2. ApplicationContext와 다양한 빈 설정 방법

감감감감감감 2020. 7. 22. 21:42

정리 시작 전 이해를 돕기위한 사전설명

  • 프로젝트는 Spring Boot의 web starter만 dependency를 받아 사용.

  • BookService , BookRepository class를 만들어 사용하였습니다.

public class BookService {
    BookRepository bookRepository;

    public void setBookRepository(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }
}
public class BookRepository {
}



다양한 ApplicationContext 설정방법

  • ClassPathXmlApplicationContext

아주 고전적인 Spring Bean설정방법

일단 resources의 하위 경로에 “application.xml” 파일을 생성

 

<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">
 	
    <bean id="bookService" class="me.jsh.springapplicationcontext.BookService"/>
    <bean id="bookRepository"class="me.jsh.springapplicationcontext.BookRepository"/>
    
</beans>

<bean>의 하위 속성을 간단히 살펴보면

id : bean의 id를 의미합니다. 보편적으로 첫 글자는 소문자를 사용합니다.

class: bean의 타입을 의미합니다.

scope: 해당 예제에는 사용하지않았기 때문에 간략히 설명. 속성으로는

  • prototype : 매번 새롭게 만들어서 사용

  • request : 리퀘스트 마다 새롭게 만들어서 사용

  • session : 세션마다 만들어서 사용

  • singleton : application을 통틀어 한번만 생성 (속성을 지정해주지않으면 기본 값)

package me.jsh.springapplicationcontext;


import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.springframework.context.ApplicationContext;

import java.util.Arrays;


public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); // context 파일인 applicaition.xml를 읽어옵니다.

        String[] beanDefinitionNames = context.getBeanDefinitionNames(); // 등록된 Bean의 id를 가져옵니다. 

        System.out.println(Arrays.toString(beanDefinitionNames)); // Bean들의 id를 출력

        BookService bookService = (BookService) context.getBean("bookService"); //BookService는 BookRepository를 필드로 가지고있기때문에 제대로 생성되었는지 확인

        System.out.println(bookService.bookRepository != null); // bookRepository가 있는지 확인


    }

}

여기까지 작성을 한 뒤 실행.

application.xml의 빈들은 정상적으로 읽어와 출력이 되지만,

bookService의 bookRepository는 null 이기 때문에 false를 출력합니다.

why?

당연히 BookRepository가 빈으로 등록되었지만, 이를 BookService의 주입하지않았기때문입니다.

 

application.xml 를 수정해줍니다.

<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">
    
    <bean id="bookService" class="me.jsh.springapplicationcontext.BookService">
        <property name="bookRepository" ref="bookRepository"/>
    </bean>
    <bean id="bookRepository" class="me.jsh.springapplicationcontext.BookRepository"/>
    
</beans>

bean으로 생성된 BookRepository를 Bookservice에 DI 해줍니다.

당연히 다시 실행해 보면 true를 반환합니다.

 

 

 

 

 

 

application.xml를 보면 모든 빈을 직접 등록해줘야한다는 아주 큰 단점이있습니다.

그래서 등장한 방식인 context:component-scan 입니다.

<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="me.jsh.springapplicationcontext"/>
    
</beans>

= me.jsh.springapplicationcontext 패키지 부터 컴포넌트를 스캐닝 하여 빈을 등록하겠다.라는 뜻을 가지고있습니다.

 

기본적으로 @Component 어노테이션을 사용하여 빈으로 등록을 할 수 있습니다.

@Component

public class BookService {
    BookRepository bookRepository;

    public void setBookRepository(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }
}
@Component
public class BookRepository {
}

@Component가 붙은 class 들을 스캔하여 빈으로 등록합니다.

 

@Component를 확장한 몇가지 어노테이션이 있습니다.

@Service , @Repository 등등

@Service 애노테이션을 타고들어가면 @Component가 붙어있는것을 확인 할 수있습니다.

즉, 기능과 사용처에따라 이 확장된 어노테이션을 사용해도 Component-scan에 해당 클래스들이 걸리게됩니다.

@Service
public class BookService {
    @Autowired // 추후에 자세히 설명. Bean을 주입받아 사용한다 정도로 생각
    BookRepository bookRepository;
    
    public void setBookRepository(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }
}
@Repository
public class BookRepository {
}

 

실행을 하면 정상적으로 context파일을 읽어들여 빈이 생성되는것을 확인할 수 있습니다.

하지만 기존의 동작 방식과는 조금 차이가 있습니다.

해당 방법은 component들을 스캔하여 빈들을 생성합니다.

 

 

 

 


 

 

 

 

지금까지 ClassPathXmlApplicationContext 를 사용한 context작성을 알아보았습니다. 다음으로 알아볼 방법은

AnnotationConfigApplicationContext 를 사용한 context 작성을 알아보겠습니다.

 

  • AnnotationConfigApplicationContext 

xml파일말고 java로 만들수 없을까?

라고생각하여 등장한게 자바설정파일 입니다.

 

기존의 애노테이션들을 모두 제거한 뒤, 

ApplicationConfig 클래스를 만들어줍니다.

@Configuration 애노테이션을 사용합니다.

@Configuration
public class ApplicationConfig {
    @Bean
    public BookRepository bookRepository(){
        return new BookRepository();
    }

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

context를 읽어오는 방식이 바뀌었기 때문에 main을 수정해 줍니다.

public class DemoApplication {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        System.out.println(Arrays.toString(beanDefinitionNames));
        BookService bookService = (BookService) context.getBean("bookService");
        System.out.println(bookService.bookRepository != null);
    }
}

정상적으로 작동하는것을 확인할 수 있습니다.



xml파일을 만들필요도 없기때문에 편리하지만, 아직 이 방식에도 불편한 점이 있습니다.

가장 원시적인 xml로 빈등록을 했던것과 같이 모든 빈들을 직접 등록해주어야합니다.

 

 

 

 

 


하지만

사실 component-scan을 통한 방식이 존재합니다.

@ComponentScan 애노테이션을 이용하는 방식입니다.

 

-ApplicationConfig.class-

@Configuration
@ComponentScan(basePackageClasses = DemoApplication.class)
public class ApplicationConfig {
}

“DemoApplication”클래스의 패키지부터 스캔을 하여 @Component 애노테이션이붙은 (이를 확장한 @Service, @Repository, @Controller 등등) 클래스를 확인하여 빈으로 등록해 줍니다.

 

이로인해 기존의 ApplicationConfig의 빈을 일일히 등록하는 번거로움을 덜 수 있습니다.

 

 

 

 

 

 

 

 

 

하지만 이런 방식으로 ApplicationContext를 생성하여 사용하는 방법보다 더 간단한 방법이 있습니다.

SpringBoot에서 지원해주는 방식을 사용하는 것입니다.

 

바로 @SpringBootApplication 애노테이션을 이용하는 방식입니다.

@SpringBootApplication를 타고 올라가보면

@ComponentScan 애노테이션이 붙은 걸 확인할 수 있으며,

@Configuration 애노테이션이 붙은걸 확인할 수 있습니다.

즉, 기존의 ApplicationConfig.class 가 필요없습니다. (삭제해줍니다.)

 

사실상 @SpringBootApplication이 붙은 이 자체가 ApplicationContext역할을 합니다.
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
    }
}

 

 

 

 

지금까지 xml설정파일을 이용하여 빈을 등록하여 사용하는 방식과

애노테이션을 이용한 자바 설정파일을 이용하여 빈을 등록하여 사용하는 방식에 대해 정리해보았습니다.

또한 Springboot에서 지원해주는 @SpringBootApplication을 통해 빈을 등록하여 사용하는 방법에대해서도 정리해보았습니다.

 

Comments