어떤 프레임워크이든 간에 그 프레임워카의 진행 흐름이나 기초 구성을 아는 것이 중요하다. 스프링 또한 많은 기능들을 제공하지만 핵심적인 구조에 대해서 알 필요가 있다. 이번 기회에 스프링에 대해 제대로 파악하고자 인프런에서 김영한 님의 스프링 기초 수강을 들었다. 오늘은 그 내용을 정리한 포스팅을 연재하려고 한다.
new AnnotationConfigApplicationContext(AppConfig.class)
AnnotationConfigApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
//System.out.println("name = " + name);
//Object bean = ac.getBean(name);
//System.out.println("bean = " + bean);
BeanDefinition beanDefinition = ac.getBeanDefinition(name);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
System.out.println("name = " + name);
Object bean2 = ac.getBean(name);
System.out.println("bean2 = " + bean2);
}
}
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService",
MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름 없이 타입만으로 조회") void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2() {
MemberServiceImpl memberService = ac.getBean("memberService",
MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회X") void findBeanByNameX() {
//ac.getBean("xxxxx", MemberService.class);
Assertions.assertThrows(NoSuchBeanDefinitionException.class, () ->
ac.getBean("xxxxx", MemberService.class));
} }
추상화를 통해 구현체를 2개 이상 만들고 추상화 클래스 타입로 bean을 찾을 때 오류가 발생한다.
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType() {
Map<String, MemberRepository> beansOfType =
ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " +
beansOfType.get(key));
}
System.out.println("beansOfType = " + beansOfType);
assertThat(beansOfType.size()).isEqualTo(2);
}
물론 위와 같이 getBeansOfType을 통해 해당 클래스 타입 빈을 모두 반환할 수 있다.
AnnotationConfigApplicationContext는 클래스 타입 형식의 설정 정보를 받아들일 수 있고ApplicationContext를 상속받은 다른 객체들은 다양한 형식의 설정 정보를 받아들일 수 있다.
BeanDefinition 정보
빈에 등록된 클래스 인스턴스는 딱 1개만 생성되는 것이 보장되는 디자인 패턴이다. 이를 위해 스프링 컨테이너는 default 값으로 싱글톤으로 객체 인스턴스를 관리한다.
싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다. 무상태(stateless)로 설계해야 한다!
CGLIB라는 바이트 조작 라이브러리를 사용해 등록한 클래스를 상속받은 가짜 클래스를 스프링 빈으로 등록한다.
일일히 Configuration 어노테이션을 통해 수동으로 Bean등록시 설정 정보가 엄청나게 많아진다. 이 점 때문에 스프링은 컴포넌트 스캔이라는 기능을 제공한다. 의존관계 주입 또한 @Autowired 어노테이션을 통해 기능을 제공한다.
컴포넌트 스캔은 @Component 뿐만 아니라 다음과 내용도 추가로 대상에 포함한다.
@Configuration
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyExcludeComponent.class)
)
static class ComponentFilterAppConfig {
}
이렇게 Configuration 에서 includeFilters, excludeFilters를 통해 빈 등록에 제외할 수 있다.
수동 빈 등록이 우선권을 가진다
다양한 의존관계 주입 방법이 있지만 생성자 주입을 선택하라 이 떄 RequiredArgsConstructor 롬복 어노테이션을 사용하면 자동으로 final 붙은 필드들을 모아 생성자를 만들어주기에 이를 사용하자
조회 대상 빈이 2개 이상일 때 해결 방법
@Autowired
private DiscountPolicy rateDiscountPolicy
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
해당 타입의 스프링 빈이 모두 필요할 때는 List Map을 활용할 수 있다.
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
public DiscountService(Map<String, DiscountPolicy> policyMap,
List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
DiscountService는 Map으로 모든 DiscountPolicy 를 주입받는다. 이때 fixDiscountPolicy , rateDiscountPolicy 가 주입된다.
discount () 메서드는 discountCode로 "fixDiscountPolicy"가 넘어오면 map에서 fixDiscountPolicy 스프링 빈을 찾아서 실행한다. 물론 “rateDiscountPolicy”가 넘어오면 rateDiscountPolicy 스프링 빈을 찾아서 실행한다.
애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 딱! 설정 정보에 바로
나타나게 하는 것이 유지보수 하기 좋다
스프링컨테이너생성 → 스프링빈생성 → 의존관계주입 → 초기화콜백 → 사용 → 소멸전 콜백 → 스프링 종료
@PostConstruct
public void init() {
System.out.println("NetworkClient.init"); connect();
call("초기화 연결 메시지");
}
@PreDestroy
public void close() {
System.out.println("NetworkClient.close");
disConnect();
}
@Scope("prototype")
static class PrototypeBean {
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
Scope Annotation을 통해 쉽게 프로토타입 스코프로 선언할 수 있다
싱글톤에서 프로토타입에 대한 의존 주입이 있는 경우나 같이 사용해야 하는 경우는 직접 프로토타입을 받으면 된다.
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
웹 스코프를 의존관계에 주입 받을 경우 문제점 : 해당 빈은 실제 고객의 요청이 와야(request) 생성할 수 있다. 그렇다면 테스트 용도 등으로 request 요청이 안와도 생성하는 방법은 없을까?
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
이렇게 하면 적용 대상(MyLogger) 이를 상속받은 가짜 프록시 객체를 생성한다. 의존관계 주입도 가짜 프록시 객체가 주입된다.
[Spring] Spring MVC 1 기본 정리, 빠르게 알아보자 (0) | 2022.05.12 |
---|---|
[React] React.JS 빠르게 기초 개념 정리 및 이해하기 (0) | 2022.05.12 |
[Angular.js] 소수점 반올림하는 방법 (0) | 2022.05.06 |
[HTML] 이미지 비율 설정하여 보여주는 방법 (0) | 2022.05.04 |
[Keycloak] 설치 및 간단한 SSO 인증 시스템 구현 (0) | 2022.05.03 |