programming study/B-Spring

spring 기본 요약 (사용 이유, 객체지향, IOC/DI/container, 빈 등록, 의존관계 주입, 스코프 등)

gu9gu 2022. 11. 2. 15:53

배경

레거시 소스를 파악하거나 프로젝트를 새로 만들 떄 spring에 대한 개념이 부족하면 어려움이 있다고 판단해서 spring에 대해서 공부하기로 하였습니다. 아래는 inflearn에서 김영한 spring 강의를 듣고 추가로 검색하면서 정리한 내용입니다.

 

 

목록

[이론]
스프링은 여러 뜻으로 사용됩니다.
스프링을 사용하는 이유는?
객체지향이란?
객체지향에서 다형성이란?
객체 지향 설계의 5가지 원칙 ( SOLID )
IoC, DI 그리고 컨테이너
싱글톤 패턴
컴포넌트 스캔과 의존관계 자동 주입
자동 빈 등록, 수동 빈 등록의 올바른 실무 운영 기준
의존관계 옵션처리
주입 대상이 되는 빈이 2개 이상이라면?
주입 대상이 되는 빈들을 모두 받고 싶을 때  (List, map)
빈 생명주기에 따른 콜백 기능
빈 스코프

[스프링 실행]
스프링 프로젝트 생성 방법
스프링 실행
Lombok 사용
어노테이션 직접 만들기

 

 

[이론]

스프링은 여러 뜻으로 사용됩니다.

  • 스프링 DI 컨테이너 기술
  • 스프링 프레임워크
  • 스프링 프레임워크, 스프링부트 등을 모두 포함한 뜻
  • 스프링 프레임워크, 스프링부트, 스프링 세션, 스프링 배치 등등의 기능이 있습니다.

스프링을 사용하는 이유는?

스프링은 객채 지향 어플리케이션을 쉽게 개발하려고 만든 프레임워크입니다.

 

객체지향 프로그래밍(Object Oriented Programming)이란?

객체지향 프로그래밍은 프로그램의 데이터를 객체로 만들고 객체의 상호작용을 통해 어플리케이션이 동작하도록 로직을 구성하는 프로그래밍 방법입니다.

객체란 프로그래밍에 필요한 데이터를 동작과 속성으로 구분하여 추상화 시킨 것입니다.

특징으로는 슐화, , 상화형성이  있습니다. 상속을 통해서 코드의 재사용을 줄일 수 있고 다형성을 통해서 코드를 유연하게 할 수 있고 캡슐화를 통해서 요구사항이 변경됐을 때 변경에 용이하게 할 수 있습니다. 추상화를 통해 코드 파악을 쉽게 할 수 있께 할 수 있습니다.

 

상속

상위 클래스의 코드를 하위 클래스가 이어받아 사용할 수 있게 하는 것입니다. 하위 클래스에서 코드를 재사용하기 때문에 코드의 중복을 줄일 수 있습니다. 하위 클래스는 메서드를 재정의 해서 일부 기능을 수정할 수 있습니다. 이렇게 재정의 하는 것을 오버라이딩이라고 칭합니다. 클래스는 다중 상속이 불가합니다. 

 

 

다형성

같은 모양의 코드가 다른 행위를 하는 성질입니다. 또한 하나의 타입에 여러 객체를 대입할 수 있는 성질입니다.

이것을 구현하기 위해서  추상화,상속, 구현, 오버로딩, 오버라이딩을 사용합니다.

  • 추상화, 상속, 구현
    여러 객체들 중 공통된 것을 추상화 하여 타입으로 만들고 그것을 상속 또는 구현하면 한 타입에 여러 객체를 대입할 수 있습니다.
  • 오버로딩(overloading) 
    같은 클래스 내에서 메서드를 중복해서 사용하는 것입니다. 메서드의 이름은 같고 매개변수의 시그니처( 타입이나 개수나 순서)를 다르게 해야합니다. 매개변수 시그니처가 다르다면 메서드의 반환타입은 같아도 달라도 됩니다. 같은 기능을 하지만 매개변수만 다른 경우에 메서드 이름을 같게 할 수 있어서 유용합니다.
  • 오버라이딩(overriding)
    부모클래스의 메서드를 재정의 하는 것입니다. 메서드의 반환타입,이름,매개변수 모두 같아야 합니다. 예외적으로 반환 타입입은 부모 클래스의 메서드 반환타입에 대입 가능하면 되고 부모 클래스의 메소드보다 접근 제어자는 더 넓게, 예외 범위는 더 좁게 선언 가능합니다.(private -> public,  Exception->NullPointException )

