public class AppConfigurer {
// 싱글톤
private Cart cart = new Cart(productRepository(), menu());
public Menu menu() {
return new Menu(productRepository());
}
public ProductRepository productRepository() {
return new ProductRepository();
}
public Cart cart() {
return cart;
}
public Order order() {
return new Order(cart(), discount());
}
}
AppConfigurer 클래스는 각 역할과 책임에 따라 필요한 객체를 생성한 후에 생성한 객체의 참조값을 생성자를 통해 주입
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfigurer {
// private Cart cart = new Cart(productRepository(), menu());
@Bean
public Menu menu() {
return new Menu(productRepository());
}
@Bean
public ProductRepository productRepository() {
return new ProductRepository();
}
@Bean
public Cart cart() {
return new Cart(productRepository(), menu());
}
@Bean
public Order order() {
return new Order(cart(), discount());
}
}
@Configuration, @Bean 애너테이션 사용
public static void main(String[] args) {
// AppConfigurer appConfigurer = new AppConfigurer();
//
// OrderApp orderApp = new OrderApp(
// appConfigurer.productRepository(),
// appConfigurer.menu(),
// appConfigurer.cart(),
// appConfigurer.order()
// );
// 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfigurer.class);
// 스프링 빈 조회
ProductRepository productRepository = applicationContext.getBean("productRepository", ProductRepository.class);
Menu menu = applicationContext.getBean("menu", Menu.class);
Cart cart = applicationContext.getBean("cart", Cart.class);
Order order = applicationContext.getBean("order", Order.class);
// 불러온 빈 사용
OrderApp orderApp = new OrderApp(
productRepository,
menu,
cart,
order
);
AppConfigurer 클래스를 사용하여 직접 객체를 생성하고 DI를 통해 객체를 연결해 주었던 역할을 이제 스프링 컨테이너가 대신함
// 생성자 주입 예시
public class Menu {
private Product[] products;
public Menu(ProductRepository products) {
this.products = products.getAllProducts();
}
}
: 애플리케이션의 구성 요소를 관리하고 제어
public static void main(String[] args) {
// 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfigurer.class);
// 스프링 빈 조회
ProductRepository productRepository = applicationContext.getBean("productRepository", ProductRepository.class);
Menu menu = applicationContext.getBean("menu", Menu.class);
Cart cart = applicationContext.getBean("cart", Cart.class);
Order order = applicationContext.getBean("order", Order.class);
// 의존성 주입
OrderApp orderApp = new OrderApp(
productRepository,
menu,
cart,
order
);
}
ApplicationContext 인터페이스를 일반적으로 스프링 컨테이너라 부름
AnnotationConfigApplicationContext는 ApplicationContext의 구현 객체이며 매개변수로 구성 정보를 넘겨줌
스프링 컨테이너는 넘겨받은 구성 정보를 가지고 메서드들을 호출해 빈을 생성하고, 빈들 간 의존관계를 설정
호출하는 메서드의 이름을 기준으로 빈의 이름을 등록
@Bean(name="menu2") // 빈의 이름을 다르게 설정 가능
public Menu menu() {
return new Menu(productRepository());
}
: 스프링 컨테이너가 관리하는 자바 객체
클래스의 등록 정보, getter/setter 메서드를 포함하며, 구성 정보(설정 메타 정보)를 통해 생성
스프링 컨테이너가 빈을 생성하고 의존 관계를 연결해 주면 이제 스프링 컨테이너의 관리 하에 있는 객체 빈들을 getBean() 메서드로 조회 가능
applicationContext.getBean("menu", Menu.class);
applicationContext.getBean(Menu.class);
빈 조회 단위 테스트 예시
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainTest {
// 스프링 컨테이너 생성
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfigurer.class);
// 빈 조회 테스트 케이스
@Test
// @DisplayName("")
void findBean() {
// given
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfigurer.class);
// when
Menu menu = applicationContext.getBean("menu", Menu.class);
// then
Assertions.assertThat(menu).isInstanceOf(Menu.class);
}
}
싱글톤으로 객체를 생성하고 관리하는 기능
// 초기화 단계에서 실행되는 메서드
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
// 소멸 단계에서 실행되는 메서드
public interface DisposableBean {
void destroy() throws Exception;
}
@PostConstruct
public void init() {
System.out.println("초기화 메서드 실행");
}
@PreDestroy
public void close() {
System.out.println("종료 메서드 실행");
}
// ClientCongif.java
import org.springframework.context.annotation.Bean;
public class ClientConfig {
@Bean
public TestClient testClient() {
TestClient testClient = new TestClient();
return testClient;
}
}
// ClientMain.java
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ClientMain {
public static void main(String[] args) {
// 컨테이너 생성
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ClientConfig.class);
// 컨테이너 사용(빈 초기화)
TestClient testClient = applicationContext.getBean("testClient", TestClient.class);
testClient.use();
// 컨테이너 종료
applicationContext.close();
}
}
// TestClient.java
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class TestClient {
public TestClient() {
System.out.println("생성자 호출.");
}
@PostConstruct
public void init() {
System.out.println("초기화 메서드 실행");
}
public void use() {
System.out.println("빈 사용");
}
@PreDestroy
public void close() {
System.out.println("종료 메서드 실행");
}
}
@Bean(initMethod = "init", destroyMethod = "close")
싱글톤 이외에 프로토타입(prototype), 세션(session), 리퀘스트(request) 등의 범위가 존재
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class ScopeTest {
@Test
public void scopeTest() {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(TestBean.class);
TestBean bean1 = annotationConfigApplicationContext.getBean(TestBean.class);
TestBean bean2 = annotationConfigApplicationContext.getBean(TestBean.class);
Assertions.assertThat(bean1).isNotEqualTo(bean2); // 테스트 통과
annotationConfigApplicationContext.close();
}
@Scope("prototype")
static class TestBean {
@PostConstruct
public void init() {
System.out.println("init() 초기화 메서드 실행");
}
@PreDestroy
public void close() {
System.out.println("close() 종료 메서드 실행");
}
}
}
// 출력
// init() 초기화 메서드 실행
// init() 초기화 메서드 실행
@Configuration
@ComponentScan
public class AppConfigurer {
// private Cart cart = new Cart(productRepository(), menu());
//
// @Bean
// public Menu menu() {
// return new Menu(productRepository());
// }
//
// @Bean
// public ProductRepository productRepository() {
// return new ProductRepository();
// }
//
// @Bean
// public Cart cart() {
// return new Cart(productRepository(), menu());
// }
//
// @Bean
// public Order order() {
// return new Order(cart(), discount());
// }
//
// @Bean
// public Discount discount() {
// return new Discount(new DiscountCondition[] {
// new CozDiscountCondition(new FixedRateDiscountPolicy()),
// new KidDiscountCondition(new FixedAmountDiscountPolicy())
// });
// }
}
필요한 클래스의 인스턴스를 생성하고, 해당 클래스가 필요로 하는 의존성을 자동으로 찾아서 주입
생성자, 필드, 메서드에 적용 가능
타입으로 빈을 조회
애너테이션이 붙은 곳에 들어 올 수 있는 타입의 객체가 여러개면 필드명이나 매개변수명으로 매칭을 시도
@Autowired 필드명 매칭
@Component
public class UserDiscountCondition implements DiscountCondition {
@Autowired
private DiscountPolicy fixedRateDiscountPolicy;
@Component
public class FixedRateDiscountPolicy implements DiscountPolicy {
private int discountRate = 10;
public int calculateDiscountedPrice(int price) {
return price - (price * discountRate / 100);
}
}
@Component
public class KidDiscountCondition implements DiscountCondition {
@Autowired
private DiscountPolicy fixedAmountDiscountPolicy;
@Component
public class FixedAmountDiscountPolicy implements DiscountPolicy {
private int discountAmount = 500;
public int calculateDiscountedPrice(int price) {
return price - discountAmount;
}
}
@Component
public class UserDiscountCondition implements DiscountCondition {
private DiscountPolicy discountPolicy;
public UserDiscountCondition(@Qualifier("fixedRate") DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Component
@Qualifier("fixedRate")
public class FixedRateDiscountPolicy implements DiscountPolicy {
private int discountRate = 10;
public int calculateDiscountedPrice(int price) {
return price - (price * discountRate / 100);
}
}
@Component
public class KidDiscountCondition implements DiscountCondition {
private DiscountPolicy discountPolicy;
public KidDiscountCondition(@Qualifier("fixedAmount") DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Component
@Qualifier("fixedAmount")
public class FixedAmountDiscountPolicy implements DiscountPolicy {
private int discountAmount = 500;
public int calculateDiscountedPrice(int price) {
return price - discountAmount;
}
}
@Component
public class UserDiscountCondition implements DiscountCondition {
private DiscountPolicy discountPolicy;
public UserDiscountCondition(@Qualifier("fixedRate") DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Component
@Qualifier("fixedRate")
public class FixedRateDiscountPolicy implements DiscountPolicy {
private int discountRate = 10;
public int calculateDiscountedPrice(int price) {
return price - (price * discountRate / 100);
}
}
@Component
public class KidDiscountCondition implements DiscountCondition {
private DiscountPolicy discountPolicy;
public KidDiscountCondition(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Component
@Primary
public class FixedAmountDiscountPolicy implements DiscountPolicy {
private int discountAmount = 500;
public int calculateDiscountedPrice(int price) {
return price - discountAmount;
}
}