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

 

 

 

▶ CASE WHEN 문법의 경우 SQL Injection 공격시 아주 유용하게 활용된다.

 

강의출처 : https://www.inflearn.com/course/SQL-%EC%9D%B8%EC%A0%9D%EC%85%98-%EA%B3%B5%EA%B2%A9-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95#

앞전 학습하였던 @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;
}

 

강의출처 : 스프링 핵심 원리 - 기본편 - 인프런 | 강의 (inflearn.com)

조회 대상 Bean이 2개 이상일때 해결방법

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

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = rateDiscountPolicy;
}

DiscountPolicy rateDiscountPolicy 필드명 매칭시 에러가 발생하지않는다.

@Autowired매칭정리

  1. 타입매칭
  2. 타입매칭의 결과가 2개이상일때 필드명, 파라미터 명으로 빈 이름 매칭

@Quilifier 

@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는 추가 구분자를 붙여주는 방법이다. 주입시 추가적인 방법을 제공하는것이지 빈이름을 변경하는것은 아니다.
  • @Quilifider로 주입할때 @Quilifider("mainDiscountPolicy")를 못찾으면 어떻게 될까? 
  • 그러면 "mainDiscountPolicy"라는 이름의 스프링 빈을 추가로 찾는다.
  • @Quilifider는 @Quilifider를 찾는 용도로만 사용하는개 명확하고 좋다고한다! 

@Quilifider정리

  1. @Quilifider끼리매칭
  2. 빈 이름 매칭
  3. "NoSuchBeanDefinitionException" 예외발생

▶모든 구현체에 @Quilifider를 사용해야하여 코드가 지저분해진다.

@Primary사용

@Primary는 우선순위를 정하는 방법이다. @Autowired시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.

 

▶팀 프로젝트시 Bean에러가 지속 발생한적이있다. 그 당시에는 프로젝트 개발에만 급급하여 스프링내부 구조 및 의존관계와 같이 내부적인 구조를 몰랐었다. 그당시 에러메세지를 구글링한결과 @Primary라는 어노테이션을 사용하라고하여 에러를 해결한적이있었던 기억이난다....!

 

 

강의출처 : 스프링 핵심 원리 - 기본편 - 인프런 | 강의 (inflearn.com)

의존관계 주입 방법

의존관계 주입은 크게 4가지 방법이있다.

  • 생성자 주입
  • 수정자 주입(Setter 주입)
  • 필드 주입
  • 일반 메서드 주입

생성자 주입

  • 이름그대로 생성자를 통해서 의존관계를 주입받는 방법이다.

특징

  1. 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
  2. 불변, 필수 의존관계에 사용
@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 어노테이션 생략이 가능하다.

 

수정자 주입(Setter 주입)

  • setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.

특징

  1. 선택, 변경 가능성이 있는 의존관계에 사용
  2. 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방식이다.
@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)로 지정하여야한다.

 

필드 주입

특징

  • 코드가 간결하지만, 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명적인 단점이있다.
  • DI 프레임워크가 없으면 아무것도 할 수 없다.
@Component
public class OrderServiceImpl implements OrderService{

    @Autowired private final MemberRepository memberRepository;
    @Autowired private final DiscountPolicy discountPolicy;
}

일반 메서드 주입

  • 일반 메서드를 통해서 주입 받을수 있다.

특징

  • 한번에 여러 필드를 주입 받을 수 있다.
  • 일반적으로 잘 사용하지 않는다.

 

▶  의존관계 자동주입은 스프링 컨테이너가 관리하는 스프링 빈이여야 동작한다. 스프링 빈이 아닌 Class의 경우 '@Autowired' 코드를 적용해도 아무 기능도 동작하지않는다.

 

 

생성자주입이 권장되는 이유

불변

  • 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경 할 일이 없다.
    오히려 의존관계는 애플리케이션 종료 전 까지 변하면 안된다.
  • 생성자 주입은 객체를 생성할때 딱 1번만 호출되므로 이후에 호출되는 일이 없다.

누락

  • 생성자 주입시 파라메타 누락시 인지가 가능하다.(컴파일 오류)

final 키워드

  • 생성자 주입을 사용하면 final 키워드를 사용할 수 있다.

 

옵션처리

  • 주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
  • 그런데 '@Autowired'만 사용하면 'required' 의 기본값이 'true' 로 되어 있어서 자동 주입 대상이 없으면 오류가 발생한다.