캡슐화

 관련있는 속성과 기능을 묶고 처리 로직을 내부에 두고 접근제어자를 통해 은닉하는 것을 말합니다.

장점

1 처리로직이 내부 코드만으로 동작하게 해서 요구사항이 변경됐을 때 외부의 영향을 받지 않고 수정할 수 있으며

2 (처리로직이 내부 코드만으로 동작하게 해서) 객체 내부의 조작 방법(처리 로직)이 바뀌어도 사용방법은 바뀌지 않는 것도 장점입니다.

3 처리 로직을 한 군데서 관리함으로써 일관성을 유지할 수 있고 변경에 용이합니다.

4 또한 은닉을 통해  외부에서 객체 속성에 직접적인 접근이 불가하므로 객체의 정보 손상과 오용을 막을 수 있다는 것도 장점입니다.

 

처리하는 로직 자체를 반복해서 사용한다면 코드 수정이 필요할 때 처리 로직을 일일이 찾아서 수정해야 하는데, 처리 로직을 객체 내부에 메서드로 만들어두고 메서드를 호출해서 사용하게 하면 코드 수정이 필요할 때 일부분만 간편하게 수정할 수 있습니다.  결국에 캡슐화의 조건은 속성과 기능을 잘 묶 어서 객체를 만드는 것, 내부 처리 로직을 잘 만들어서 외부에서 호출하기 쉽게 하는 것이라고 할 수 있습니다.

 

 

추상화

 공통적인 속성과 기능을 뽑아서 정의하는 것 입니다. 주로 인터페이스나 추상클래스로 구현합니다. 추상화의 목적은 코드를 간결하고 유연하게 사용하고 재사용성을 높이는 것입니다. 또한 상위 개념만으로도 도메인의 중요한 개념을 설명할 수 있게 하는 것입니다. 

 

예를 들어서 고양이와 개가 객체의 공통적인 건 동물인 것과 걸을 수 있다는 것입니다. 동물 인터페이스에 걷기 메서드를  둔 다음  동물 타입에 개나 고양이 인스턴스를 대입해서 동물의 걷기 메서드를 사용합니다. 이렇게 하면 코드를 간결하게 사용할 수 있고 재사용성을 높일 수 있고 상위 개념만으로도 도메인의 중요한 개념을 설명할 수 있습니다.

 

참고

상호작용: 서로 연관되어 작용하고 영향을 끼치는 것

추상화: 구체적인 걸 숨기고 전체적인 특성을 나타내는 것

도메인 : 해결하고자 하는 문제의 영역
https://radait.tistory.com/5

http://www.tcpschool.com/java/java_usingMethod_overloading

https://brunch.co.kr/@kd4/4

https://jeong-pro.tistory.com/95

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=knix008&logNo=220700047637 

https://taste-and-investment.tistory.com/entry/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%96%B8%EC%96%B4%EC%9D%98-%EC%9E%A5%EC%A0%90%EA%B3%BC-%EB%8B%A8%EC%A0%90-%EA%B0%9C%EB%85%90-%EC%A2%85%EB%A5%98-%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5-%EC%96%B8%EC%96%B4%EC%99%80-%EB%B9%84%EA%B5%90

https://mangkyu.tistory.com/194 

추상화 : https://velog.io/@ha0kim/2020-12-21

객체 지향 설계의 5가지 원칙 ( SOLID )

1) 단일 책임 원칙 (SRP : single resposibility priciple)

 - 한 클래스는 하나의 책임만을 가져야 한다는 원칙입니다. 변경했을 때 영향도가 적게하는 원칙입니다.

 

2) 개방 폐쇠 원칙 (OCP : Open-Close priciple)

 - 확장에는 열려있어야 하고 변경에는 닫혀있어야 한다는 원칙, 즉 기존 코드를 변경하지 않으면서 기능을 추가할 수 있게 설계해야 한다는 원칙입니다.

ex) "db클래스"에 mysql_save, oracle_save를 넣으면 mongoDB저장기능을 넣고 싶을 때 "db클래스"를 변경해야 해서 OCP 위반입니다. db인터페이스에 save 메서드 두고 mysql, oracle 클래스 만들어서 구현하면 기존 클래스 변경 없이 다른 종류의 저장기능을 추가할 수 있습니다.

 

