*본 게시글은 김영한님 스프링 핵심 원리 기본편을 보고 이해한 내용을 바탕으로 정리한 글입니다.
스프링 빈 ( Spring bean )
스프링 빈은 스프링 컨테이너에 의해 관리되는 자바 객체(POJO)를 의미한다.
스프링 컨테이너에서 싱글톤으로 관리된다.
아래와 같이 어노테이션을 통해 스프링 빈으로 등록할 수 있다.
@Bean
public SpringBean springBean(){
return new SpringBean();
}
스프링 빈이 싱글톤으로 관리되는 이유는, 서버에 수많은 커넥션이 오게 되고, 이때 마다 똑같은 역할을 하는 객체를 계속 새로 생성하게 된다면 엄청난 자원의 낭비가 된다.
이러한 자원의 낭비를 방지하고자 스프링 컨테이너에 사용할 객체들을 스프링 빈으로 등록하여, 하나만 생성해두고 생성된 하나의 객체만을 사용하는 것이다.
*싱글톤 - 생성자가 여러차례 호출되어도 실제로 생성되는 객체는 하나고 최초 생성 이후에 호출된 생성자는 최초에 생성한 객체를 리턴하여 객체의 중복생성을 방지한다. 싱글톤이라해서 무조건 Thread-safe하다고 생각하지 않아야한다. 만약 통장 잔고가 1000원 있다고 하여 1000원을 객체의 변수로 저장해버리면 1000원이라는 상태는 모든 고객이 공유하게되어 내가 원치 않은 결과를 초래한다. 싱글톤 객체들은 상태를 갖지 않도록 주의하자.
*POJO - POJO란, 객체 지향적인 원리에 충실하면서 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트이다.
스프링 컨테이너 ( Spring Container)
스프링 컨테이너는 스프링 빈의 생명 주기를 관리하며, 생성된 스프링 빈들에게 추가적인 기능을 제공하는 역할을 한다.
IoC와 DI의 원리가 스프링 컨테이너에 적용된다.
개발자는 new 연산자, 인터페이스 호출, 팩토리 호출 방식으로 객체를 생성하고 소멸하지만, 스프링 컨테이너를 사용하면 해당 역할을 대신해 준다.
즉, 제어 흐름을 외부에서 관리하게 된다.( Ioc 제어의 역전 )
또한, 객체들 간의 의존 관계를 스프링 컨테이너가 런타임 과정에서 알아서 만들어 준다.(DI 의존관계 주입)
*Ioc와 DI에 대한 더 자세한 내용은 아래 정리한 내용을 참고하도록 하자.
스프링 설정 파일은 java class, xml, Groovy 등으로 구현할 수 있기 때문에
스프링 컨테이너를 생성할 때도 각각 아래의 구현 클래스 3가지를 통해서 생성할 수 있다.
가장 최상위 부모는 BeanFactory로 인터페이스이다.
BeanFactory 인터페이스의 내부를 확인해보았을 때, getBean은 등록한 스프링 빈을 스프링 컨테이너로부터 가져올 수 있는 기능인데 이런 스프링 컨테이너가 제공해주는 기본 메서드를 BeanFactory 인터페이스에 등록한 것을 알 수 있다.
BeanFactory는 스프링 컨테이너의 최상위 인터페이스이고, 스프링 빈을 관리하고 조회하는 역할을 담당한다.
당연히 하위의 ApplicationContext는 BeanFactory의 모든 기능을 포함해 더 많은 기능을 갖고있고, 또 하위의 자세한 구현코드는 각 설정파일 별로 특화된 더 많은 기능들을 갖고 있다.
ApplicationContext는 BeanFactory를 비롯해 더 다양한 인터페이스를 상속하여 BeanFactory보다 더 다양한 기능을 제공한다.
- MeassgeSource : 메시지소스를 활용한 국제화 기능. 예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
- EnvironmentCapable : 환경변수. 로컬, 개발, 운영등을 구분해서 처리
- ApplocationEventPublisher : 애플리케이션 이벤트 이벤트를 발행하고 구독하는 모델을 편리하게 지원 편리한
- ResourcePatternResolver : 리소스 조회. 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
자바 클래스 AppConfig.class 설정 파일을 만들었다고 가정했을 때, 아래와 같이 스프링 컨테이너를 생성할 수 있다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
applicationContext.~로 스프링 컨테이너에서 등록된 빈을 조회하거나 여러가지 스프링 컨테이너의 기능들을 사용할 수 있다.
스프링 컨테이너 생성과 스프링 빈 등록 방식 ( 수동 vs 자동 )
1. 수동 등록
아래와 같이 @Configuration 어노테이션을 통해 스프링 빈을 수동으로 설정하는 설정 클래스를 만들 수 있다.
@Configuration
public class AppConfig {
@Bean
public SpringBean springBean(){
return new SpringBean();
}
}
스프링 컨테이너에 스프링 빈이 저장될 때는 빈의 이름과 생성된 빈의 객체가 테이블 형으로 저장된다.
위와 같이 빈을 등록할 때는 "springBean"이 이름이 되고 return하며 새로 만드는 객체인 SpringBean()이 객체로 저장된다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
SpringBean springBean = applicationContext.getBean("springBean",SpringBean.class);
위와 같이 AppConfig.class를 토대로 스프링 컨테이너를 만들고, 스프링 컨테이너에서 getBean("스프링 이름",객체.class)을 통해 생성한 빈을 통해 기능을 구현할 수 있다.
위와 같이 만들면 스프링 빈으로 SpringBean 객체 뿐만 아니라, AppConfig까지 같이 스프링 빈으로 만들어 등록한다.
getBean()시에 두 개의 매개변수 중 하나만 써도 무방하다.
객체.class만 사용할 시에는 해당 객체와 객체의 하위 클래스들로 등록된 빈들을 모두 찾는데,
이때 찾아지는 빈이 두개 이상이면 오류를 낸다.
getBeansOfType(객체.class) 메서드를 이용하여 본인 객체를 포함한 자식 클래스들의 빈들을 전부 뽑아올 수도 있다.
*@Configuration이 있고 없고의 차이
해당 어노테이션이 없어도 해당 클래스 내부의 빈들과 해당 클래스는 너무나도 잘 생성된다.
@Configuration이 있으면 설정 클래스에 스프링이 바이트 조작을 하여 내부 객체들을 싱글톤으로 유지하게끔 한다. 실제로 코드를 돌려서 확인해보면
이렇게 CGLIB이 붙은 우리가 직접 만든 AppConfig 객체가 아니라 어딘가 조작된 객체가 나온다.
CGLIB은 프록시 객체의 일종으로 AppConfig가 빈으로 등록될 때, AppConfig 대신 AppConfig를 상속 받은 AppConfig$CGLIB 형태로 프록시 객체가 등록된다.
이렇게 조작된 객체로 인해 내부의 메서드들이 return할 때 싱글톤 패턴을 유지할 수 있게된다.
AppConfig$CGLIB 은 AppConfig의 자식 클래스이기 때문에 getBean(AppConfig.class)로 조회할 수 있다.
@Configuration없이 빈을 등록하는 것을 Bean Lite Mode라고 하고, 이는 빈을 조회할 때 마다 새로운 객체를 반환한다.
2. 자동 등록 - Component Scan
@Configuration
@ComponentScan(
basePackages = "hello.core",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
설정 파일에 위와 같이 @ComponentScan을 넣어서 설정파일을 만들 수 있다.
basePackages를 지정해주지 않으면 보통 현재 위치하고 있는 패키지로 지정되고, 지정한 패키지 하위의 모든 클래스를 스캔한다.
내부에 @Bean 어노테이션으로 메서드들을 만들지 않아도 하위 패키지의 @Component가 붙은 클래스를 전부 스캔하여 스프링 빈으로 등록한다.
컴포넌트 스캔의 대상
@Component 외에 @Controller, @Service, @Repository, @Configuration는 내부 구현 코드를 보면 @Component의 상속을 받고 있으므로 모두 컴포넌트 스캔의 대상이다.
*스프링에서 보면 @어노테이션을 위에 써주어서 기능을 상속받는 식으로 되어있는데, 이는 자바에서 지원하는 기능이 아닌 스프링이 지원하는 기능이다.
- @Controller
- 스프링 MVC 컨트롤러로 인식된다.
- @Repository
- 스프링 데이터 접근 계층으로 인식하고 해당 계층에서 발생하는 예외는 모두 DataAccessException으로 변환한다.
- @Service
- 특별한 처리는 하지 않으나, 개발자들이 핵심 비즈니스 계층을 인식하는데 도움을 준다.
- @Configuration
- 스프링 설정 정보로 인식하고 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다. (물론 스프링 빈 스코프가 싱글톤이 아니라면 추가 처리를 하지 않음.)
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AutoAppConfig.class);
SpringBean springBean = applicationContext.getBean("springBean",SpringBean.class);
수동 등록과 마찬가지로 스프링컨테이너에서 스프링 빈을 꺼내어 사용할 수 있다.
기본적으로 맨처음 어플리케이션 실행 클래스에 있는 @SpringBootApplication 어노테이션도 @ComponentScan 기능을 갖고 있다.
즉, 최상위 패키지부터 컴포넌트 스캔이 들어가므로, 내부 클래스들에 @Component 어노테이션만 사용해주어도 스프링빈으로 자동 등록된다.
스프링 빈으로 등록될 때는 Pascal case로 작성된 클래스명을 Camel case로 바꾸어 스프링 빈 이름으로 사용한다.
자동 vs 수동
수동으로만 등록한다고 했을 때, 나중에 등록해야할 스프링 빈이 매우 많아지게 된다면 매우 복잡해지고 관리할 빈이 ㅁ낳아져서 설정 정보를 관리하는 것 자체가 부담이 된다.
반면에 자동 등록은 등록할 빈 클래스에 @Component (혹은 Service Repository 등등)을 붙여주면 되므로, 매우 간단하고 의존관계 자동 주입까지 사용한다면 OCP DIP도 지킬 수 있다.
보통 수동 빈 등록은 기술적인 문제나 모든 클래스에 공통적으로 들어가는 AOP 등을 처리할 때 주로 사용된다. 저런 기술 지원 빈은 그 수도 매우 적고 어플리케이션 전반에 광범위하게 영향을 미치기 때문에 가급적 수동 빈 등록을 사용해서 명확하게 드러내는 것이 유지보수 관점에서 좋다.
나머지 Controller, Service, Repository 등은 웬만하면 자동 등록을 사용하는 편이 좋다.
중복 등록과 충돌
컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까?
다음 두가지 상황이 있다.
1. 자동 빈 등록 vs 자동 빈 등록
2. 수동 빈 등록 vs 자동 빈 등록
1번의 경우 오류를 발생시킨다.
2번의 경우는 좀 더 구체적인 수동 빈 등록이 자동 빈 등록을 오버라이딩 해버린다.
웬만하면 둘 경우 다 피하는 것이 좋다
'서버 > 스프링' 카테고리의 다른 글
[Spring] 빈 생명주기 콜백 (0) | 2022.08.28 |
---|---|
[Spring] 의존관계 자동 주입 @Autowired (0) | 2022.08.27 |
[Spring] Ioc(제어의 역전)와 DI(의존성 주입)의 개념과 그 차이 (4) | 2022.08.05 |
[Spring] Spring @ResponseBody를 사용하여 API 생성하기 (0) | 2022.08.04 |
[Spring] Spring MVC를 활용하여 동적인 화면 만들기 (0) | 2022.08.03 |