목차
- 객체지향이란?
- 좋은 객체지향이란? (객체지향 설계 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 : 의존관계 주입 이라고 나오는 결과들
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를 찾아서 진짜 빈을 요청한다.
- 프록시 : 미리 요청을 받아 대신 요청을 처리하고 위임하는 역할을 하는 것
- 프록시로 해결하는 것이 매우 좋은 방법인 이유
클라이언트 코드를 수정할 필요가 없음.. 이 자체로 엄청나게 좋은 방법임!
- 프록시 해결의 주의점
- 싱글톤과 동일한것처럼 보이지만 다르게 동작함.
- 남발하면 안되고 최소한으로 사용해야함 (유지보수가 어렵기 때문에!!)
'Web > Spring' 카테고리의 다른 글
[Spring] 스프링의 탄생목적 - SOLID, DI 컨테이너 (1) | 2022.04.28 |
---|---|
[boot+jpa실전 1] 1. 스프링부트 프로젝트 생성 (0) | 2021.01.19 |
웹 스코프 java.lang.IllegalStateException 에러 해결방법 (0) | 2021.01.18 |
IoC, DI 용어정리 (0) | 2020.12.31 |
좋은 객체지향이란? (5가지 원칙) (0) | 2020.12.24 |