Coder Social home page Coder Social logo

spring-core-principles-basic's Introduction

header

         

footer

spring-core-principles-basic's People

Contributors

code5753 avatar

Watchers

 avatar

spring-core-principles-basic's Issues

@Configuration의 역할

Configuration 어노테이션의 역할

@Configuration
public class AppConfig {}

Configuration을 부착 후 실행 했을 때 콘솔창

call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService
bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$ddfbf8e6
  • 로직상 memberRepository가 중복 호출되는데 모두 싱글톤으로 관리 됨
  • 그 이유가 CGLIB이라는 코드 생성 라이브러리를 통해 순수한 AppConfig가 아닌 CGLIB이 재생성한 AppConfig가 생성되는 것으로 유추
  • 새롭게 생성된 AppConfig@CGLIB에서 호출되는(new) 객체가 빈 컨테이너에 등록이 되어있는지 확인하고, 있다면 해당 빈을 없다면 새롭게 추가하여 반환하는 것으로 유추됨

Configuration을 제거 했을 때 콘솔창

call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.memberRepository
call AppConfig.orderService
call AppConfig.memberRepository
bean = class hello.core.AppConfig
  • 순수한 AppConfig class명이 나옴
  • Bean 어노테이션이 부착되어 있어도 memberRepository가 중복 호출되어 더 이상 싱글톤을 유지하지 않음

Configuration을 제거 했을 때 Warning

image

  • 직접적으로 Bean을 호출했으니 DI를 사용하라는 경고문이 뜸

Bean 네이밍의 충돌

What

Bean 네이밍은 크게 두 가지가 있다.

자동 네이밍

@Component // 자동으로 "naming"으로 빈 네임이 설정된다 (lowerCamelCase)
public class Naming { }

수동 네이밍

@Component("naming")
public class NoName { }

스프링 부트 환경인 곳과 아닌 곳에서 위 네이밍이 충돌하는 경우에 어떻게 쓰이는지 살펴보자.

스프링 부트가 아닌 환경

정상실행되지만 Overriding 되는 것을 볼 수 있다

01:04:19.317 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing [Generic bean: class [hello.core.member.MemoryMemberRepository]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; 
... 생략

스프링 부트

런타임시에 에러가 발생하고 아예 종료된다

image

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

위 메세지를 보면 default option으로 오버라이딩이 false인 것을 알 수 있고, 오버라이딩 기능을 원하는 경우 옵션을 변경할 수 있다.

결론

수동 네이밍과 자동 네이밍이 충돌하는 경우는 절대 의도한 경우는 아닐 것이고 개발자의 실수인 경우가 대부분일 것이라 예상된다.
즉, 오버라이딩된 경우도 의도한 경우가 아닌데 실행에는 문제가 없다는 것은 큰 문제를 야기할 수 있다.

SRP, OCP, DIP 위반 사례

What

✉refact: 주문 시 할인 정책 변경

// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();

SRP 위반

한 클래스는 하나의 책임만 가져야 한다

클라이언트 객체는 직접 구현 객체를 생성, 연결, 실행하는 다양한 책임을 갖고 있음

DIP 위반

구현체가 아닌 역할에 의존

OrderServiceImpl이 interface인 DiscountPolicy를 의존하는 것 처럼 보이지만,
실제론 FixDiscountPolicy에도 의존하고 있음

OCP 위반

수정에 닫혀있고(코드 수정 X) 확장하는 것

FixDiscountPolicyRateDiscountPolicy로 변경하는 순간 OrderServiceImpl의 코드도 함께 변경해야 한다는 문제 발생
구현(class)과 역할(interface)을 분리한 것 처럼 보이지만 기존 코드를 수정했기에 완벽한 분리가 아님

Solution

  • 클라이언트인 OrderServiceImplDiscountPolicy의 구현 객체를 대신 생성하고 주입해야 한다.
    • 클라이언트 객체는 실행만, 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당하여 SRP, OCP, DIP를 지킬 수 있음

상태(stateful)를 갖는 싱글톤 문제

What

feat(test): 상태를 갖는 싱글톤 구현

        // ThreadA: A 사용자가 1만원 주문
        statefulService1.order("userA", 10000);

        // ThreadB: B 사용자가 2만원 주문
        statefulService1.order("userB", 20000);

        // ThreadA: A 사용자가 주문 금액 조회
        int price = statefulService1.getPrice();
        System.out.println("price = " + price);
  • A 사용자의 주문금액은 1만원이지만 B 사용자의 금액 동기화로 인해 2만원으로 인식되는 문제 발생

Solution

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

StatefulService에서 price라는 필드값이 바뀌어 발생하는 문제이므로 각 클라이언트별로 상태(stateful)를 유지하지 않는 형태로 변경 필요

request scope의 로거의 문제점

What

request scope는 요청이 들어온 직후 ~ 끝나는 시점까지만 생존하는 주기를 갖고 있다.
하지만 스프링 구동 시 logger(request scope)를 찾게 되는데 bean이 비어있으므로 에러를 발생시킨다.

Solution

logger 찾는 것을 지연시킬 수 있다면 어떨까?
ObjectProvider를 이용해 가능하다.
쉽게 얘기하자면,
ObjectProvider를 사용하지 않고 스프링을 구동시키면 빈을 생성하고 주입하는 과정에서 불가피하게 request scope bean을 주입하게된다. 하지만 request scope이 아니기 때문에 주입에 실패하여 에러가 발생한다.
따라서, ObjectProvider로 선언하여 실제 Request가 들어 왔을 때 Request scope bean을 생성하도록 하는 것이다.

조회시 2개의 빈이 충돌하는 문제

What

orderServiceImpl에서 DiscountPolicy를 주입 받는다.
기존 코드에서는 RateDiscountPolicyComponent로 등록되어 문제가 없었으나
만약 FixDiscountPolicyComponent로 등록하게 되는 경우
DiscountPolicy를 조회하는 경우 2개의 빈이 조회되는 문제가 발생한다.

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

How

조회 대상 빈이 2개 이상일 때 대처법

Autowired 필드 명 매칭

  1. 타입으로 확인 -> 기존은 필드명도 discountPolicy 였으므로 2번 역할이 무의미하다
  2. 타입 매칭의 결과가 2개 이상일 때 필드명, 파라미터명으로 확인
    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy fixDiscountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = fixDiscountPolicy;
    }