3) 리스코프 치환 원칙 (LSP : Liskov Subsituation Principle)

- 상위 객체를 하위 객체로 치환 해도 의도한대로 작동해야 하며 문제가 없어야 한다는 원칙입니다.

ex) 필통 클래스를 연필 클래스가 상속 받았을 떄 연필 클래스가 지퍼를 여는 행위를 하는 것은 논리적으로 문제가 되어 LSP 위반입니다.

 

4) 인터페이스 분리 원칙(ISP : Interface Segregation Principle)

- 클라이언트는 사용하지 않는 인터페이스에 강제로 의존해서는 안된다(로버트 C.마틴) : 클라이언트는 사용하지 않는 메서드에 의존하지 않아야 한다는 원칙입니다.

- 범용성 인터페이스를 분리시켜서 인터페이스에 클라이언트가 사용할 메서드만 포함시켜야 한다는 원칙입니다.

ex) Vehicle에 go, fly를 넣으면 car 클래스는 fly를 구현해야 해서 ISP 위반입니다.

Vehicle에 go, Flyalbe에 fly를 넣으면 car 클래스는 fly를 구현하지 않아도 됩니다.

 

5) 의존관계 역전 원칙(DIP: Dependency Inversion Priciple)

- 구현 클래스가 아니라 추상화한 인터페이스에 의존해야 한다는 원칙

 


IoC, DI 그리고 컨테이너

제어의 역전 IoC( Inversion of Control)

프로그램의 제어 흐름을 직접하지 않고 외부에서 제어 하는 것입니다. 즉 객체를 생성하고 연결하는 것들을 직접 하지 않고 외부에서 하는 것입니다.

 

의존관계 주입 DI( Dependency Injection)

애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 의존관계가 연결 되는 것.

의존관계 주입을 사용하면 클라이언트 코드는 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.

 

스프링 컨테이너

ApplicationContext를 스프링 컨테이너라고 한다.

@Configuration이 붙은 클래스 내부에 있는 @Bean이 붙은 메서드에서 반환된 객체를 컨테이너에 등록합니다.

이 때 등록된 객체를 스프링 빈이라 하고 스프링 빈은 기본적으로 싱글톤으로 생성됩니다.

( 설정 클래스에서 @Configuration을 붙이지 않아도 내부 메서드를 사용할 수는 있지만 직접 new로 생성한 객체이기 때문에 싱글톤이 아니다.)

 


싱글톤 패턴

  • 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
  • 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤으로 등록할 클래스에 특정 클라이언트가 변경할 수 있는 필드가 있으면 안된다.
  • 아래는 price를 변경할 수 있는 잘못 설계된 클래스이다.
public class StatefulWrongExService {

    private int price; // 변경 가능한 필드

    public void order(String name, int price) {
        System.out.println("name = " + name + " price = " + price);
        this.price = price; // 문제 되는 코드 : price가 변경됨
    }

    public int getPrice() {
        return price;
    }
}

 


컴포넌트 스캔과 의존관계 자동 주입

@ComponentScan

  •  @Compnent, @Service, @Repository, @Controller, @Configuration이 붙은 class를 스캔하여 자동으로 스프링 빈으로 등록해주는 역할을 합니다.
  •  basePackages로 스캔 대상 package 설정. 설정하지 않으면 @ComponentScan이 붙은 클래스가 위치하고 있는 패키지를 포함해서 하위 패키지가 대상이 된다. 관례상 basePackage를 붙이지 않고 프로젝트 최상단에 ComponentScan 설정 클래스를 둔다.
  • @SpringBootApplication은 @ComponentScan을 포함하고 있다. 따라서 @SpringBootApplication 붙은 클래스가 위치한 패키지와 그 하위 패키지가 컴포넌트 스캔 대상이 된다. 

@SpringBootApplication

  • @SpringBootApplication가 붙은 클래스는 스프링부트 프로젝트의 시작 하는 곳입니다.
  • @SpringBootApplication은 @SpringBootConfiguration,@ComponentScan,@EnableAutoConfiguration 을 포함하고 있다.
    스프링 부트 어플리케이션은 Bean을 2번 등록한다.
    처음에 @ComponentScan으로 빈을 등록하고 그 후에 @EnableAutoConfiguration 으로 추가적인 빈들을 읽어서 등록한다.
  •  @SpringBootConfiguration은 @Configuration을 포함하고 있다. 따라서 스프링 빈 등록시 싱글톤을 보장한다.
  • @SpringBootConfiguration vs @Configuration : @SpringBootConfiguration 한 서버에서 1개만 사용 가능하다.
    @Configuration vs @EnableAutoConfiguration : @EnableAutoConfiguration은 spring.factories안의 자동설정에 따라 빈을 생성한다. @EnableAutoConfiguration@Configuration과 같이 사용해야 한다.

