스프링 프레임워크의 가장 큰 특징 중 하나는 제어의 역전(IOC)이다.
스프링은 IOC 컨테이너에서 빈(Bean)이라는 객체를 관리하는데, 스프링 프레임워크는 해당 빈(Bean) 객체가 다른 객체의 의존성으로 필요한 경우 자동으로 빈(Bean) 객체를 주입해준다.
따라서 개발자는 빈(Bean) 객체가 의존성으로 필요한 경우, new
키워드를 통해 새로 객체를 만들 필요 없이 스프링 IOC 컨테이너로부터 자동으로 주입받을 수 있다.
다만, 스프링 IOC 컨테이너가 어떤 객체를 빈(Bean)으로써 관리해야 하는지는 개발자가 직접 설정해 주어야 한다.
스프링에서 빈을 등록하는 방법으로는 아래의 2가지 방식이 존재한다.
어떤 클래스의 객체를 스프링의 빈으로 등록하는 가장 손쉬운 방법은 @Component
어노테이션을 클래스에 붙여주는 것이다.
@Component
public class PersonBean{
private String name;
private int age;
public String toString(){
return "name : " + this.name + "\nage : " + age;
}
}
이제 PersonBean 객체는 스프링 IOC 컨테이너에 빈으로서 등록되고, 이후 DI에 이용될 수 있게 된다.
@Component
외에 우리가 흔히 사용하는 @Controller
, @Service
, @Repository
어노테이션이 붙은 클래스 역시도 스프링 빈으로 등록되는데, 그 이유는 세 어노테이션이 정의된 부분을 보면 알 수 있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}
@Controller
어노테이션의 정의부를 보면 @Component
어노테이션이 붙어있다. 나머지 @Service
@Repository
역시도 마찬가지다.
세 어노테이션은 모두 @Component
어노테이션을 구체화 한 것이다.
그 때문에 세 어노테이션이 붙은 클래스 역시 스프링의 빈으로서 등록될 수 있는 것이다.
다만, 아무 클래스에 @Component
어노테이션을 붙인다고 전부 다 스프링 빈으로 등록되는 것이 아니다.
스프링 어플리케이션이 실행될 때, 스프링 프레임워크는 컴포넌트 스캔을 진행하면서 @Component
어노테이션이 붙은 클래스들을 확인하고 이들을 객체로 만들어 IOC 컨테이너에 빈으로 등록한다.
이 컴포넌트 스캔은 특정 패키지 하위로 존재하는 모든 클래스에 대해서 수행되는데, 이 범위에 포함되지 않는 클래스는 @Component
어노테이션이 붙어있더라도 스프링의 빈으로 등록되지 않는다.
@ComponentScan
어노테이션컴포넌트 스캔의 범위를 지정해 주는 것이 바로 @ComponentScan
어노테이션이다.
@ComponentScan
어노테이션이 제대로 동작하기 위해서는 아래 코드처럼 스프링 설정을 위한 클래스를 하나 만들고, @Configuration
어노테이션을 붙여서 해당 클래스가 스프링 설정 클래스임을 명시해 주어야 한다.
@Configuration
@ComponentScan(basePackages = "com.study.spring")
public class Configuration{
...
}
@ComponentScan(basePackages = "패키지 이름")
과 같이 컴포넌트 스캔을 시작할 패키지의 이름을 지정해 주면, 스프링은 해당 패키지와 그 하위 패키지에 존재하는 모든 클래스에 대해 컴포넌트 스캔을 진행하고 @Component
어노테이션이 붙은 클래스의 객체를 빈으로 등록한다.
@Configuration
@ComponentScan(basePackageClasses = MySpringApplication.class)
public class Configuration{
...
}
패키지 이름을 지정해 주는 대신, 위 코드처럼 클래스를 지정해 주는 것도 가능하다.
위 코드와 같이 클래스를 넘겨주게 되면 해당 클래스가 위치한 패키지를 컴포넌트 스캔의 시작점으로 잡게 된다.
하지만 아마 대부분의 스프링 어플리케이션에서 @ComponentScan
어노테이션을 직접 붙여놓은 모습은 찾기 힘들 것이다.
우리가 스프링 프로젝트를 생성했을 때 보통 프로젝트이름Application
라는 클래스가 생성되는데, 이 클래스의 윗 부분을 보면 @SpringBootApplication
어노테이션이 붙어있다.
@SpringBootApplication
어노테이션의 구현부를 보면 다음과 같이 되어있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
덕지덕지 붙어있는 어노테이션들 중, @ComponentScan
이 눈에 띈다.
즉, @SpringBootApplication
어노테이션을 붙이는 순간 자동으로 @ComponentScan
어노테이션이 적용되기 때문에 우리가 별도의 설정 없이도 바로 @Component
를 통해 빈을 등록할 수 있는 것이다.
두 번째 방법은 Configuration 클래스를 만들고, @Bean
어노테이션을 단 메서드를 통해 빈을 등록하는 방법이다.
어노테이션 하나만 붙여주면 됐었던 첫 번째 방법에 비해서 번거로워 보일 수 있는 방법인데, 두 번째 방법이 이용되는 경우는 다음과 같다.
- 외부 라이브러리에서 제공되는 객체를 빈으로 등록해야 하는 경우.
- 외부 라이브러리에서 제공되는 클래스는 개발자가 임의로 변경할 수 없어 따로
@Component
어노테이션을 붙일 수 없는 경우가 발생한다.
- 다형성을 활용해야 하는 경우
- 특정 인터페이스의 구현체를 빈으로 등록해야 하고, 해당 구현체가 차후 다른 구현체로 변경될 가능성이 높은 경우.
- 하나의 Configuration 클래스에서 어떤 구현체가 등록되었는지 한눈에 볼 수 있고, 수정할 수 있다.
빈으로 등록할 Car라는 클래스가 다음과 같이 있다고 하자.
public class Car{
private String name;
private String owner;
...
}
Car 클래스의 객체를 스프링의 빈으로 등록하기 위해 VehicleConfiguration 클래스를 작성한다.
@Configuration
public class VehicleConfiguration{
@Bean
public Car car(){
return new Car();
}
}
@Configuration
어노테이션이 붙은 클래스에 @Bean
어노테이션이 붙은 메서드를 작성했다.
이제 스프링이 @Bean
어노테이션이 붙은 메서드를 실행하고, 해당 메서드의 리턴값으로 나오는 각체를 스프링 IOC 컨테이너에 빈으로서 등록한다.
@Bean(name = "carBean")
과 같이 빈의 이름을 어노테이션에 직접 넘겨주어 빈의 이름을 따로 설정해 줄 수도 있다.
별도의 이름을 설정하지 않는 경우, 기본적으로 @Bean
어노테이션이 붙은 메서드의 이름이 빈의 이름으로 설정된다.