필드명을 fixDiscountPolicy로 지정하는 경우 문제 없이 돌아가는 것을 알 수 있다.

Qualify 사용

Qualify는 추가 구분자를 붙여주는 방법이다.

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("fixDiscountPolicy") DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
  1. Qualifier끼리 매칭
  2. 빈 이름 매칭
  3. NoSuchBeanDefinitionException 발생
    QualifierQualifier가 붙은 애들끼리 찾을 때 사용하는게 유용하다.

단점

  • @Qualifer를 쓰는 쪽도 등록하는 쪽도 모두 붙여주어야 함
  • 문자열 비교이므로 컴파일시 에러가 발생하지 않아 개발자 실수로 에러가 발생할 수 있음
      @Qualifier("mainn")
    • 이 경우 @Qualifier를 포함한 커스텀 어노테이션으로 해결이 가능함
    • 단, 스프링에서 이미 지원하고 있는 기능은 아닌지 판단이 필요하고 커스텀 어노테이션을 무분별하게 사용하는 것은 지양

Primary 사용

Primary는 우선순위를 정하는 방법이다.
우선 순위가 높은 빈에 @Primary를 붙여주면 된다.

Qualifier와 Primary의 우선권

늘 그렇지만 수동에 가까운 쪽이 더 높은 우선권을 갖게 된다.
이 경우 @Qualifier@Primary보다 높은 우선권을 갖는다.

싱글톤 빈과 프로토타입 빈 함께 사용하기

What

Singleton Bean, Prototype Bean을 함께 사용하면 아래와 같은 문제가 발생한다

  • prototype bean은 최초 생성시에만 초기화/주입 작업이 이루어지고 그 이후엔 관리하지 않음
  • 만약 해당 prototype bean을 client(singleton bean)에서 호출해 사용한다면?
  • 해당 prototype bean은 호출 시 마다 새롭게 생성되는 것이 아닌 동일한 빈이 계속 쓰이는 문제가 발생

Solution

Applicationcontext로 찾아서 매 번 생성하는 방법

@Autowired
Applicationcontext ac;

public int logic() {
  PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
  prototypeBean.addCount();
  int count = prototypeBean.getCount();
  return count;
}
  • 직접 Applicationcontext으로 빈을 가져오는 방식이다. 당연히 prototype 빈이 가져와지고 매번 생성/주입 할 수 있다.
  • 하지만 의존관계를 외부에서 주입 받는게 아니라 직접 필요한 의존관계를 찾는 것을 Dependency Lookup(DL) 의존관계 조회(탐색)이라 한다.
  • 이처럼 스프링 Applicationcontext 전체를 주입받게 되면 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트가 어려워진다.

ObjectFactory, ObjectProvider 사용

        private final ObjectProvider<PrototypeBean> prototypeBeanProvider;

        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }

DL 기능만 해주는 ObjectProvider를 사용하는 방법이다.
ObjectFactory의 하위 인터페이스인데 ObjectFactorygetObject만 수행 가능하고, 여기서 추가적인 편의 기능이 생긴 것이 ObjectProvider이다.
하지만 스프링에 종속적이라는 단점이 있다.

javax.inject Provider

자바 표준 Provider 사용

        private final Provider<PrototypeBean> prototypeBeanProvider;

        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.get();
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }
implementation 'javax.inject:javax.inject:1'

ObjectProvider와 사용법은 동일하지만 라이브러리를 추가해야하고 기능이 get밖에 없다는 특징이 있다.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.