@EnableAutoConfiguration

AutoConfiguration은 결국 Configuration이다. 즉, Bean을 등록하는 자바 설정 파일이다.

spring.factories 내부에 여러 Configuration 들이 있고, 조건에 따라 Bean을 등록한다. 따라서 메인 클래스(@SpringBootApplication)를 실행하면, @EnableAutoConfiguration에 의해 spring.factories 안에 들어있는 수많은 자동 설정들이 조건에 따라 적용이 되어 수 많은 Bean들이 생성되고, 스프링 부트 어플리케이션이 실행되는 것이다.

 

 

 

빈으로 등록할 class에 아래 어노테이션을 붙인다.

  • @Component : 컴포넌트 스캔에서 사용
  • @Controlller : 스프링 MVC 컨트롤러에서 사용
  • @Service : 스프링 비즈니스 로직에서 사용
  • @Repository : 스프링 데이터 접근 계층에서 사용
  • @Configuration : 스프링 설정 정보에서 사용(스프링 빈 등록시 싱글톤을 보장)

@Autowired

 의존관계를 자동으로 주입 받는다.

  • 생성자 의존관계 주입
    • 생성자가 1개인 경우 @Autowired 생략 가능
    • 생성자 호출 시점에 딱 1번만 호출됨
    • 필드가 변하지 않음, 생성자이기때문에 필수로 호출됨, 필드에 final을 붙여줌
  • setter 의존관계 주입
    • 선택 가능하다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정
    • 계속해서 호출 하여 변경 가능하다. 필드에 final을 안 붙임
  • 필드 의존관계 주입
    • 테스트 코드에서 사용 못 해서 잘 사용되지 않는다.
    • 테스트 코드에서 직접 new로 생성 하면 필드에 객체가 주입되지 않아서 내부 코드에서 그 필드를 사용하는 경우 문제가 된다. 
    • 필드에 final을 안 붙임
  • 일반 의존관계 주입
    • setter 의존관계 주입과 같은 타이밍에 의존관계가 주입된다고 보면 되는데, 잘 사용되지 않는다.
    • 필드에 final을 안 붙임
  • 참고
    기본적으로 스프링 빈들이 먼저 등록 되고 의존관계가 주입된다.
    생성자 주입은 스프링 빈이 등록되면서 생성자도 호출하기 때문에 의존관계 주입도 바로 된다.
    setter 주입은 스프링 빈이 등록되고 나중에 의존관계 주입이 된다.
    스프링 빈이 아닌 클래스에서 @Autowired는 적용되지 않는다.

 

  • 생성자 의존관계를 사용해야 하는 이유는 뭘까?
    • 생성자 의존관계를 사용하면 Test 코드에서 new로 직접 생성할 때 필요한 의존관계가 생성자에 바로 보이기 때문에 좋다.
    • 클래스에서 생성자로 의존관계설정을 하는 경우 필드에 final을 붙일 수 있다. final을 붙이면 의존관계 설정 코드 누락을 하면 컴파일 오류로 알려준다.   수정자(setter) 주입 방식을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final을 붙일 수 없다.
    • 기본으로 생성자 의존관계 주입을 사용하고 필수 값이 아닌 경우 수정자 주입 방식을 옵션으로 사용하면 좋다. 필드주입 방식은 간단하게 쓸 수 있다는 거 말고는 장점이 없다.

 

