Web/Spring

스프링 기초 정리

목차

 

  • 객체지향이란?
  • 좋은 객체지향이란? (객체지향 설계 5가지 원칙)
  • IoC, DI
  • @Component, @ComponentScan
  • 스프링 컨테이너를 통해 Bean을 가져오는 것이 좋은이유
  • 싱글톤
  • @Configuration
  • @ComponentScan
  • @Component
  • @Autowired
  • 다양한 의존관계 주입 방법
  • 옵션처리
  • 수정자주입말고 생성자 주입을 선택하는 이유
  • lombok
  • 조회대상 빈이 2개 이상일때 해결방법 3가지
    • @Autowired 필드 명 매칭
    • @Qualifier 등록
    • @Primary
  • annotation 직접 만들기
  • 조회한 빈이 모두 필요할 때
  • 수동 빈 등록은 언제 사용해야 될까?
    • 업무로직 빈
    • 기술지원 빈
    • 다형성을 적극 활용할 때
  • 빈 생명주기 콜백
  • 스프링 빈의 전체적인 라이프 사이클
  • 사용 전에 초기화 콜백이 있는 이유
  • 종료 전에 소멸전 콜백이 있는 이유
  • 스프링 빈 중요한 점
  • 객체 생성과 초기화를 분리하는 이유
  • 콜백 받기
    • 방법1. 인터페이스로 콜백 받기
    • 방법2. 빈 등록 초기화, 소멸 메소드
    • 방법3. 애노테이션
  • 빈 스코프란?
  • 빈의 다양한 스코프
  • 빈 스코프 정리
  • 싱글톤 빈과 프로토타입 빈을 같이 사용할 때
    • ObjectProvider
    • JSP-330 Provider
  • 프로토타입 빈을 언제 사용함?
  • ObjectProvider 와 JSP-330 Provider 중에 어떤것을 선택하면 좋을까?
  • 웹 스코프
  • 웹 스코프 종류
  • request 스코프 사용
  • 중요한 오류
    • 해결방법 1. ObjectProvider 사용
    • 해결방법 2. 프록시
  • 프록시로 해결하는 방법이 매우 좋은 방법인 이유
  • 프록시 해결의 주의점

 

 

 

 

스프링은 올바르고 편리한 객체지향 설계를 위해 만들어진 프레임워크이다.

 

객체지향이란?

 

객체지향이란?

Spring 공부를 오늘 시작했다! Spring은 '객체지향'을 극한으로 사용하기 위해 도와주는 프레임 워크이다. ( 현재 Spring 배운지 하루차 이기 때문에 내가 모르는 스프링의 기능과 의미가 있을 것 같다

ksabs.tistory.com

 

좋은 객체지향이란?

 

좋은 객체지향이란? (5가지 원칙)

Spring을 제대로 시작하기 전에 좋은 객체지향이 무엇인지 뇌에 때려박고 시작해야할 것 같아서 정리해보았다. 솔직히 Java로 객체지향을 엄격히 지켜 프로그래밍 한 적은 거의 없었다. 혼자서 개

ksabs.tistory.com

 

IoC, Di ?

 

IoC, DI 용어정리

IoC, DI 도 스프링 개발자 오픈톡방이나 스프링 이야기가 나올 때마다 자주 보이던 용어들이었다. 당시에 궁금해서 구글에 검색했을땐 IoC : 제어의 역전 DI : 의존관계 주입 이라고 나오는 결과들

ksabs.tistory.com

 

 

* @Component 와 @ComponentScan

 

IoC와 DI를 지키기 위해서 실제로 어떤것이 사용될 구현체인지 모아놓은 class가 필요했다.

 

그래서 AppConfig.class를 만들어 애플리케이션에서 어떤 구현체를 선택할지 구성정보를 적어놓았다.

 

구성정보를 적기위해 @Configuration 어노테이션을 AppConfig 위에 적어주었다.

 

 

@ComponentScan 을 이용하면 @Configuration에 있는 객체들과 @Component가 붙어있는 객체들은 전부 자동으로 스프링 컨테이너에  빈으로 등록이 된다.

 

 

* 스프링 컨테이너를 통해 Bean을 가져오는 것이 좋은 이유

1. 의존 관계 주입이 자동으로 된다.

2. 싱글톤 컨테이너, 프로토타입 컨테이너 등 사용할 컨테이너의 종류를 선택할 수 있고 자동으로 적용된다.

 

* 싱글톤 : 여러번 객체를 호출하더라도 새로운 객체를 생성하지 않고 스프링컨테이너에서 해당하는 빈을 반환함

* @Configuration : 없으면 싱글톤 보장 x

* @ComponentScan : @Component가 붙어있는 것들을 스프링 컨테이너에 등록

* @Component : 자동으로 bean에 등록될수 있도록 해줌 그런데 이것만 등록하면 의존관계를 모름. 원래는 AppConfig에서 지정해줬었음.

* @Autowired : 그래서 autowired사용하면 해당하는 타입을 자동으로 의존관계를 맺어줌

 

* 다양한 의존관계 주입 방법

- 생성자주입 : 딱 1번만, 불변, 필수일때 꼭 사용하기

- 생성자 1개일때 @Autowired 생략 가능

- 수정자주입 : 변경 가능하게 할때

스프링 컨테이너 생성과정은 생성, 의존관계주입 2가지 존재. 생성자 주입은 생성하면서 동시에 의존관계가 주입됨. 근데 수정자주입에 @Autowired가 붙어있으면 의존관계 주입 시간에 주입됨.

 

* 옵션처리

@Autowired(required = true) : 기본값

@Autowired(required = false) : 자동주입시 bean이 아닐경우에 아예 주입 x

 

 * 수정자주입 말고 생성자 주입을 선택하는 이유

- 자바단위코드를 테스트할때 주입시키는 코드가 복잡함

생성자 주입을 선택하면  만들어지는 시점에 생성자로 원하는 것 주입하면 됨

- final 남겨줄 수 있음

 

* lombok

- @RequiredArgsConstructor : final 붙은 변수들 가지고 생성자 만들어줌

 

* 조회 대상 빈이 2개 이상일때 (FixdiscountPolicy, RatediscountPolicy) 해결방법 3가지

1. @Autowired 필드 명 매칭

parameter나 변수명에 그냥 쓰고싶은 빈 이름을 적어주면 빈이 2개 이상일 때 그 이름으로 매칭한다.

ex) DiscountPolicy discountPolicy 대신에 DiscountPolicy RateDiscountPolicy

 

