스프링 프레임워크 IoC Container와 bean 설정

백기선님의 ‘스프링 프레임워크 핵심 기술’ 강의 정리

IoC container

IoC(Inversion of Control)

  • 의존 관계 주입(Dependency Injection)
  • 어떤 객체가 사용하는 의존 객체를 직접 만들어 사용하지 않고, 주입받아 사용하는 방법
  • 꼭 스프링이 없어도 그런 기능을 제공하는 장치가 있으면 가능

Spring IoC Container

  • BeanFactory: 스프링 빈 컨테이너의 핵심적인 기본 인터페이스
  • 애플리케이션 컴포넌트의 중앙 저장소
  • bean 설정 소스로부터 정의를 읽어들이고 구성하고 제공
  • 초기에는 xml 파일로 설정했지만, 현재는 어노테이션으로 설정하고 주입 받음
    • @Service, @Repository, @Controller, @Compnent, @Autowired

Bean

  • 스프링 IoC 컨테이너가 관리하는 객체
  • 어노테이션으로 등록된 객체만 스프링 빈
  • 의존성 주입이 필요한 객체는 빈으로 등록해야 함
  • 빈으로 등록된 객체만 IoC 컨테이너에 의해 의존성 주입을 받을 수 있음
  • 빈으로 등록되면 scope 설정 가능
    • singleton: 하나의 객체(메모리 효율적 사용, 런타임 시 성능 최적화 유리)
    • prototype: 매번 다른 객체
  • 빈으로 등록하는 어노테이션의 기본 설정값은 singleton
  • 하나의 객체만 필요한 경우에도 스프링 빈으로 등록
  • Lifecycle 인터페이스 지원
    • 빈에 대한 추가적인 작업 가능
    • @PostConstruct,@PreDestroy

ApplicationContext

  • BeanFactory를 상속받은 인터페이스
  • BeanFactory 기능을 가지면서 추가적인 기능 제공
  • 메세지 소스 처리 기능(i18n)
    • 메세지 다국화
  • 이벤트 발행 기능
  • 리소스 로딩 기능
    • 클래스 패스에 있는 특정 파일, 파일 패스에 있는 특정 파일 등을 읽어오는 기능

xml으로 spring bean 설정

스프링 앱을 실행할 수 있는 파일에서 @SpringBootApplication 어노테이션과 main 메소드에서 스프링 관련 코드를 제거한다. BookRepository, BookService 클래스를 만들고 이 두 객체를 xml에서 직접 스프링 빈으로 등록한다.

public class SpringCoreApplication {

	public static void main(String[] args) {
		
	}
}

// BookService
public class BookService {

	BookRepository bookRepository;

	public void setBookRepository(BookRepository bookRepository) {
		this.bookRepository = bookRepository;
	}
}
  • Xml 설정
    • <bean/> 으로 등록
    • id는 camel case
    • class는 클래스의 패스
    • scope는 기본 설정은 singleton이고 prototype, request, session도 가능
    • autowire는 기본 설정은 default이고 byName, byType, constructor, no도 가능
    • <property/> 로 의존성 주입
    • name은 BookService의 setter에서 가져옴
    • ref는 bean의 id 값
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bookService" class="com.example.springcore.BookService">
        <property name="bookRepository" ref="bookRepository"/>
    </bean>

    <bean id="bookRepository" class="com.example.springcore.BookRepository"/>

</beans>

Main 메소드에서 직접 스프링 빈을 가져와서 확인해보도록 한다. bookService.bookRepository가 null이 아니라는 것을 확인함으로서 의존성이 주입된 것을 알 수 있다.

import java.util.Arrays;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringCoreApplication {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        
		String[] beanDefinitionNames = context.getBeanDefinitionNames();        
		System.out.println(Arrays.toString(beanDefinitionNames));
        // [bookService, bookRepository]
        
		BookService bookService = (BookService)context.getBean("bookService");
		System.out.println(bookService.bookRepository != null);
        // true
	}

}

의존성 주입은 잘 되지만 너무나 번거로운 방법이다. 그래서 빈을 하나씩 설정하지 않고 특정 범위를 스캔해서 빈으로 주입하는 설정을 할 수 있다. <context:component-scan base-package=""/> 로 위의 <bean/>을 대체하면 base-package 안에서 어노테이션을 스캔해서 빈으로 등록하게 된다.

<?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
       http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.example.springcore"/>

</beans>

그리고 BookService, BookRepository에는 어노테이션을 추가해주도록 한다. 위의 설정으로 인해 @Component 어노테이션이 있으면 빈으로 등록하게 되는데 이 어노테이션을 확장한 @Service, @Repository, @Controller 등의 어노테이션을 적절하게 추가하도록 한다. 그리고 BookService에서는 BookRepository를 주입받을 수 있도록 @Autowired 어노테이션을 추가한다.

// BookRepository
@Repository
public class BookRepository {
}

// BookSerice
@Service
public class BookService {

	@Autowired
	BookRepository bookRepository;

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

Java 파일로 spring bean 설정

컴포넌트 스캔을 해도 xml을 작성하는 일은 매우 번거롭다. 자바 파일로 빈 설정을 해보도록 한다. 위에서 추가한 @Service, @Repository, @Autowired 어노테이션은 모두 지우고 자바 설정 파일에서 빈을 추가한다. @Configuration 어노테이션이 있어야 스프링이 자바 파일을 기반으로 설정하게 된다. xml과 비슷하게 여기서는 @Bean 어노테이션으로 스프링 빈을 등록한다. 의존성 주입 또한 setter 메서드를 이용해 쉽게 할 수 있다. BookRepository가 빈으로 등록되었기 때문에 BookService에서 @Autowired 어노테이션을 통해 의존성을 주입해도 된다.

// ApplicationConfig
@Configuration
public class ApplicationConfig {

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

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

Main 메소드에서 xml에서 설정을 읽어오게 되어 있는데 위의 파일을 읽어오도록 수정한 후 앱을 실행하면 동일하게 동작한다.

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringCoreApplication {

	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 작성보다는 덜 번거롭지만 그래도 @Bean으로 일일이 등록하는 것은 여전히 귀찮은 일이다. xml에서 컴포넌트 스캔을 했던 것처럼 자바 파일에서도 컴포넌트 스캔을 해보도록 하자. @ComponentScan 어노테이션을 추가해서 basePackages에 패키지 경로를 작성해도 좋지만 오타의 가능성이 있으므로 좀 더 편리하게 basePackageClasses를 사용해 해당 클래스가 있는 곳부터 컴포넌트 스캔을 하도록 한다. 이번에도 특정 어노테이션들을 스캔해서 빈으로 등록하게 된다. BookService, BookRepository에는 다시 어노테이션을 추가해주도록 한다.

// ApplicationConfig
@Configuration
@ComponentScan(basePackageClasses = SpringCoreApplication.class)
public class ApplicationConfig {

}

// BookService
@Service
public class BookService {

	@Autowired
	BookRepository bookRepository;

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

// BookRepository
@Repository
public class BookRepository {}

스프링 부트에서는 자바 파일로 설정하고 컴포넌트 스캔한 것도 @SpringBootApplication 어노테이션을 이용해 해결할 수 있다. @Configuration, @ComponentScan 어노테이션을 모두 포함하고 있기 때문이다. 그래서 이 어노테이션 하나만 있으면 위의 ApplicationConfig 파일은 필요없다.

@SpringBootApplication
public class SpringCoreApplication {

	public static void main(String[] args) {
        SpringApplication.run(SpringCoreApplication.class, args);
    }
}