자동 빈 등록, 수동 빈 등록의 올바른 실무 운영 기준

  • 자동 빈 등록
    @Component,@Service, @Controller, @Repository 붙여준 클래스를 컴포넌트 스캔해서 빈으로 등록하는 것. 간편하며 OCP, DIP가 지켜진다. 스프링부트는 컴포넌트 스캔을 기본으로 한다. 빈 이름은 소문자로 시작하는 클래스 이름

    자동 빈 등록 사용하면 좋은 경우
    • 비지니스 로직은 양이 많아서 수동 빈 등록하기 번거럽고 한번 개발 되면 일정한 패턴으로 개발하기 때문에 문제 파악이 쉽기 때문에 자동 빈 등록을 사용한다.
  • 수동 빈 등록
    설정 클래스에 메서드를 작성하고 @Bean 설정해서 스프링 빈으르 등록하는 것. ( 설정 클래스에 싱글톤을 위해 @Configuration을 붙여준다. 설정 클래스에는 메서드를 작성해주는데, 메서드는 스프링 빈으로 생성할 객체 리턴해주고  @Bean을 붙여준다.) 빈 이름은 소문자로 시작하는 메서드 이름 또는 @Bean(name="이름")으로 빈 이름 등록

    수동 빈 등록을 사용하면 좋은 경우
    • 기술적인 로직(Transaction 설정, 공통 AOP 처리, DB연결, 공통 Log 처리, 외부 라이브러리 연동 등등)은 수동 빈 등록을 사용. 기술적인 로직은 어플리케이션 전체에 영향을 미치기 때문에 문제 파악이 어렵고 양이 적기 때문에 한 파일에 모아서 수동 빈 등록하여 관리하기 쉽게 하는 것이 좋습니다.
    • 같은 이름으로 여러개의 빈을 등록할 때는 수동 빈 등록을 사용합니다.
      같은 이름의 빈은 한개의 설정 파일에 모아서 @Bean으로 수동 등록하는 것이 한 눈에 확인 할 수 있어서 좋습니다. 자등 빈 등록을 사용한다면 해당 빈 이름으로 흩어져있는 빈들을 찾아봐야 해서 불편합니다. 적어도 자동 빈 등록을 사용하려면 하나의 패키지에 생성해두는게 파악하기 쉽습니다.
        (같은 이름으로 여러개의 빈을 등록해서 의존 관계 주입하고 싶으면 List,Map을 사용합니다.)

의존관계 옵션처리

의존관계 옵션 처리를 사용하는 이유는?

의존관계 설정 코드에서 주입할 게 없는 경우에도 에러를 발생시키지 않고 run이 정상적으로 되도록 하게 하기 위해서 사용합니다.

옵션처리 3가지 방법

required, @Nullable, Option<> 

    static class TestBean {

        // required가 true인데 member bean이 없으면 setNoBean1 스캔하다가 에러를 발생시킨다. 
        @Autowired(required = false) // true가 기본값
        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);
        }
    }

결과) Member 빈이 없을 때

  • noBean1은 실행되지 않는다.
  • noBean2은 null
  • noBean3은 Optional.Empty

 

주입 대상이 되는 빈이 2개 이상이라면?  

  • UnUniqueBeanDefinitionException 발생
    동일한 타입으로 검색되는 빈이 2개 이상일 때 run 하면 컴포넌트 스캔 된 빈 중 해당 타입(ex)DiscountPolicy ) 또는 하위타입으로 검색되는 빈이 2개여서 UnUniqueBeanDefinitionException 발생
  •  3가지 해결 방법!
    • @Autowired 필드 명 매칭 :  의존관계를 주입 받는 필드명(필드주입-필드명, 생성자주입-파라미터 필드명, 수정(setter)주입-파라미터 필드명)을 스프링 빈 이름과 매칭시킨다.
    • @Qualifier 사용 : '스캔 대상이 되는 클래스', '주입 받는 필드'에 @Qualifier("빈이름")을 설정한다.
    • @Primary 사용 : 우선으로 주입 시키고 싶은 대상 클래스에 @Primary 를 설정한다.
  • 의존관계 주입 되는 우선 순위!
    타입 > 필드명 매칭 > @Qualifier("빈 이름") > @Primary
     : 타입이 겹치면 필드명을 보고 필드명 매칭이 안되면 @Qualifier 확인 그 다음 매칭 되는 게 없으면 @Primary
  • 팁!
    겹치는 타입이 있으면 주로 주입시키는 클래스에 @Primary 사용하다가 가끔 사용되는 클래스에는 @Qualifier 사용

 

