| DBMS | 구문 |
| ORACLE | DECODE함수, CASE WHEN 구문 |
| MSSQL | CASE WHEN 구문 |
| MYSQL | IF 함수, CASE WHEN 구문 |

▶ CASE WHEN 문법의 경우 SQL Injection 공격시 아주 유용하게 활용된다.
| DBMS | 구문 |
| ORACLE | DECODE함수, CASE WHEN 구문 |
| MSSQL | CASE WHEN 구문 |
| MYSQL | IF 함수, CASE WHEN 구문 |

▶ CASE WHEN 문법의 경우 SQL Injection 공격시 아주 유용하게 활용된다.
앞전 학습하였던 @Qualifier("mainDiscountPolicy) 라는 어노테이션은 컴파일시 타입 체크가 안된다.
다음과 같이 어노테이션을 만들어 해결 할 수 있다.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy{
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
DiscountPolicy의 구현체인 RateDiscountPolicy, FixDiscountPolicy를 둘다 @Component로 Bean등록을 하였다.
실행시
@Test
void basicScan(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderServiceImpl' defined in file [C:\java\inflearn\core\out\production\classes\hello\core\order\OrderServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy
- '빈이 2개 등록되어있다' 라는 에러 발생
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
DiscountPolicy rateDiscountPolicy 필드명 매칭시 에러가 발생하지않는다.
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy{
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy{
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
▶모든 구현체에 @Quilifider를 사용해야하여 코드가 지저분해진다.
@Primary는 우선순위를 정하는 방법이다. @Autowired시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
▶팀 프로젝트시 Bean에러가 지속 발생한적이있다. 그 당시에는 프로젝트 개발에만 급급하여 스프링내부 구조 및 의존관계와 같이 내부적인 구조를 몰랐었다. 그당시 에러메세지를 구글링한결과 @Primary라는 어노테이션을 사용하라고하여 에러를 해결한적이있었던 기억이난다....!
의존관계 주입은 크게 4가지 방법이있다.
특징
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
※ 생성자가 1개일시 @Autowired 어노테이션 생략이 가능하다.
특징
@Component
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
※ '@Autowired'의 기본동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 (required = false)로 지정하여야한다.
특징
@Component
public class OrderServiceImpl implements OrderService{
@Autowired private final MemberRepository memberRepository;
@Autowired private final DiscountPolicy discountPolicy;
}
특징
▶ 의존관계 자동주입은 스프링 컨테이너가 관리하는 스프링 빈이여야 동작한다. 스프링 빈이 아닌 Class의 경우 '@Autowired' 코드를 적용해도 아무 기능도 동작하지않는다.
불변
누락
final 키워드
자동주입 대상을 옵션으로 처리하는 방법
'@Autowired(required = true)'(기본값 사용시)
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'autowiredTest.TestBean': Unsatisfied dependency expressed through method 'setNoBean1' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'hello.core.member.Member' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
public class AutowiredTest {
@Test
void AutowiredOption(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
}
static class TestBean {
@Autowired(required = false)
public void setNoBean1(Member noBean1){
System.out.println("noBean1 = " + noBean1);
}
@Autowired
public void setNoBean2(@Nullable Member noBean2) {
System.out.println("noBean2 = " + noBean2);
}
@Autowired
public void setNoBean3(Optional<Member> noBean3) {
System.out.println("noBean3 = " + noBean3);
}
}
}
noBean3 = Optional.empty
noBean2 = null
※ Member는 스프링 빈이 아니다.
▶ 이전 프로젝트 개발시에는 스프링 컨테이너, 빈, 의존관계에 대한 세부적인 개념이없었는데 조금씩 정리가 되어간다.
▶Lombok을 활용한 생성자 주입으로 하자!
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
컴퍼넌트 스캔을 사용하려면 '@ComponentScan'을 설정 정보에 붙여줘야한다.
기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가없다.
※ 컴퍼넌트 스캔을 사용하려면 '@Configuration'이 붙은 설정 정보도 자동으로 등록되기때문에 AppConfig, TestConfig 등 앞서 만들어두었던 설정정보도 함께 등록된다. 그래서 'excluderFilters' 를 이용해서 설정정보 컴포넌트 스캔 대상에서 제외시켰다.
※ '@Configration' 이 컴포넌트 스캔의 대상이 된 이유는 '@Configration' 소스코드를 열어보면 '@Component' 어노테이션이 붙어있기 때문이다.
@Component라는 어노테이션이 붙은 클래스에 대해 Spring Bean으로 등록한다.
@Component
public class MemoryMemberRepository implements MemberRepository{
@Component
public class RateDiscountPolicy implements DiscountPolicy{
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Component
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
'@Autowired' 를 사용하면 생성자에 여러의존관계도 한번에 주입 할 수 있다.
Test
public class AutoAppConfigTest {
@Test
void basicScan(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
}


생성자에 '@Autowired'를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.
모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래걸린다. 그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정 할 수 있다.
@ComponentScan(
basePackages = "hello.core"
)
※ 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 '@SpringBootApplication'를 이 프로젝트 시작루트 위치에 두는것이 관례이다.('@SpringBootApplication' 설정안에 '@ComponentScan'이 들어있다.)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
컴포넌트 스캔은 '@Component'뿐만 아니라 다음과 내용도 추가로 대상에 포함한다.
※ 어노테이션은 상속관계라는 것이 없다. 그래서 이렇게 어노테이션이 특정 어노테이션을 들고 있는 것을 인식 할 수 있는 것은 자바의기능이 아닌 스프링이 지원하는 기능이다.
▶ 가장 궁금했던 점이 풀렸다. 팀 프로젝트 시 Bean에러를 며칠간 해결하기위해 구글링 중 ComponentScan에 대해 알았
지만, 정확한 사용법은 몰랐다. (그당시 Bean등록에러가아닌 entity의 문제였지만..)
▶ 나는 개인프로젝트시 @Controller, @Service, @Repository 의 어노테이션을 주로 사용하였다.
Appconfig내에서
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
// return new FixDiscountPolicy();
}
}
결과적으로 각각 다른 2개의 new MemberRepository()가 생성되면서 싱글톤이 깨지는것처럼보인다.
MemberServiceImpl
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
//테스트 용도
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
OrderServiceImpl
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
//테스트 용도
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
public class ConfigurationTest {
@Test
void configurationTest(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println("memberRepository = " + memberRepository);
System.out.println("memberService -> memberRepository = " + memberRepository1);
System.out.println("orderService -> memberRepository = " + memberRepository2);
Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
}
memberRepository = hello.core.member.MemoryMemberRepository@95e33cc
memberService -> memberRepository = hello.core.member.MemoryMemberRepository@95e33cc
orderService -> memberRepository = hello.core.member.MemoryMemberRepository@95e33cc
확인결과 모든 memberRepository 인스턴스는 같은 객체를 가진다.
@Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반홚나다.
=> 싱글톤이 보장된다.
AppCofig@CGLIB


순수한 클래스라면 'class.hello.core.AppCofig' 라고 출력이되어야 한다.
xxxCGLIB이 붙으면 내가만든 클래스가 아니라 스프링이 CGBLIB이라는 바이트 코드조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클랜스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다.

그 임의의 다른 클래스가 바로 싱글톤이 보장되도록해준다.
※ @Configuration 을 적용하지않고 @Bean만적용시 스프링 빈 등록은 가능하다, 하지만 싱글톤 보장이 되지않는다.
Spring 설정정보 Class에는 @Configuration을 항상 사용하자!
memberRepository = hello.core.member.MemoryMemberRepository@74287ea3
memberService -> memberRepository = hello.core.member.MemoryMemberRepository@7d7758be
orderService -> memberRepository = hello.core.member.MemoryMemberRepository@2bdd8394

public class SingletonTest {
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer(){
AppConfig appConfig = new AppConfig();
// 1.조회 : 호출할 때 마다 객체를 생성
MemberService memberService1 = appConfig.memberService();
// 2.조회 : 호출할 때 마다 객체를 생성
MemberService memberService2 = appConfig.memberService();
//참조값이 다른것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isNotSameAs(memberService2);
}
}
memberService1 = hello.core.member.MemberServiceImpl@275710fc
memberService2 = hello.core.member.MemberServiceImpl@525f1e4e
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance(){
return instance;
}
private SingletonService(){
}
public void logic(){
System.out.println("싱글톤 객체");
}
}
@Test
@DisplayName("싱글톤 패턴을 사용")
void singletonServiceTest(){
SingletonService instance1 = SingletonService.getInstance();
SingletonService instance2 = SingletonService.getInstance();
System.out.println("instance1 = " + instance1);
System.out.println("instance2 = " + instance2);
Assertions.assertThat(instance1).isSameAs(instance2);
//same ==
//equal equals메서드
}
instance1 = hello.core.singleton.SingletonService@6ee12bac
instance2 = hello.core.singleton.SingletonService@6ee12bac
※싱글톤 패턴을 구현하는 방법은 여러가지가 있다. 본예시는 가장단순하고 간편한벙법이다.
결론적으로 싱글톤패턴은 유연성이 떨어져 안티패턴이라고 불리기도 한다.
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체인스턴스를 싱글톤(1개만 생성)으로 관리한다.
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer(){
// AppConfig appConfig = new AppConfig();
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// 1.조회 : 호출할 때 마다 객체를 생성
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
// 2.조회 : 호출할 때 마다 객체를 생성
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
//참조값이 같은것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isSameAs(memberService2);
}
memberService1 = hello.core.member.MemberServiceImpl@38604b81
memberService2 = hello.core.member.MemberServiceImpl@38604b81

public class StatefulService {
private int price;
public void order(String name, int price){
System.out.println("name = " + name + ", price = " + price);
this.price = price;
}
public int getPrice(){
return price;
}
}
class StatefulServiceTest {
@Test
void statefulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//ThreadA : 사용자A 10000원주문
statefulService1.order("userA", 10000);
//ThreadB : 사용자B 20000원주문
statefulService2.order("userB", 20000);
//ThreadA : 사용자A 주문금액 조회
System.out.println(statefulService1.getPrice());
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
public class StatefulService {
// private int price;
public int order(String name, int price){
System.out.println("name = " + name + ", price = " + price);
// this.price = price;
return price;
}
/*public int getPrice(){
return price;
}*/
}
class StatefulServiceTest {
@Test
void statefulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//ThreadA : 사용자A 10000원주문
int userAPrice = statefulService1.order("userA", 10000);
//ThreadB : 사용자B 20000원주문
int userBPrice =statefulService2.order("userB", 20000);
//ThreadA : 사용자A 주문금액 조회
System.out.println(userAPrice);
Assertions.assertThat(userAPrice).isEqualTo(10000);
}
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
싱글톤 방식은 공유되지 않는 무상태로 설계하자.
김영한님께서 실무에서도 몇년에 한번씩 꼭나오고, 금액적인부분은 고객에게 배상해야하기때문에 아주아주 중요하다고한다!!
public class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " , object = " + bean);
}
}
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
//BeanDefinition.ROLE_APPLICATION : 직접 등록한 애플리케이션 빈
//BeanDefinition.ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " , object = " + bean);
}
}
}
}
빈 출력은 3가지방법으로 가능하다.
public class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName(){
MemberService memberService = ac.getBean("memberService", MemberService.class);
System.out.println("memberService = " + memberService);
System.out.println("memberService.getClass() = " + memberService.getClass());
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름없이 타입으로만 조회")
void findBeanByType(){
MemberService memberService = ac.getBean(MemberService.class);
System.out.println("memberService = " + memberService);
System.out.println("memberService.getClass() = " + memberService.getClass());
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2(){
MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
System.out.println("memberService = " + memberService);
System.out.println("memberService.getClass() = " + memberService.getClass());
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회X")
void findBeanByNameX(){
assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("xxxxx", MemberService.class));
}
※ 빈 이름으로 조회시 조회되지않을경우 NoSuchBeanDefinitionException 에러 발생
타입의 경우 같이 타입이 2개 이상일경우 에러가 발생한다.
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
@Test
@DisplayName("타입으로 조회시 같은타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanTypeDuplicate(){
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findBeanByName(){
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
※ 타입으로 조회시 둘이상일경우 NoUniqueBeanDefinitionException 에러 발생
타입이 둘이상일경우 빈 이름을 지정하여야 조회가 가능하다.
@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);
}
2개이상의 타입을 조회시에는 Map을 이용하여 조회가 가능하다.
부모타입으로 조회하면 자식타입도 함께 조회가된다.
@Configuration
static class TestConfig{
@Bean
public DiscountPolicy rateDiscountPolicy(){
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy(){
return new FixDiscountPolicy();
}
}
@Test
@DisplayName("부모타입으로 조회시, 자식이 둘 이상있으면 중복오류가 발생한다.")
void findBeanByParentTypeDuplicate(){
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("부모타입으로 조회시, 자식이 둘 이상있으면 빈이름을 지정하면된다.")
void findBeanByParentTypeBeanName(){
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("특정하위 타입으로 조회")
void findBeanBySubType(){
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("부모타입으로 모두조회")
void findBeanByParentType(){
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
assertThat(beansOfType.size()).isEqualTo(2);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + ", value = " + beansOfType.get(key));
}
}
@Test
@DisplayName("부모타입으로 모두조회 - Object")
void findBeanByObjectType(){
Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + ", value = " + beansOfType.get(key));
}
}
※ 부모타입으로 조회시 자식까지 모두 조회가된다.
Java의 최상위 Class인 Object 조회시 Spring의 모든 Bean이 조회된다.
포트폴리오 개발시에는 Test를 사용하지못했다. 실력도없었고, 시간적여유가없었기때문이다.
에러 잡기바쁘고 API설계, DB 스키마 수정 등 때문에 Test코드가 중요하다 말은들었지만....
아무튼 실무에서는 80%가 Test개발이라고한다. 김영한님께서도 기본적인 Test로 강의를 많이해주시듯
Test코드를 익힐 필요성을 느꼈다..
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
※ 더정확히는 스프링 컨테이너를 부를때 'BeanFactory', 'ApplicationContext'로 구분해서 이야기한다.
스프링 컨테이너 생성

스프링 빈 등록
※ 빈이름은 항상 다른 이름을 부여해야한다!!

스프링 빈 의존관계 설정 - 준비

스프링 빈 의존관계 설정 - 완료