자동주입 대상을 옵션으로 처리하는 방법

  1. '@Autowired(required = false)' : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨.
  2. @Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
  3. Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.

'@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을 활용한 생성자 주입으로 하자!

 

 

강의출처 : 스프링 핵심 원리 - 기본편 - 인프런 | 강의 (inflearn.com)

스프링 빈 등록방법 @ComponentScan

@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);
    }

}

 

  • '@ComponentScan'은 '@component'가 붙은 모든 클래스를 스프링 빈으로 등록한다.

생성자에 '@Autowired'를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.

탐색위치와 기본 스캔 대상

모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래걸린다. 그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정 할 수 있다.

@ComponentScan(
        basePackages = "hello.core"
        )
  • 'basePackages' : 탐색할 패키지의 시작위치를 지정, 하위 패키지를 모두 탐색한다.
  • 'basePackageClasses' : 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.
  • 만약 지정하지않으면 '@ComponentScan' 이 붙은 설정 정보 클래스이 패키지가 시작위치가 된다.
  • 권장방법 : 패키지위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는것이다.

※ 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 '@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'뿐만 아니라 다음과 내용도 추가로 대상에 포함한다.

  • '@Component' : 컴포넌트 스캔에서 사용
  • '@Controller' : 스프링 MVC 컨트롤러에서 사용
    • 스프링 MVC 컨트롤러로 인식
  • '@Service' : 스프링 비즈니스 로직에서 사용
    • 개발자들이 핵심 비즈니스 로직이 여기있다 인식정도
  • '@Repository' : 스프링 데이터 접근 계층에서 사용
    • 데이터 계층의 예외를 스프링예외로 변환
    • A DB사용시 예외와 B DB예외 사용시 예외는 다르다. 그래서 Spring이 추상적인 예외로 변경 통합시켜준다?
  • '@Configuration' : 스프링 설정 정보에서 사용

※ 어노테이션은 상속관계라는 것이 없다. 그래서 이렇게 어노테이션이 특정 어노테이션을 들고 있는 것을 인식 할 수 있는 것은 자바의기능이 아닌 스프링이 지원하는 기능이다.

 

▶ 가장 궁금했던 점이 풀렸다. 팀 프로젝트 시 Bean에러를 며칠간 해결하기위해 구글링 중 ComponentScan에 대해 알았

    지만, 정확한 사용법은 몰랐다. (그당시 Bean등록에러가아닌 entity의 문제였지만..)

나는 개인프로젝트시 @Controller, @Service, @Repository 의 어노테이션을 주로 사용하였다.

 

 

강의출처 : 스프링 핵심 원리 - 기본편 - 인프런 | 강의 (inflearn.com)

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();
    }

}
  • memberService 빈을 만드는 코드를 보면 memberRepository()를 호출한다.
    • 이메서드를 호출하면 new MemberRepository()를 호출한다.
  • orderService 빈을 만드는 코드를 보면 memberRepository()를 호출한다.
    • 이메서드를 호출하면 new MemberRepository()를 호출한다.

결과적으로 각각 다른 2개의  new MemberRepository()가 생성되면서 싱글톤이 깨지는것처럼보인다.

 

Test 코드를 작성하여 확인

 

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

 

 

 

강의출처 : 스프링 핵심 원리 - 기본편 - 인프런 | 강의 (inflearn.com)

 

스프링이 없는 순수 DI 컨테이너 테스트

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

  • 스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청을 할 때 마다 객체를 새로 생성한다.
  • 고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고 소멸된다. => 메모리 낭비가 심하다.
  • 해결방안은 해당 객체가 딱 1개만 생성되고 공유하도록 설계하면된다. => 싱글톤

싱글톤적용

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance(){
        return instance;
    }
    private  SingletonService(){
        
    }
    
    public void logic(){
        System.out.println("싱글톤 객체");
    }
}
  • private으로 new 키워들 막아두었다.
  • 호출할때 마다 같은 객체 인스턴스가 반환된다.
@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

 

※싱글톤 패턴을 구현하는 방법은 여러가지가 있다. 본예시는 가장단순하고 간편한벙법이다.

싱글톤 패턴의 문제점

  • 싱글톤 패턴을 구현하는 코드 자체가 많이들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다 => DIP 위반
  • 클라이언트가 구체클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
  • 테스트하기 어렵다.
  • 내부 속성을 변경하거나 초기화 하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.

결론적으로 싱글톤패턴은 유연성이 떨어져 안티패턴이라고 불리기도 한다.