주입 대상이 되는 빈들을 모두 받고 싶을 때  (List, map)

  • 조회되는 여러개의 빈을 모두 의존관계 주입 하여 사용하고 싶다면 List 또는 Map을 사용합니다.
  • 스프링 빈이 같은 이름으로 여러개 있는데, List 또는 Map을 사용하지 않고 클래스 자료형으로만 의존관계를 주입 받는다면 빈 중복으로 NoUniqueBeanDefinitionException 가 발생합니다.
  • List<클래스>를 사용하면 클래스 타입으로 조회되는 모든 스프링빈을 List 형태로 받습니다. 또는 Map<String, 클래스> 를 사용하면 클래스 타입으로 조회되는 모든 스프링 빈을 Map 형태로 받습니다. Map 의 key 에 스프링 빈 이름이 들어가고 Map 의 value 에는 스프링 빈이 들어갑니다.

빈 생명주기에 따른 콜백 기능

: 초기화/소멸 콜백 함수로 @PostConstruct, @PreDestroy 를 주로 사용하면 좋고
외부 라이브러리 사용시에는 @Bean(initMethod="", destroyMethod="")를 사용하면 좋습니다.


- 실행 시점 : 빈 생성과 의존관계 주입이 끝난 후, 빈이 소멸 되기 전

 

- 실행 시점 별 3가지 방법

  • 해당 빈에서 인터페이스의 메서드를 implements
    InitializingBean - afterPropertiesSet, DisposableBean - destoy)
    현 시점에서는 단점이 많아서 사용하지 않는다.
    • 스프링 전용으로만 사용 가능
    • 초기화/소멸 메서드의 이름을 변경 불가
    • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용 불가
  • 빈 등록시 초기화/소멸 메서드를 등록
    @Bean(initMethod="init", destroyMethod="close")
    • init, close와 같이 메서드 이름을 자유롭게 지정 가능
    • 스프링 빈에서는 메서드만 추가되고 스프링에 의존하는 코드를 사용하지 않는다.
    • 수정할 수 없는 외부라이브러리의 초기화/종료 메서드를 지정할 수 있다.
    • destoryMethod 특수한 추론 기능 (inferred) : destroyMethod 를 지정하지 않아도 close, shutdown 함수를 찾아서 실행 시킨다.(외부 라이브러리의 종료메서드 이름이 보통 close, shutdown)
      이 기능을 동작 시키지 않고 싶으면 destoryMethod=""로 설정한다.
  • 해당 빈에서 콜백 기능으로 사용할 메서드에 어노테이션을 붙인다.
    @PostConstruct, @PreDestroy
    • 최신 스프링에서 권항하는 사용 방법이며 간단하게 사용 가능해서 주로 사용
    • javax에서 가져다 쓰는 어노테이션이기 때문에 스프링에 의존하지 않는다.
    • @Bean 을 사용하지 않기 때문에 컴포넌트 스캔 방식과 잘 어율림
    • 외부 라이브러리 사용시 외부 라이브러리의 초기화/소멸 함수를 호출 할 수 없음

빈 스코프

- 빈이 존재할 수 있는 범위, 싱글톤/프로토타입/request/session/application 스코프가 있습니다.

 

  • @scope(value = "singleton") : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프, 중복되지 않고 하나의 빈만 생성됨. 스프링 컨테이너가 컴포넌트스캔 할 때 빈의 init 메서드가 실행되고 스프링 컨터이너에서 종료될 때 destroy메서드 실행됨
  • @scope(value = "prototype") : 빈을 스프링 컨테이너에서 조회할 때마다 새로운 빈을 생성하고 초기화 메서드가 실행됨,스프링 컨테이너는 빈 생성과 의존관계 주입, 초기화 까지만 관여하고 종료에는 관여하지 않는다. 따라서 직접 bean.destroy() 메서드를 실행해서 빈을 소멸시켜야 한다.
    • 스프링컨테이너에서 빈을 조회하는 방식
       - 스프링의 ApplicationContex 전체를 주입받기 때문에 스프링 컨테이너에 종속적인 코드가 되고 단위 테스트가 어려워짐.
      @Autowired
      private ApplicationContext ac;
      ac.getBean(PrototypeBean.class);

    • 스프링 컨테이너에서 빈을 대신 조회해주는 ObjectProvider 을 이용하는 방식
      - 스프링을 의존하긴 하지만 조회해주는 기능과 부주적인 기능만 있어서 가볍게 사용 가능.단위테스트/mock코드만들기 쉬워짐.  ( DL(Dependency Lookup) : 빈을 지정해서 찾는 기능)
      @Autowired
      private ObjectProvider<PrototypeBean> prototypeBeanProvider;
      prototypeBeanProvider.getObject();
    • 자바 표준의 javax.inject.Provider 사용
      build.gradle에 dependency 추가
      dependencies {
         implementation 'javax.inject:javax.inject:1'
      }
      @Autowired
      private Provider<PrototypeBean> provider;
      provider.get();

      사용 팁 : 자바 표준과 스프링이 제공하는 기능 중 특별히 다른 컨테이너를 사용하는 게 아니라면 스프링이 제공하는 기능을 사용하는 것이 편리하다. ObjectProvider 를 사용하는 것이 편리함. 그리도 애초에 직접 코딩할 떄는 singleton만 주로 사용됨. prototype은 라이브러리 내부 소스에서 사용되는 경우가 있음.
  • @Scope(value="request")
    - http 요청이 들어오고 나가는 동안 유지되는 스코프이며, 각 요청별로 중복되지 않는 인스턴스가 생성됩니다.
    - request 스코프 빈을 필드주입 방식 사용의 문제점
     request 스코프 빈을 필드주입 방식으로 자동주입 하면 스프링 런 시점에 에러가 발생합니다.
     request 스코프 빈은 http 요청시에 만들어지므로 스프링 런 시점에는 생성되지 않은 상태다. 그런데 주입을 하려니까 에러가 발생하는 것이다.
    • 해결법1. ObjectProvider 사용
      ObjectProvider 의 getObject() 호출 시점까지 빈 사용을 지연할 수 있다. http 요청이 오면 빈이 생성되기 떄문에 getObject() 를 호출 하는 시점에는 이미 빈이 생성된 상태여서 에러 없이 정상 동작하게 된다.
