스파이더 웹 개발

스프링 기본 본문

Spring

스프링 기본

스파이더웹 2022. 7. 20. 14:30
728x90
반응형

스프링이란?

스프링 부트

  • 스프링을 편리하게 사용할 수 있도록 지원
  • 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성
  • Tomcat같은 웹 서버를 내장하여 별도의 웹 서버를 설치하지 않아도됨
  • 손쉬운 빌드 구성을 위한 statrer 종속 성제공
  • 스프링과 외부 라이브러리 자동 구성

스프링 핵심

  • 스프링은 자바 언어 기반의 프레임워크
  • 자바 언어의 가장 큰 특징 - 객체 지향 언어
  • 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임 워크
  • 스프링은 좋은 객체 지향 어플리케이션을 개발할 수 있게 도와주는 프레임워크

좋은 프로그래밍 설계

역할과 구현을 분리

  • 클라이언트는 대상의 역할만 알면 된다
  • 클라이언트는 구현 대상의 내부 구조를 몰라도 된다
  • 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다
  • 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다
  • 즉, 인터페이스에만 의존해야지 인터페이스를 구현한 클래스에는 의존해서는 안된다

ex) 순수자바

import hello.core.member.Member;

//할인 정책 인터페이스
public interface DiscountPolicy {
    //return 할인 대상 금액

    int discount(Member member, int price);
}
//정액 할인 정책, 할인정책을 구현한 클래스
public class FixDiscountPolicy implements DiscountPolicy{

    private int discountFixAmount = 1000;

    @Override
    public int discount(Member member, int price) {

        //enum 타입은 equals가 아니는 == 비교교
       if(member.getGrade()== Grade.VIP){
            return discountFixAmount;
        }else {
           return 0;
       }
    }
}

정액 할인 정책이 아닌 정률 할인 정책으로 변경해야 할 경우

할인정책 인터페이스를 구현한 정률할인정책 클래스만 만들어주면된다

//정률할인정책 클래스
public class RateDiscountPolicy implements DiscountPolicy{

    private static int discount = 10;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return price * discount / 100;
        } else {
            return 0;
        }
    }
}

그러나 할인정책의 역할을 의존하는 클래스에서 문제(?)가 발생되는데 DIP원칙과 OCP원칙에 위반된다 그 이유는 DIP의경우 주문서비스 클라이언트는 인터페이스 뿐만아니라 인터페이스를 구현한 클래스까지 의존하는 상황이다

또한 OCP의 경우 기능을 확장(정액 할인 정책→ 정률 할인 정책)하는 경우 코드의 변경이 발생된다

//할인정책에따른 주문서비스
public class OrderServiceImpl implements  OrderService{

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

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

이러한 문제를 해결하기 위해서 인터페이스만 의존하도록 변경해주면 된다

//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
//위의 두 줄 코드를 아래코드로 변경해주면된다 
private final DiscountPolicy discountPolicy;

/*
하지만 해당 코드로 변경할 경우 NullPointException 예외가 발생되는데, 
이유는 OrderServiceImpl 클래스에서 discountPolicy에 할당한 값이 없기때문이다
그렇다면 DIP도 지키고 OCP의 원칙도 지키려면 어떻게 해야할까?
그건 설정클래스를 별도로 생성해주면된다. 그리고 외부 클래스의 의존관계는 생성자를 통하여 
주입해주면된다
*/

public class AppConfig {
    
   private MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    private FixDiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}
/*
해당 클래스처럼 설정 정보 클래스를 생성해주어, 
해당 클래스에서만 변경사항을 변경해주기만 하면되고, 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;
    }

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

/*
이렇게 관심사를 분리하여 설계할 경우 어디에도 구현 클래스를 의존하고 있는 내용은 없으며, 
순수하게 인터페이스만 의존하고 있으며, 
또한 생성자를 통해 어떤 구현 객체가 주입될지 OrderServiceImpl은 알 수 없다
오직 외부 설정 클래스에서만 주입이 이루어진다
그러므로 개발자는 비지니스 로직에 집중할 수 있다.
*/

제어의 역전 IoC

프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리 하는 것을 제어의 역전이라 한다

프레임워크 vs 라이브러리

프레임워크가 내가 작성한 코드를 제어하, 대신 실행하면 프레임워크(JUnit)

반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한 다면 라이브러리

 

ex) 스프링으로 변경

@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}
  • @Configuration 어노테이션을 사용하여  스프링 컨테이너에 설정 정보로 사용한다
  • @Bean 어노테이션이 붙은 메서드를 호출해서 반환하는 객체를 스프링 컨테이너에 등록한다
  • 스프링 빈은 @Bean 이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. ( orderService)
public class OrderApp {

    public static void main(String[] args) {
//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();
//        OrderService orderService = appConfig.orderService();

        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        MemberService memberService = ac.getBean("memberService", MemberService.class);
        OrderService orderService = ac.getBean("orderService", OrderService.class);
       }
  }
  • 이전에는 개발자가 필요한 객체를 AppConfig 를 사용해서 직접 조회했지만, 이제부터는 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾아야 한다.( ApplicationContext를 스프링 컨테이너라고 한다)
  • 스프링 빈은 applicationContext.getBean() 메서드를 사용해서 찾을 수 있다.

 

 

참조

김영한님 스프링 핵심원리
728x90
반응형

'Spring' 카테고리의 다른 글

컴포넌트 스캔  (0) 2022.07.30
싱글톤 컨테이너  (0) 2022.07.29
스프링 컨테이너와 스프링 빈  (0) 2022.07.25
@Transactional 알고 쓰자  (0) 2022.07.24
스프링 빈 조회  (0) 2022.07.20
Comments