싱글톤 컨테이너

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체인스턴스를 싱글톤(1개만 생성)으로 관리한다.

 

  • 스프링 컨테이너는 싱글톤 패턴을 적용하지않아도, 객체인스턴스를 싱글톤으로 관리한다.
  • 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 
  • 스프링 컨테이너는 이런기능 덕분에 싱글톤 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지 할 수 있다.
    • 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.
    • DIP, OCP, 테스트, private 생성자로부터 자유롭게 싱글톤을 사용가능
@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

 

싱글톤 방식의 주의점(매우중요!!)

  • 싱글톤 패턴이든, 스피링 같은 싱글톤 컨테이너를 사용하든 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지하게 설계하면 안된다.
  • 무상태(stateless)로 설계해야한다.
    1. 특정 클라이언트에 의존적인 필드가 있으면 안된다.
    2. 특정 클라이언트가 값을 변경 할 수 있는 필드가 있으면 안된다.
    3. 가급적 읽기만 가능해야한다.
    4. 필드 대신에 자바에서 공유되지않는 지역변수, 파라미터, ThreadLocal 등을 사용해야한다.
  • 스프링빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생 할 수 있다.
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();
        }
    }
}
  • statefulService의 price필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경하였다.
  • 사용자 A의 주문금액은 10,000원 이지만  출력 결과는 20,000원이다.

무상태로 변경코드

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();
        }
    }
}

 

 

싱글톤 방식은 공유되지 않는 무상태로 설계하자.

 

 

 

김영한님께서 실무에서도 몇년에 한번씩 꼭나오고, 금액적인부분은 고객에게 배상해야하기때문에 아주아주 중요하다고한다!!

 

강의출처 : 스프링 핵심 원리 - 기본편 - 인프런 | 강의 (inflearn.com)

스프링 빈 출력 확인하기

 

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);
  1. 빈 이름으로 출력
  2. 빈 타입으로 출력
  3. 구체 타입으로 출력
@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코드를 익힐 필요성을 느꼈다..

 

강의출처 : 스프링 핵심 원리 - 기본편 - 인프런 | 강의 (inflearn.com)

스프링 컨테이너 생성

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

 

  • 'ApplicationContext' 를 스프링 컨테이너라 한다.
  • 'ApplicationContext' 는 인터페이스이다.
  • 'AppConfig' (@Configuration)를 사용했던 방식이 어노테이션기반의 자바설정 클래스로 스프링 컨테이너를
    만든 것이다.

 

※ 더정확히는 스프링 컨테이너를 부를때 'BeanFactory', 'ApplicationContext'로 구분해서 이야기한다.

 

스프링 컨테이너의 생성과정

스프링 컨테이너 생성

  • 'new AnnotationConfigApplicationContext(AppConfig.class)'
  • 스프링 컨테이너를 생성할때는 구성정보를 지정해주어야한다.
  • 여기서는 (AppConfig.class)를 구성정보로 지정했다.

 

 

 

 

 

 

 

 

 

 

스프링 빈 등록

  • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스정보를 사용해서 스프링 빈을 등록한다.
  • 빈이름
    1. 빈이름은 메서드 이름을 사용
    2. 빈이름으 직접 부여가능 @Bean(name ="memberService2")

 

※ 빈이름은 항상 다른 이름을 부여해야한다!!

 

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

 

 

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

 

  • 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.

 

강의출처 : 스프링 핵심 원리 - 기본편 - 인프런 | 강의 (inflearn.com)

스프링 컨테이너

  • ApplicationContext를 스프링 컨테이너라 한다.
  • 기존에는 개발자가 AppConfig를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨터이너를 통해서 사용한다.
  • 스프링 컨테이너는 @Configration이 붙은 AppConfig를 설정(구성) 정보로 사용한다. 여기서 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다.
  • 스프링 빈은 @Bean이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다.
    ex) memberService, orderService
  • 이전에는 개발자가 필요한 객체를 AppConfig를 사용해서 직접 조회했지만, 스프링 컨테이너를 통해서 필요한 스프링 Bean을 찾아야한다. 
  • ex)
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    MemberService memberService = applicationContext.getBean("memberService", MemberService.class); OrderService orderService = applicationContext.getBean("orderService", OrderService.class);

 

 

강의출처 : 스프링 핵심 원리 - 기본편 - 인프런 | 강의 (inflearn.com)

+ Recent posts