@Controller
@RequiredArgsConstructor
public class LogDemoController {
    private final ObjectProvider<MyLogger> myLoggerObjectProvider;
    
    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerObjectProvider.getObject();
        myLogger.setRequestURL(requestURL);
        ...
    }
    • 해결법2. 프록시(proxyMode) 사용
      @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
      http 요청과 상관 없이 CGLIB 라이브러리로 내 클래스를 상속 받은 가짜 프록시 클래스를 만들어두고 이를 다른 빈에 미리 주입해둘 수 있다. 가짜 프록시 객체는 요청이 오면 그 때 내부에서 진짜 빈을 요청하는 위임 로직을 실행시킨다.
    • 팁!
      Provider, 프록시는 모두 진짜 객체 조회를 꼭 필요한 시점까지 지연처리하기 때문에 request 스코프와 같이 사용되는 것이다. 프록시를 사용하면 실제로는 request 스코프 임에도 싱글톤 빈을 필드 주입 하듯이 사용할 수 있어서 편리하다. 대신 싱글톤 사용방법과 같기 떄문에 유지보수 관점에서 꼭 필요한 곳에만 사용하는 게 좋다.

 

 


[스프링 사용하기]

 

스프링 프로젝트 생성 방법

https://start.spring.io/

옵션( ex) Gradle Project, Java, SNAPSHOT과 M 버전을 제외한 것 중 최신(안정화된 버전임), Group에 hello, Arifact에 core, Jar, 11 ) 을 선택하고  GENERATE 하여 생성된 압축 파일을 풀어서 build.gradle을 import하면 됩니다.

 

스프링 실행

1. coreApplication 파일 우클릭 후 Run

2. 웹을 띄운게 아니라서 로그가 몇 줄 안 뜸

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.4)

2022-09-25 00:16:58.254  INFO 7648 --- [           main] hello.core.CoreApplication               : Starting CoreApplication using Java 11.0.7 on XXXXXXXXXXXXXXXXXXXXXXXXXXX
2022-09-25 00:16:58.258  INFO 7648 --- [           main] hello.core.CoreApplication               : No active profile set, falling back to 1 default profile: "default"
2022-09-25 00:16:59.185  INFO 7648 --- [           main] hello.core.CoreApplication               : Started CoreApplication in 1.651 seconds (JVM running for 2.912)

Process finished with exit code 0

 

수동 빈 등록,자동 빈 등록으로 이름이 충돌 되는 경우 스프링 부트로 시작하면 에러를 발생시킨다.

