티스토리 뷰
BeanFactory - 가장 기본적인 IoC 컨테이너
ApplicationContext - BeanFactory에 여러 기능을 추가한 하위 인터페이스
Configuration Metadata
1. 애너테이션 기반 설정
@Service, @Autowitred 같은 애너테이션이 설정 메타데이터 역할을 한다.
2. 자바 기반 설정
@Configuration, @Bean 같은 어노테이션을 사용하여 Bean을 정의한다.
Bean 재정의
1. @Component를 이용하여 Bean 등록
2. @Configuration 클래스 내부에서 @Bean으로 똑같은 Bean 등록
이 경우에서 Configuration 설정이 우선적으로 적용된다.
생성자 기반 DI, 세터 기반 DI
필수 종속성에는 생성자를 사용하고 선택적 종속성에는 세터 메서드를 사용하는 것이 좋다.
스프링에서는 생성자 주입을 권장한다. 변경 불가능한 객체로 구현할 수 있고 필요한 종속성이 보장되기 때문
Dependency Resolution Process
Bean의 생성 시점
- 사전 인스턴스화 + 싱글톤 : 컨테이너가 생성될 때 생성된다. (기본값)
- Lazy-Init 이거나 프로토타입 : Bean이 요청될 때 생성된다.
Bean이 요청될 때 생성되는 경우는 컨테이너 생성 시점에서 의존성 문제가 나타나지 않고 Bean이 실제 요청될 때 나타나게 된다.(순환 종속성 문제 등)
순환 종속성
순환 종속성을 푸는 권장 방법은 설계를 변경하는 것이지만, 한쪽 혹은 양쪽을 세터주입으로 변경하는 방식으로도 해결 가능하다.
depends-on
의존성 주입과 같이 직접 참조가 있는 경우 스프링이 자동으로 순서를 해결해준다. 하지만 코드로 표현되지 않는 간접적인 의존성인 경우 문제가 생길 수 있다.
- 어떤 클래스의 static 초기화 블록에서 DB 드라이버를 등록해야 하는 경우
- A와 B가 직접 사용하진 않지만, B가 먼저 초기화되어야 A가 정상 동작하는 경우
이런 경우 depends-on을 통해 순서를 지정할 수 있다.
Lazy-Initialized Beans
기본적으로는 싱글톤 + 사전 인스턴스화로 동작한다. 이 방식이 바람직한 이유는 구성 상의 오류를 컨테이너 구동 즉시 발견할 수 있기 때문이다.
지연 초기화가 다른 즉시 초기화 빈에 주입되어야 하는 경우 즉시 초기화하여 주입한다.
@Qualifier
같은 타입의 Bean이 두 개 이상 있으면 어떤걸 주입할 지 모호해져서 에러가 발생한다.
이때 @Qualifier로 특정 이름을 지정하여 주입이 가능하다.
@Component("creditCardPaymentService")
public class CreditCardPaymentService implements PaymentService { }
@Component("kakaoPayPaymentService")
public class KakaoPayPaymentService implements PaymentService { }
@Service
public class OrderService {
private final PaymentService paymentService;
@Autowired
public OrderService(@Qualifier("kakaoPayPaymentService") PaymentService paymentService) {
this.paymentService = paymentService; // 카카오페이 버전 주입
}
}
@Primary
같은 타입의 Bean이 여러개 있을 때 기본으로 선택될 Bean을 지정하는 애너테이션
@Component
@Primary
public class KakaoPayPaymentService implements PaymentService { }
@Component
public class CreditCardPaymentService implements PaymentService { }
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
// 자동으로 KakaoPayPaymentService가 주입됨 (Primary)
}
@Qualifier와 @Primary의 우선 순위
@Qualifier와 @Primary가 모두 있는 경우, @Qualifier가 우선순위를 가진다.
Method Injection
싱글톤 빈 A가 프로토타입 빈 B를 사용해야 하는 경우, 컨테이너는 싱글톤 빈 A를 한 번만 생성하므로 속성을 결정할 기회가 한 번 뿐이다. 빈 A에 빈 B의 새 인스턴스를 제공할 수 없게된다.
IoC를 포기하여 해결하는 방법이 있다. (비권장)
빈 A가 컨테이너를 인식하도록 ApplicationContextAware를 구현하고, 빈 A가 필요할 때마다 getBean("B") 요청
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// command를 받아온다.
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
하지만 비즈니스 코드가 Spring Framework를 인식하고 결합되는 방식을 권장하지는 않는다.
Lookup Method Injection
스프링이 특정 메서드를 가짜로 오버라이드해서 호출할 때마다 컨테이너에서 Bean을 새로 꺼내 반환하도록 바꿔준다.
동작 방식
- 스프링은 CGLIB을 사용해서 런타임에 해당 클래스의 서브클래스를 생성한다.
- 지정된 메서드를 오버라이드해서, 컨테이너에서 지정된 Bean을 getBean으로 가져오도록 바꿔준다.
public abstract class CommandManager {
public Object process() {
// 호출할 때마다 새로운 Command bean 사용
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
// 이 메서드는 스프링이 런타임에 오버라이드해서
// 컨테이너에서 prototype Bean을 반환하도록 바꿔줌
@Lookup
protected abstract Command createCommand();
}
Lookup Method를 만들기 위해서는 아래 시그니처를 만족하면 된다.
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
제약 조건
- 클래스가 final이면 불가능 (상속을 할 수 없기 때문)
- 오버라이드 할 메서드가 final이면 안된다.
- 추상 메서드로 선언하면 단위 테스트할 때 직접 구현체를 만들 수 있다.
- @Bean 메서드나 static factory method에서는 동작하지 않음, 인스턴스를 스프링이 서브클래싱해서 만들 수 없기 때문 (@Bean으로 등록한 메서드 AService() 가 있다고 하면 스프링은 해당 메서드의 응답 값을 그대로 받아 빈으로 등록할 뿐 그 과정에 개입하여 프록시로 바꿀 기회가 없다.)
Singleton Scope
하나의 인스턴스만 생성해서 사용한다. Spring의 기본 범위이다.
Portotype Scope
bean 요청이 있을 때마다 새롭게 생성한다. Prototype Bean은 상태를 저장하는 경우에 사용하고, Singleton Bean은 상태를 저장하지 않는 경우 사용해야 한다.
초기화 lifecycle 콜백 메서드는 호출 되지만, 소멸 lifecycle 콜백 메서드는 호출되지 않는다.
Lifecycle Callback
IntializingBean과 DisposableBean을 구현하여 빈 라이프사이클 관리와 상호작용할 수 있다.
@PostConstruct, @PreDestroy 애너테이션이 최신 Spring 애플리케이션에서 가장 적합한 방식으로 간주된다.
이런 애너테이션을 사용하면 Bean이 Spring 전용 인터페이스에 결합되지 않는다.
초기화 콜백
PostConstruct는 싱글톤 생성 Lock 안에서 실행된다. 따라서 다른 Bean을 접근하면 데드락 위험이 생길 수 있고, 무거운 작업을 하면 안된다.
라이프사이클 메커니즘 결합
아래와 같은 순서로 호출하고, 하나의 메서드에 여러 방식을 설정한 경우 해당 메서드는 한 번만 실행된다.
초기화 콜백
1. @PostConstruct
2. InitializingBean 인터페이스의 afterPropertiesSet()
3. 사용자 구성 init() 방법
소멸 콜백
1. @PreDestroy
2. DisposableBean 인터페이스의 destroy()
3. 사용자 구성 destroy() 방법
Startup and Shutdown Callbacks
Lifecycle 인터페이스는 자체적으로 수명주기를 제어해야 할 요구사항이 있는 경우 사용할 수 있다.
ApplicationContext로 부터 시작 및 중지 신호를 수신하면 start, stop 이 호출된다.
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
하지만 자동적으로 호출이 된다는 것을 의미하지는 않고 context.start()와 같이 신호를 전달해야 한다.
자동으로 제어하려면 SmartLifecycle을 이용할 수 있다.
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
autoStartup이 true이면 Context refresh 후 자동 실행된다.
Phase 기반 실행 순서를 제어할 수 있다. (phase가 낮으면 먼저 시작하고, 가장 마지막에 파괴된다.)
하지만 아래와 같은 경우에서 stop() 호출이 보장되지는 않는다.
정상 종료 시
1. 컨테이너가 stop() 호출
2. @PreDestroy 등 일반적인 Bean 소멸 콜백 실행
컨텍스트 Hot refresh 중간 실패 / 중단 시
1. stop()은 호출되지 않고, Bean 소멸 콜백만 실행될 수 있음
Hot refresh란?
refresh는 applicationContext의 생명 주기를 시작시키는 메인 루틴이다. 내부적으로 자동 호출되지만 context.refresh를 통해서 직접 호출하는 경우 Hot refresh라고 한다.
Self Injection
자기 자신을 주입받아 메소드를 호출하면 프록시를 거치므로 AOP 기능이 적용된다.
@Service
public class MyService {
@Autowired
private MyService self;
public void outer() {
self.inner(); // 실제론 프록시를 통해 호출됨
}
@Transactional
public void inner() {
// 트랜잭션 처리되는 메서드
}
}
- @Autowired는 기본적으로 다른 Bean을 먼저 찾는다.
- 그래도 없으면 자기 자신을 후보로 고려한다.
Self Injection은 최후의 수단으로 사용하고 아래와 같은 권고되는 방법이 있다.
- 메서드를 별도 Bean으로 분리
- @Resource(name="beanName")
- Autowired는 타입을 기반으로 찾지만, Resource는 Name을 기준으로 찾는다. 즉 주입 받을거라면 다른 bean이나 @Primary에 영향받지 않도록 beanName을 지정해서 주입 받으라는 뜻
주입 받을 수 있는 빈 유형
배열, 컬렉션 등을 주입받을 수 있다.
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
}
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
}
@Fallback
@Primary는 여러 빈이 후보일 때 우선적으로 사용할 빈을 지정하는 반면 @Fallback은 여러 빈이 후보이고 Primary가 없는 경우 선택된다.
@Value
외부화된 속성을 주입하는 데 사용한다.
SpEL 표현식을 이용할 수 있다.
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
Classpath Scanning
어떤 객체를 주입할까를 애너테이션으로 지정할 수 있었다. 하지만 어떤 객체를 Bean으로 등록할 지는 XML이든, java config에서든 정의해야 했다.
그래서 나온게 클래스 패스 스캐닝(컴포넌트 스캔) 이다.
@Component와 @Component의 특수화된 형태인 @Service, @Repository, @Controller 등을 제공한다.
@Bean 과 @Configuration
Configuration 안의 @Bean 메서드
@Configuration // proxyBeanMethods = true (기본값)
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA(serviceB()); // 내부 메서드 호출
}
@Bean
public ServiceB serviceB() {
return new ServiceB();
}
}
스프링은 @Configuration을 CGLIB 프록시로 감싼다.
serviceA에서 serviceB를 호출해도 실제로 new를 하는 것이 아니라, 컨테이너가 관리하는 싱글톤 Bean을 반환한다.
@Configuration이 없거나 있더라도 proxyBeanMethods = false인 경우에는 CGLIB 프록시로 감싸지 않는다.
@Configuration(proxyBeanMethods = false)
public class LiteConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA(serviceB()); // 여기서 호출하면?
}
@Bean
public ServiceB serviceB() {
return new ServiceB();
}
}
serviceA가 serviceB를 호출하면 매번 새로운 객체를 생성한다.
init, close, shutdown
Bean이 init, close, shutdown라는 메서드 명을 가진 다면 자동으로 초기화 혹은 소멸 콜백에 등록된다.
@RequestMapping
@RestController
public class MyController {
@GetMapping("/test")
public String test(){
return "test";
}
public void close(){
System.out.println("close 자동 호출 ! ");
}
}
- 출력 -
close 자동 호출 !
Process finished with exit code 130
이런 기능을 원치 않으면 @Bean(destroyMethod="")를 통해 비활성화할 수 있다.
또한 JNDI로 가져온 자원에 대해 스프링이 자동으로 destroy 콜백을 실행하지 않도록 설정해야 한다. (애플리케이션 외부에서 관리되는 자원을 닫아버리면 안된다.)
커스텀이벤트
이벤트 클래스는 ApplicationEvent를 상속하거나 POJO여도 된다.
public class BlockedListEvent {
private final String email;
public BlockedListEvent(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
}
이벤트 발행
@Autowired
private ApplicationEventPublisher publisher;
public void blockEmail(String email) {
publisher.publishEvent(new BlockedListEvent(email));
}
이벤트 수신
@EventListener
public void handleBlocked(BlockedListEvent event) {
System.out.println("Blocked email: " + event.getEmail());
}
내부 비지니스 로직을 느슨하게 연결할 수 있다는 장점이 있다. 동기 방식이며 비동기 방식은 다른 방법을 사용해야 한다.
- Total
- Today
- Yesterday
- 1918
- 1004
- 어린왕자 C++
- 타임머신
- 골목길C++
- 6539
- 골목길
- 소셜네트워킹어플리케이션
- 스택
- 6018
- C++
- 11657
- 벨만-포드
- tea time
- 상범빌딩
- 7511
- 중위 표기식 후위 표기식으로 변환
- 백준
- 1738
- 후위 표기식
- 벨만포드
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |