[Spring Boot] Java Bean vs Spring Bean
Spring 공부를 하다보면 Bean이라는 개념이 자주 등장한다.
자주 사용하는 용어이기 때문에 당연히 알고 있는 개념이라고 생각하지만...
막상 의미를 정의하라고 하면 헷갈리는 경우가 많다.
허허 Bean의 개념을 명확하게 정리해보도록 하자!
1. Java Bean
결론부터 말하자면 Java Bean은 특정 형태의 클래스를 가르키는 뜻으로 사용된다.
DTO 혹은 VO의 형태가 Java Bean이라고 생각하면 쉽다.
필드는 private으로 구성되어 getter과 setter를 통해서만 접근할 수 있고, 전달 인자가 없는 생성자를 가지는 형태의 클래스이다.
getter / setter
public의 no-argument 생성자
모든 필드는 private으로 getter와 setter를 통해서만 접근 가능
getter와 setter, 생성자를 가지는 클래스를 가리키는 뜻으로 사용되는 만큼 POJO(Plain Old Java Object)와 거의 동일한 개념이라고 이해하면 될 것 같다.
코드로 보자.
public class AboutJavaBean {
// 필드는 private로 선언
private String bean;
private int beanValue;
// 전달 인자가 없는(no-argument) 생성자
public AboutJavaBean() {
}
// getter
public String getBean() {
return beanName;
}
// setter
public void setBean(String bean) {
this.bean = bean;
}
public int getBeanValue() {
return beanValue;
}
public void setBeanValue(int beanValue) {
this.beanValue = beanValue;
}
}
2. Spring Bean
Spring에서 Bean은 스프링 Ioc 컨테이너가 관리하는 Java 객체를 뜻한다.
일반 Java 객체와 다른 점은 없다. 그냥 스프링 Ioc 컨테이너에서 관리되는 객체를 Bean이라고 부르는 것이다.
스프링 Ioc가 관리하는 객체는 스프링에 의해 생성되고, 라이프 사이클을 수행하고, 의존성 주입이 일어나는 객체들을 말한다.
즉, 개발자가 관리하는 객체가 아닌 스프링에게 제어권을 넘긴 객체를 스프링에서 Bean이라고 부른다.
그러면 객체? 객체가 Bean 이라면 아래 코드도 Bean인가?
BeanTest bean = new BeanTest();
아니다. 이는 그냥 객체이지 Bean은 아니다.
아니 Bean이 객체라며? 그런데 이건 왜 아니야?
정확히 말하면 IoC 컨테이너가 관리하는 객체이다.
아니 이게 도대체 뭔 소리인가 싶다.
위 코드가 객체인데 Bean이 아니다. IoC 컨테이너에서 관리를 하지 않기 때문이다.
그럼 이 코드는 어떨까?
ApplicationContext applicationContext;
public void Bean(){
BeanTest bean = applicationContext.getBean(BeanTest.class);
}
여기에 선언된 bean은 Bean이 맞는가? 맞다. applicationContext에서 관리를 하기 때문이다.
applicationContext는 Bean Factory를 상속한 스프링 컨테이너의 종류이다.
스프링 컨테이너에서 관리한다, IoC 컨테이너에서 관리한다. 이게 다 뭔소리인가아아아ㅏ
처음엔 두개가 다른거라고 생각했지만, 공부를 하다보니 이 두개가 같은거라고 할 수 있다고 한다.
왜 같은지는 IoC/DI 관련 글을 통해서 이야기 해보도록 하겠다.
Bean 생성 방법
Component Scanning 방법과 혹은 직접 일일히 XML 이나 자바 설정파일에 등록하는 방법이다.
Component Scanning
이를 이해하기 위해 조금 깊게 알아보도록 하자.
우리가 흔히 @Controller 혹은 @RestController 를 사용하는데, 이들은 Component라는 어노테이션으로 묶여있다.
이게 무슨소리인가 감이 잡히지 않을것이다.
@RestController
public void getBeans(){}
이와같은 RestController가 있다고 하자.
여기서 RestController는 어떻게 생겼을까?
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(annotation = Controller.class)
String value() default "";
}
이런식으로 생겼다. 위에 여러 어노테이션들이 있는데 필자가 위에 언급했듯 Controller 는 Component로 묶여있다고 했다.
그럼 Controller안으로 들어가보도록 하겠다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}
Controller는 이렇게 생겨먹은놈인데 보면 Component 어노테이션을 사용하고있다.
우선 Component Scanning을 알아보기 위함이니 우선 여기까지만 알아보고,
Controller어노테이션과 RestController어노테이션은 Component로 묶여있다고 생각을 하면 편하다.
그럼 어디서 Scanning을 하는것일까?
우리가 SpringBoot 애플리케이션을 제작하면 src/main/java 안에 [프로젝트명]Application.java 라는 파일이 하나 있을것이다.
이 안을 한번 봐보자.
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
이런식으로 작성이 되어있는데...
SpringBootApplication이라는 어노테이션을 통해 Spring Boot의 시작점이라는것을 알려준다.
이 어노테이션 안에는 Component Scanning을 하는 로직이 들어있다.
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication { ... }
좀 생략된 부분이 많은데, SpringBootApplication 어노테이션이 어떻게 생겨먹은건지 보면 ComponentScan 이 있다.
그렇다, 바로 이 어노테이션 안에서 Component Scanning을 하는 기능이 들어있는것이다.
안에 들어가보면 다양한 로직들이 있는데 거기까지 알아보기에도 머리가 터질 것 같으니...stop하고
ComponentScan 어노테이션은 “ Component를 찾아봐!” 라고 말하는 어노테이션이라고 할 수 있다.
어디서부터 찾아야 하나?
SpringBootApplication 어노테이션이 붙은곳부터 모든 하위 패키지를 둘러보며 Component를 사용하는 어노테이션
즉, 앞서 말한 RestController 혹은 Controller 등이 있는데 꼭 이 두개만 있는것이 아니라, Repository, Service, Configuration 등 아주 많은것들이 있다. 그 중 대표적인것들이 필자가 지금 언급한것들이다. 이들을 전부 찾아 Bean으로 등록을 해준다.
Java 설정
XML 혹은 Java에서 설정을 해주는 방법인데, Java에서 설정하는 방법을 알아보도록 하겠다.
public class TestConfig{}
위와같은 클래스가 있다고 가정을 해보자.
위 클래스에다가 Bean을 등록하는 설정 코드를 작성을 하도록 하겠다.
우선, Configuration 어노테이션을 작성하고, class안에 Bean을 등록해준다.
@Configuration
public class TestConfig{
@Bean
public TestController testController(){
return new TestController();
}
}
이와 같은 형식으로 작성을 해주게 된다면 TestController는 Bean객체가 된다.
아니 그러면 RestController나 Conroller, Component 이런거 안붙혀도 Bean객체가 되는건가?
맞다..
public class TestController{}
이런식으로 적어줘도 해당 TestController는 Bean 객체가 된 것이다.
Bean 객체 불러오기
ApplicationContext만 이용한 방법
위에 처음 예시를 들어 설명한 방법이 ApplicationContext를 이용해서 Bean인지 아닌지 예시를 들어 설명을 했는데,
이를 이용해서 테스트코드를 작성해보도록 하겠다.
@SpringBootTest
public class TestConrollerTest{
private final ApplicationContext applicationContext;
public TestConrollerTest(ApplicationContext applicationContext){
this.applicationContext = applicationContext;
}
@Test
public void TestDI(){
TestConroller bean = applicationCotnext.getBean(TestConroller);
assertThat(bean).isNotNull;
}
}
이와 같은 형태로 Bean을 불러올 수 있다.
그런데 이렇게 하면 생성자를 불러오다보니 코드가 좀 길어지는 기분이다.
이때 Autowired를 이용해서 불러올 수 있다.
Autowired + ApplicationContext
@SpringBootTest
public class TestConrollerTest{
@Autowired
ApplicationContext applicationContext;
@Test
public void TestDI(){
TestConroller bean = applicationCotnext.getBean(TestConroller);
assertThat(bean).isNotNull;
}
}
Autowired 어노테이션을 사용하여 생성자 없이도 사용할 수 있게 된다.
3. Java Bean vs Spring Bean
1. 스프링 빈은 스프링 IoC 컨테이너에 의해 관리되지만 자바 빈은 그렇지 않다.
2. 자바 빈은 반드시 직렬화를 지원해야 하지만, 스프링 빈은 반드시 지원하지 않아도 된다.
3. 스프링 빈은 디폴트 생성자가 없어도 된다.
4. 자바 객체는 동시에 자바 빈 이며 POJO이고, 스프링 빈이 될 수 있다.