(@SpringBootApplication 붙은 클래스가 스프링부트 시작 클래스)

  • 수동 빈 등록,자동 빈 등록으로 이름이 충돌 되는 경우 스프링 부트로 시작하면 에러를 발생시킨다.
  • 개발자들의 편의를 위해서 기본값은 에러발생이지만 application.properties에 아래와 같이 설정하면 이름이 출동되는 되더라도 에러를 발생시키지 않고 자동 빈을 오버라이드 하여 수동 빈이 등록된다. 
  • 스프링 부트가 아니라 AnnotationConfigApplicationContext (스프링 컨테이너)를 이용해서 컴포넌트 스캔하는 경우는 에러를 발생시키지 않고 자동 등록 빈은 무시되고 수등 등록한 빈이 등록된다.

* application.properties :

spring.main.allow-bean-definition-overriding=true

* 수동 빈 등록(memoryMemberRepository)

@Configuration
@ComponentScan
public class AutoAppConfig {

    @Bean(name="memoryMemberRepository")
    public MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

*자동 빈 등록(memoryMemberRepository) ( 컴포넌트 스캔에 의해서 자동 빈 등록되는 경우 클래스 이름에서 맨 앞글자를 소문자로 변경해서 등록한다.)

@Component
public class MemoryMemberRepository implements MemberRepository{
  .
  .
}

 

Lombok 사용

Lombok을 사용하는 이유는?

한번 주입한 의존관계는 변경하지 않는 경우가 대부분이다. 그래서 필드에 final을 붙여주면 좋다.

Lombok에서 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 대상으로 생성자를 만들어준다.

생성자가 1개일 때는 @Autowired를 생략할 수 있다.

따라서 final필드 @RequiredArgsConstructor 어노테이션만 있으면 편하게 의존관계 주입을 할 수 있다.

 

참고

  @RequireArgsConstructor 설정으로 만들어진 생성자는 ctrl + f12에서 확인 할 수 있다.  생성된 메서드를 확인

 

Lombok 설정 방법

 1. build.gradle에 라이브러리, 환경 추가

plugins {
	id 'org.springframework.boot' version '2.7.4'
	id 'io.spring.dependency-management' version '1.0.14.RELEASE'
	id 'java'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

//lombok 설정 추가 시작
configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}
//lombok 설정 추가 끝

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'

	//lombok 라이브러리 추가 시작
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testCompileOnly 'org.projectlombok:lombok'
	testAnnotationProcessor 'org.projectlombok:lombok'
	//lombok 라이브러리 추가 끝

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

2. 인텔리 제이 환경 설정

Preferences Annotation Processors 검색 Enable annotation processing 체크 (재시작

 

3. 확인

테스트 클래스를 만들고 클래스에 @Getter @Setter 설정 가능한지 확인

 

어노테이션 직접 만들기

어노테이션 만드는 팁

Qualifier 어노테이션 파일에서 @target,@Retention,@Inherited,@Documented 를 직접 만든 어노테이션에 붙여서 어노테이션 기능을 할 수 있게 한다.

 

어노테이션 상속

  • 어노테이션의 상속 기능은 자바에서 지원하지 않는다. 하지만 스프링에서 지원하고 있다.
  • 예시: 직접 만든 어노테이션에 @Qualifier("mainDiscountPolicy") 어노테이션을 붙여서 직접 만든 어노테이션을 사용하면 @Qualifier 가 적용된다.

 1. 어노테이션 

package hello.core.annotation;

import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
package hello.core.order;

import hello.core.annotation.MainDiscountPolicy;
import hello.core.discount.DiscountPolicy;
import hello.core.member.MemberRepository;
import org.springframework.stereotype.Component;

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired // 생성자가 1개일 때는 @Autowired 생략 가능하다.
    public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
        .
        .   
    }
}

 

 

2. Qulifier 등록

@Qualifier를 어노테이션에 상속해서 사용하면 좋은점

@Qualifier("mm") 에서 문자열을 매번 작성하다 보면 오타를 내게 될텐데, 컴파일 오류에 잡히지 않기 (인텔리제이에서는 잡아줌)  때문에 런할 때 에러가 발생하고 런 했을 때 발생하는 에러로그를 보고 수정하기가 쉽지 않다.
이와 다르게 어노테이션을 잘 못 작성했을 때는 빨간 줄(컴파일 에러)로 확인할 수 있기때문에 @Qualifier("mm")를 직접 만든 어노테이션에 붙인 다음 직접 만든 어노테이션을 사용하면 에러 발생을 줄일 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

참고)

https://yoongrammer.tistory.com/category/OOP

 

'OOP' 카테고리의 글 목록

새로운 것을 접하고 공유하는 개발 블로그

yoongrammer.tistory.com