2. @Qualifier 등록

등록시키고 싶은 빈에 @Qualifier("mainDiscountPolicy) 같이 원하는 이름 붙여주고 사용 시에

DiscountPolicy discountPolicy 대신에 @Qualifier("mainDiscountPolicy) DiscountPolicy RateDiscountPolicy

주의 : bean이름 자체를 바꿔주는 것은 아님!

주의 : 만약 "mainDiscountPolicy"를 못찾으면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾음 (그런데 이런 용도로 사용하지 않는 것이 좋다)

 

3. @Primary : 우선순위 등록하기 ( 3가지 방법 중 많이 사용하는 방법임 )

 

- 꿀팁 : @Primary, @Qualifier 활용할때 main으로 사용하는 것을 @Primary로 등록해놓고, 가끔 사용하는 것을 @Qualifier로 이름을 등록해놓고 사용하면 좋다.

 

- 주의 : @Qualifier 는 이름을 지정하므로 더 자세하다. 그래서 @Primary와 겹칠경우 우선권이 더 높다.

 

* annotation 직접 만들기

이유 : @Qualifier("mainnDiscountPolicy") 이런식으로 할때 n이 2개가 들어간 오타를 컴파일 시간에 체크할 수가 없음.

방법 : @MainDiscountPolicy annotation을 만들고 그 안을 @Qualifier("mainDiscountPolicy")로 만듬

* 조회한 빈이 모두 필요할 때 (List, Map)

- 빈을 List나 Map으로 다 들여오고 동적으로 고를 수 있다. 여기서도 다형성을 사용하여 아래 코드와 같이 사용할 수 있다.

(discountCode 매개변수로 어떤 discount를 사용할지 key를 받아와 map에서 value를 부를 수 있다.

public int discount(Member member, int price, String discountCode) {
    DiscountPolicy discountPolicy = policyMap.get(discountCode);
    return discountPolicy.discount(member, price);
}

 

* 수동 빈 등록은 언제 사용해야 할까?

- 업무 로직 빈 : 비즈니스 요구사항에 따라 추가되거나 변경되는 것들

요구되는 로직이 많기 때문에 컨트롤러, 서비스, 리포지토리 같이 패턴이 비슷한 것들은 -> 자동기능 사용

 

- 기술 지원 빈 : 기술적으로 공통적으로 요구되는 기술들 (아직 뭔지 이해 안감..)

업무로직에 비해 수가 매우 적지만 이 하나가 애플리케이션 전반적으로 다 영향을 미치기 때문에 수동 빈으로 만들어서 명확하게 볼 수 있게 해놓는게 좋음

 

- 다형성을 적극 활용할 때

ex) DiscountPolicy에 FixdiscountPolicy와 RatediscountPolicy 두개가 존재할 때 명확히 어떤것들이 존재하는지 명시하기 위해서

@Configuration
public class DiscountPolicyConfig {

  @Bean
  public DiscountPolicy rateDiscountPolicy() {
    return new RateDiscountPolicy();
  }
  
  @Bean
  public DiscountPolicy fixDiscountPolicy() {
    return new FixDiscountPolicy();
  }
}

이런식으로 따로 설정정보로 만들어서 수동으로 등록하면 어떤 DiscountPolicy 들이 있는지 한 눈에 볼 수 있다.

 

이게 아니더라도 한 pakage에라도 모아놔야 한다.

 

* 빈 생명주기 콜백 시작

애플리케이션 시작시점에 미리 연결해두고 종료시점에 모두 종료해야 하는 작업들이 존재한다.

(데이터베이스 커넥션 풀, 네트워크 소켓 등등)
그래서 스프링을 통해 시작시점시 초기화작업, 종료시점시 종료작업을 진행하는 방법이 필요

 

* 스프링 빈의 전체적인 라이프 사이클

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료

 

* 사용 전에 초기화 콜백이 있는 이유

: 의존관계 주입이 완료된 시점을 개발자에게 알려주기 위해서

 

* 종료 전에 소멸전 콜백이 있는 이유

: 스프링 컨테이너가 종료된다는 것을 개발자에게 알려주기 위해서

 

* 스프링 빈 중요한 점

객체 생성 후에 의존관계 주입이 일어남 (객체 생성시 생성자에 의해 주입되는 것 제외)

중요해서 몇번 반복되는 개념임.

 

* 객체 생성과 초기화(의존관계 주입 포함) 분리하는 이유

초기화 단계는 어쨌든 객체가 "작용"하는 과정임.

객체가 생성될때는 생성만 되고, 작용될 때는 작용만 되어야 "하나의 역할에 충실" 하는 것인데

생성되며 작용하면 섞여 버림.

 

* 콜백 받기

- 방법1. 인터페이스로 콜백 받기 (거의 사용 x)

InitializingBean, DisposableBean 을 implemets로 받고

인터페이스 afterPropertiesSet(), destroy() 안에서 각 콜백시 수행할 명령어들을 작성하면 됨

 

단점

1. 너무 스프링 의존적임. (이름도 정해진걸로 사용해야함)

2. 외부 라이브러리에 적용할 수 없다.

 

- 방법2. 빈 등록 초기화, 소멸 메소드

@Bean 옆에 내가 이름 지정해 만든 초기화, 소멸 메소드( init(), close() )를 추가한다

@Bean(initMethod = "init", destroyMethod = "close")

 

장점

1. 외부 라이브러리에도 적용 가능하다

2. destroyMethod의 default 는 "(inferred)" (뜻은 추론)x로 되어있는데 이것은 "close"나 "shutdown"이라는 이름의 메소드를 자동으로 호출해준다. 이 기능을 사용하지 않으려면 비워두지말고 ""로 해놓아야 한다.

 

 

- 방법3. 애노테이션 (이 방법으로 사용하자)

init() 메소드 위에 @PostConstruct

close() 메소드 위에 @PreDestroy

 

유일한 단점

외부 라이브러리에 적용 불가능 -> @Bean(initMethod = "init", destroyMethod = "close") 이걸 사용하자

 

 

* 빈 스코프란?

빈이 존재할 수 있는 범위

 

* 빈의 다양한 스코프

- 싱글톤 : 기본 스코프. 스프링 컨테이너의 시작~종료

- 프로토타입 : 빈의 생성과 의존관계 주입까지만 관여

- 웹관련 스코프

  request : 웹 요청이 들어오고 나갈때 까지의 스코프

  session : 웹 session이 생성되고 종료될 때 까지의 스코프

  application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프 (?)

 

 

* 정리

싱글톤 빈을 요청하면 스프링 컨테이너는 이미 만들어진 동일한 빈을 계속 반환해준다.

그런데 프로토타입 빈을 요청하면 요청 한 당시에 생성되고 의존관계주입이 되고, 초기화메소드까지 실행 후 반환해준다. 그리고 끝이다. 스프링 컨테이너에서 계속 보관하고 있지 않다.

 

핵심은 프로토타입 빈을 요청하면 (생성,의존관계주입,초기화) 만 된다는 것이다.

= 종료메소드 @PreDestroy 가 호출되지 않는다.

->만약 데이터베이스 커넥션을 사용한다면 스프링에서 관여하지 않으니 직접 닫아주어야 한다.

 

 

* 싱글톤 빈과 프로토타입 빈을 같이 사용할때

상황 : 싱글톤 빈이 생성되면서 프로토 타입 빈을 생성하고 싱글톤 빈의 로직 중에 프로토타입 빈을 사용하는 로직이 있을 때

 

싱글톤 빈에 있는 프로토타입 빈을 이용하는 로직을 사용해도 프로토타입 빈이 계속 생성되는 것이 아님.

싱글톤빈이 생성되면서 프로토타입 빈을 생성했고 싱글톤 빈 안에 살아있기 때문이다.

 

문제 : 프로토타입 빈을 사용하는 의도는 빈을 계속 생성하며 사용하고 싶은 의도인데 의도대로 수행이 안될 수 있음

 

해결 : 

- DL( (Dependency Lookup) : 의존관계 조회 )을 대신해주는 기능 사용 (ObjectProvider or ObjectFactory)

  • 스프링에서 제공
  • ObjectProvider가 ObjectFactory보다 좀 더 많은 기능을 제공

- JSP-330 Provider : 'javax.inject:javax.inject:1' 라이브러리 추가해주기

  • 자바 표준임.
  • '프로토타입 빈 생성해주는 기능'만을 제공함.

* 내 의문점 프로토타입 빈을 언제사용함?

매번 새로운 객체가 필요할때... -> 그게 언젠데?

 

드물게 컨테이너가 오브젝트를 만들고 초기화해줘야 하는 경우가 존재한다.

바로 DI 때문이다.

매번 새롭게 만들어지는 오브젝트가 컨테이너 내의 빈을 사용해야 하는 경우이다. 

 

참고 :  happyer님 블로그

 

1.3-IoC 컨테이너 : 프로토타입과 스코프

IoC 컨테이너 : 프로토타입과 스코프 스프링의 빈은 기본적으로 싱글톤으로 만들어진다. 요청이 있을때마다 매번 애플리케이션 로직을 담은 오브젝트를 새로 만드는 것은 비효율적이기 때문이

happyer16.tistory.com

* ObjectProvider 와 JSP-330 Provider 중에 어떤것을 사용하면 좋을까?

 

1. 만약 코드를 스프링이 아닌 다른 컨테이너에서 사용해야 한다면 : JSP-330 Provider(자바표준)를 사용해야함

2. 스프링은 이 기능(프로토타입 빈을 새로 생성하는 기능)외에도 더 다양하고 편리한 기능을 제공하기 때문에 1번의 이유가 아니라면 스프링에서 제공하는 ObjectProvider를 사용하는것이 좋다.

 

 

 

* 웹 스코프

  • 웹 환경에서만 동작
  • 스코프의 종료시점까지 관리함 -> 종료 메소드 호출

* 웹 스코프 종류

request : http요청하나가 들어오고 나갈 때 까지 유지. http요청마다 별도의 인스턴스 생성

session : http와 동일한 생명주기

application : 서블릿 컨텍스트와 동일한 생명주기

websocket : 웹 소켓과 동일한 생명주기

 

* request 스코프 사용

라이브러리 추가 : implementation 'org.springframework.boot:spring-boot-starter-web'

 

 

* 중요한 오류

- 에러내용 : No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

 

- 발생이유 : request scope는 http 요청 발생시점부터 끝날때까지인데, 지금은 http요청이 들어오지 않아 빈이 생성되지 않는다는 오류이다.

- 해결방법 1 : MyLogger를 hhtp요청 발생 시점에 주입받을 수 있도록 ObjectProvider를 사용한다

private final MyLogger mylogger; -> private final ObjectProvider<MyLogger> myLoggerObjectProvider;

 

- ObjectProvider로 왜 해결이 됨? : ObjectProvider를 사용하면 MyLogger를 직접 주입받는 것이 아니라 MyLogger를 LookUp(대신주입)받을 수 있는 Provider를 받기 때문에 http요청시간에 주입받을 수 있음

 

- 해결방법 2 : 프록시

@Scope(value = "request") -> @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)

proxyMode를 추가해준다

적용대상 (MyLogger)이 클래스 : TARGET_CLASS 선택

적용대상이 인터페이스  : INTERFACES 선택

 

- 프록시로 왜 해결이 됨? : 가짜 프록시 클래스를 미리 주입함. 나중에 실제로 mylogger.logic()를 호출하면 가짜 프록시 개체에서 진짜 MyLogger를 찾아서 진짜 빈을 요청한다.

 

- 프록시 : 미리 요청을 받아 대신 요청을 처리하고 위임하는 역할을 하는 것

 

- 프록시로 해결하는 것이 매우 좋은 방법인 이유

클라이언트 코드를 수정할 필요가 없음.. 이 자체로 엄청나게 좋은 방법임!

 

- 프록시 해결의 주의점 

  • 싱글톤과 동일한것처럼 보이지만 다르게 동작함.
  • 남발하면 안되고 최소한으로 사용해야함 (유지보수가 어렵기 때문에!!)