스프링 빈의 scope는 2가지 종류가 있다.

 

  • Singleton(싱글톤) : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.
  • ProtoType(프로토타입) : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다.

 

싱글톤 스코프는 스프링 빈의 디폴트이고 스프링 컨테이너가 싱글톤으로 관리한다는 것을 예전 포스팅에서 알아보았다.

 

https://programmingrecoding.tistory.com/55?category=846369 

 

싱글톤 패턴과 스프링 컨테이너

싱글톤 패턴이란? - 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다. - 자바가 뜰 때,  SingletonPattern클래스의 static 영역에 new 가 있으니까 자기자신을 생성해서 instance변

programmingrecoding.tistory.com

 

프로토 타입 스코프에 대해서 자세히 알아보자.

 

 

 

 

 

 

프로토 타입 스코프

싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스 스프링 빈을 반환한다. 반면 프로토타입 스코프를 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다. 

스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 생성한 후 클라이언트에게 빈을 반환하고 그 이후엔 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않는다. 프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있다. 그래서 @PreDestory 같은 종료 메서드가 호출되지 않는다. 

 

 

 

PrototypeBeanTest 테스트 코드

 

간단하게 테스트 코드로 프로토타입 스코프 빈의 생성과 소멸을 살펴보자.

31줄의 ProtoTypeBean이라는 빈을 만드는데 @Scope("prototype") 애노테이션을 이용하여 빈으로 등록될 때 프로토타입 스코프 속성을 가지게 해주었다. 생성과 소멸을 보기 위해서 @PostContruct와 @PreDestory 애노테이션을 이용하여 init메서드와 close메서드를 각각 만들어주었다.

 

19번줄에서 스프링 컨테이너를 생성하고 getBean하여 빈을 가지고 와서 2번을 찍어보았다.

 

 

 

 

분명 로그 마지막줄에 Closing~~ 로그가 찍혀서 스프링 컨테이너가 소멸이 되었지만 close메서드는 호출되지 않았다.

 

일반적인 싱글톤 스코프의 스프링 빈 생명주기는 다음과 같다.

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

 

하지만, 프로토타입 스코프의 스프링 빈은 초기화 콜백 후, 스프링 컨테이너에서 관리하지 않기 때문에 소멸전 콜백의 과정은 일어나지 않는 것이다. 만약(거의 일어나지 않겠지만), 프로토타입 스코프인 빈의 소멸전 콜백을 사용해야 한다면 close() 메서드를 직접 호출해서 사용해야 한다.

 

 

또 한, 각각 protoTypeBean1과 protoTypeBean2는 싱글톤처럼 한개의 빈을 공유한 것이 아닌 서로 다른 빈 객체이다. 로그를 보면 마지막에 주소값이 서로 다른것을 확인할 수 있다. 스프링 컨테이너에게 요청할 때마다 새로 생성되는 것이다.

 

 

 

 

 

싱글톤에서 프로토타입 빈을 사용할 때 (문제점)

스프링 컨테이너에 프로토타입 스코프의 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환한다. 그런데, 문제는 싱글톤 빈과 프로토타입 빈을 함께 사용하면 의도한대로 잘 동작하지 않는다..

 

 

김영한님의 스프링 핵심 원리 - 기본편 강의자료에 너무나도 이해가 쉽게 영한님이 그려놓으신 그림이 있어서 이해를 위해 사진을 가져왔다.

출처 : <인프런> 스프링 핵심 원리 기본편 - 김영한 

 

1. clientBean은 의존관계 자동주입을 사용한다. 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청한다.

2. 스프링 컨테이너는 프로토타입 빈을 생성해서 'clientBean'에 반환한다. 프로토 타입 빈의 count필드 값은 0이다. 이제 프로토타입 빈은 스프링 컨테이너의 손을 떠났고 'clientBean'에서 관리한다.(프로토타입 빈을 내부 필드에 보관한다. 정확히는 참조값을 보관한다.)

 

 

 

3. 클라이언트 A는 clientBean을 스프링 컨테이너에 요청하여 받는다. clientBean은 싱글톤이므로 항상 같은 clientBean이 반환된다.

4. 클라이언트 A는 'clientBean.logic()'을 호출한다.

5. clientBean은 prototypeBean의 'addCount()'를 호출해서 프로토타입 빈의 count를 증가한다. count는 1이 된다.

 

 

 

 

 

6. 클라이언트 B는 clientBean을 스프링 컨테이너에 요청하여 받는다. clientBean은 싱글톤이므로 항상 같은 clientBean이 반환된다.

7. clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다. 주입 시점에서 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성된 것이지 사용할 때마다 새로 생성되는 것이 아니다. 

8. 클라이언트 B는 'clientBean.logic()'을 호출한다.

9. clientBean은 prototypeBean의 'addCount()'를 호출해서 프로토타입 빈의 count를 증가한다. count는 2이 된다.

 

 

 

 

위의 그림을 코드로 이해해보자.

 

SingletonBean과 PrototypeBean

 

싱글톤빈의 @Scope 애노테이션은 디폴트 값이기 때문에 생략해주었다. SingletonBean에서 PrototypeBean을 의존관계 자동주입한다.

 

 

 

 

스프링 컨테이너가 만들어지고, SingletonBean의 의존관계 자동주입된 PrototypeBean은 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청한다. 빈의 스코프가 프로토타입이기때문에 우리는 singleton1과 singleton2객체의 logic메서드를 각각 호출한 결과가 같은 값인 1임을 기대했지만 logic1은 1, logic2는 2라는 결과를 얻었다.

 

위에서 설명하였지만, 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다. 주입 시점에서 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성된 것이지 사용할 때마다 새로 생성되는 것이 아니다

 

 

 

우리는 어떻게 기대하는 값을 얻어낼 수 있을까?

 

 

 

 

ObjectFactory, ObjectProvider

지정한 빈을 컨테이너에서 대신 찾아주는 DL(Dependency Lookup)서비스를 제공하는 인터베이스인 ObjectFactory나 ObjectProvider를 사용하면 된다.

 

우리는 외부에서 의존관계를 주입해주는 것을 Dependency Injection이라고 불렀다. 보통 @Autowired 애노테이션을 사용하여 스프링 컨테이너가 생성된 후, 의존관계 자동주입을 할 수 있도록 해주었었다.

Dependency Lookup은 의존관계를 내부에서 주입받는 것이 아니라 직접 필요한 시점에 의존관계를 찾는 것을 말한다. 

 

 

 

 

이와 같이 의존관계 자동주입을 할 PrototypeBean을 ObjectProvider혹은 ObjectFactory인터페이스로 만들고, 실제 이 빈이 사용되는 로직에서 35번째 줄과 같이 getObject메서드를 이용해서 불러오면 된다. 자동으로 의존관계를 주입해주는 것이 아니라 클라이언트가 필요시에 빈을 가져와서 의존관계를 찾았기 때문에 DI가 아니라 DL 인 것이다. 이 때, DL을 해줌으로써 우리는 빈의 생성을 지연할 수 있었다.

 

ObjectFactory는 getObject기능만 있는 단순한 버전이고 ObjectProvider는ObjectFactory상속, 옵션, 스트림처리 등 편의 기능이 추가된 것이다. 둘 다 패키지를 보면 스프링 프레임워크에 의존하고 있다.

 

그래서 스프링 프레임워크에 의존하지 않는 자바표준(JSR-330)을 사용하는 방법이 있다.

Provider<T> 라는 인터페이스 인데 javax.inject 패키지에 있는 인터페이스 이다. 이 인터페이스를 사용하려면 build.gradle에 라이브러리를 추가해주어야 한다. ( implementation 'javax.inject:javax.inject:1' )

get() 메서드 하나로 기능이 매우 단순하며 스프링 프레임워크에 의존하지 않는다는 장점이 있다.

 

 

 

 

 

 

웹 스코프

웹 스코프들은 웹 환경에서만 동작한다. 프로토 타입과는 다르게 스프링이 해당 스코프의 종료시점까지 관리를 해준다. 따라서 종료메서드가 호출이 된다.


 "request" : HTTP 요청하나가 들어오고 나갈때 까지 유지되는 스코프, 각각의 HTTP요청마다 별도의 빈 인스턴스가 생성되고 관리된다.
 "session" : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프, HTTP Session과 동일한 생명주기를 가지는 스코프.
 "application" : 웹 서블릿 컨텍스트(ServletContext)와 동일한 생명주기를 가지는 스코프.
 "websocket" : 웹 소켓과 동일한 생명주기를 가지는 스코프.

 

 

 

이중에서 request 스코프에만 대해서 예제를 통해서 자세히 알아보자. 나머지 스코프도 범위만 다르지 동작하는 메커니즘은 거의 비슷하다.

 

 

 

클라이언트A와 클라이언트B가 각각 다른 HTTP request를 요청하였다. 컨트롤러에서 myLogger를 요청하는데 myLogger는 request 스코프이다. 이때 클라이언트A 전용 빈, 클라이언트B 전용 빈이 각각 생성되게 되는 것이다.

 

 

 

MyLogger

 

로그를 출력하기 위한 MyLogger 클래스이다. @Scope(value = "request") 애노테이션을 지정하여 스코프를 request로 설정하였다. 이제 이 빈은 HTTP요청당 한개씩 생성되고, HTTP요청이 끝나는 시점에 소멸된다. 

@PostContruct 초기화 메서드를 사용하여 빈이 생성되고 자동주입이 끝난 직후에 uuid를 랜덤하게 지정하였다. java.util.UUID의 uuid를 사용하였다. randoUUID().toString()하면 고유한 uuid값을 만들어준다.

 

url은 이 빈이 생성되는 시점에는 알 수 없으므로 setter를 이용해서 외부에서 주입받는다. 

 

 

 

 

MyLoggerService

 

Service이다.

@RequiredArgsContructor 롬복 애노테이션을 이용하여 myLogger를 생성자 파라메터로 자동주입한다.

logic메서드를 만들어서 MyLogger클래스의 log메서드를 호출해준다.

 

 

MyLoggerController

 

Controller이다.

@RequiredArgsContructor 롬복 애노테이션을 이용하여 myLoggerService와 myLogger를 생성자 파라메터로 자동주입한다. HTTP요청이 들어오면 그 URL을 25번줄의 url변수에 담아 myLogger에 set해주고, myLogger의 log메서드와 myLoggerService의 logic메서드를 각각 호출해본다.

 

(참고로 requestURL을 MyLogger에 저장하는 부분은 컨트롤러 보다는 공통처리가 가능한 스프링 인터셉터나 서블릿 필터 같은 곳을 활용하는 것이 좋다)

 

 

 

오류가 발생하였다. 

이유는 스프링 어플리케이션을 실행하는 시점에 싱글톤 빈은 생성하여 주입이 가능하지만, request스코프인 빈은 HTTP요청이 들어와야 생성되기 때문에 아직 생성되지 않았다. 아직 클라이언트의 요청이 들어오지 않았는데 컨트롤러의 저 메서드를 타게 되면 request스코프인 myLogger를 호출하게 되어 오류가 발생하는 것이다. 

 

 

 

 

 

2가지 해결방법에 대해서 알아보자.

 

 

 

 

1. Provider의 활용 (ObjectFactory, ObjectProvider 도 마찬가지)

Provider를 사용한 DL

 

Provider덕분에 myLoggerProvider.get()을 호출하는 시점까지 request scope 빈의 생성을 지연할 수 있다.

myLoggerProvider.get()을 호출하는 시점에는 HTTP요청이 진행중이므로 request scope빈의 생성이 정상 처리된다. 

 

 

 

 

 

 

2.  프록시 방식

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

 

위와 같이 request스코프를 지정해줄 때, proxyMode를 TARGET_CLASS로 지정해주는 방법이 있다.

적용대상이 클래스이면 TARGET_CLASS, 인터페이스이면 TARGET_INTERFACES를 써주면 된다. 

 

이렇게 옵션을 지정하면 MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다. 

스프링 컨테이너는 CGLIB라는 바이트 코드를 조작하는 라이브러리를 사용하여 MyLogger를 상속받은 가짜 프록시 객체를 생성한다. 의존관계 주입에서도 이 가짜 프록시 객체가 주입이 된다.

 

가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다. 가짜프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고 있다. 클라이언트가 myLogger.logic()을 호출하면 사실은 가짜프록시 객체의 메서드를 호출한 것이다. 가짜프록시 객체는 request 스코프의 진짜 myLogger.logic()를 호출한다.
가짜프록시 객체는 원본 클래스를 상속 받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다. (다형성)

 

이 CGLIB라이브러리는 스프링 빈이 자동으로 싱글톤으로 유지될 수 있도록 할 때에도 사용되었었다. (이전 포스팅 참고)

 

 

 

특징 정리

-> 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있다.
사실 Provider를사용하든, 프록시를 사용하든 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다. 단지 애노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다. 이것이 바로 다형성과 DI 컨테이너가 가진 큰 강점이다. 꼭 웹스코프가 아니어도 프록시는사용할수있다. 

 

 

주의점

-> 마치 싱글톤을 사용하는 것 같지만 다르게 동작하기 때문에 결국 주의해서 사용해야 한다. 이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용하자. 무분별하게 사용하면 유지보수 하기 어려워진다.

 

데이터베이스 커넥션 풀이나 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면 객체의 초기화와 종료작업이 필요하다. 

(데이터베이스 커넥션 풀 같은 경우, 미리 애플리케이션 서버가 올라올 때 커넥션을 미리 연결을 해놓는다.)

 

먼저, 스프링 빈의 생명주기를 살펴보자.

 

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

 

 

초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
소멸전 콜백 : 빈이 소멸되기 직전에 호출

 

초기화 콜백과 소멸전 콜백이 구분되어 있기 때문에 필요 시, 콜백 메서드를 통해서 시작 지점과 종료 지점의 처리를 각각 해줄 수 있는 것이다. 

 

 

 

그런데, 

커넥션 풀이나 네트워크 소켓처럼 생성될 때 해야하는 처리라면 객체 생성과 동시에 생성자에서 초기화 처리할 수 있지 않을까?

 

 

객체의 생성과 초기화를 분리하자.

 

생성자는 필수 정보(파라메터)를 받고, 메모리를 할당하여 객체를 생성하는 책임을 가진다. 

반면에 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는 등 무거운 동작을 수행한다.
따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다는 객체를 생성하는 부분과 초기화를 하는 부분을 명확하게 나누는 것이 유지보수의 관점에서도 좋으며 단일책임 원칙(SRP)를 지키는 설계라고 할 수 있다. 물론 초기화 작업이 내부 값들만 약간 변경하는 정도의 간단한 경우에는 생성자에서 한번에 처리하는 것이 나을 수도 있다.

 

 

 

 

 

 

 

빈 생명주기 콜백

 

 

 

BeanLifeCycle

 

빈 생명주기 콜백 방법들을 알아보기 위해 위와 같은 클래스를 만들었다.

 

 

 

 

LifeCycleTest

 

테스트 파일에 LifeCycleConfig를 만들어서 BeanLifeCycle을 빈으로 등록하였다. 그리고 등록하는 빈에 서 setter를 이용해서 url을 넣어주었다. 이렇게 하면 어떻게 나올까?

 

 

 

결과

 

 

등록될 빈인 beanLifeCycle() 에서 BeanLifeCycle bean = new BeanLifeCycle(); 로 생성자를 호출하여 객체를 만든 후에 setUrl을 해주었기 때문에 생성자를 호출했을 당시에는 url이 null 값이라 위와 같은 결과를 얻게 되는 것이다. 

connect와 disconnect를 빈 등록 시점(의존관계 주입 이후)과 빈 소멸 직전 시점에 적절하게 콜백하는 방법에 대해 한개씩 알아보자.

 

 

 

 

 

 

 

 

 

 

1. 인터페이스 InitializingBean, DisposableBean

 

 

BeanLifeCycle에 InitializingBean, DisposableBean 인터페이스를 implements 해준다.

 

 

Override된 메서드들을 재정의한다.

 

 

 

테스트 결과

 

 

 

- 이 인터페이스는 스프링 전용 인터페이스이다. 해당 코드가 스프링 전용 인터페이에 의존하게 된다. 
- 초기화, 소멸 메서드의 이름을 변경할 수 없다. 
- 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.
- 인터페이스를 사용하는 초기화, 종료방법은 스프링 초창기에 나온 방법들로 지금은 거의 사용하지 않는다.

 

 

 

 

 

 

2. 빈 등록 초기화, 소멸 메서드

 

 

 

BeanLifeCycle에 빈 생성 이후 시점과 빈 소멸 직전 시점에 호출될 메서드들을 각각 정의해준다.

 

 

 

@Bean애노테이션을 쓸 때 initMethod와 destroyMethod를 각각 등록해준다.

 

 

테스트 결과

 

- 메서드 이름을 자유롭게 줄 수 있다.
- 스프링 빈이 스프링 코드에 의존하지 않는다.
- 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.

 

 

종료 메서드 추론

- @Bean의 destroyMethod속성에는 아주 특별한 기능이 있다.
- 라이브러리는 대부분 close, shutdown 이라는 이름의 종료 메서드를 사용한다. 
- @Bean의 destroyMethod는기본값이 (inferred)(추론) 으로 등록되어 있다.
- 이 추론 기능은 close, shutdown 라는이름의 메서드를 자동으로 호출해준다. 이름 그대로 종료 메서드를 추론해서 호출 해준다.
- 따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 잘 동작한다. 추론 기능을 사용하기 싫으면destroyMethod="" 처럼 빈 공백을 지정하면 된다.

 

 

 

 

 

 

3. @PostContruct, @PreDestory

 

 

 

BeanLifeCycle에 빈 생성 이후 시점과 빈 소멸 직전 시점에 호출될 메서드를 만들고 그 메서드에 @PostContruct, @PreDestroy 애노테이션을 각각 붙여주면 된다.

 

 

 

테스트 결과

 

 

 

- 패키지를보면 'javax.annotaion.PostContruct'이다. javax는 스프링에 종속된 기술이 아니라 JSR-250이라는 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
- 컴포넌트 스캔과 잘어울린다.
- 유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다. 외부 라이브러리를 초기화, 종료 해야하면 @Bean의 기능을 사용하자 (2번)

다양한 의존관계 주입 방법

1. 생성자 주입

- 생성자 호출시점에 딱 한번만 호출되는 것이 보장이 된다. 한번 세팅하면 그 다음부터는 세팅할 수 없도록 할 수 있다는 것이다.
- 불변, 필수 의존관계에 사용된다.
- 생성자가 딱 1개만 있으면 @Autowired를 생략할수도 있다.

 


2. 수정자 주입 (setter주입)

- setter라고 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.
- 선택, 변경 가능성이 있는 의존관계에 사용된다.
- @Autowired의 기본동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false)로 지정하면 된다.

 


3. 필드 주입

- 필드 주입을 사용하면 외부에서 변경이 불가능해서 테스트하기 힘들다는 단점이 있어 최근에는 사용하지 않는다.
- 애플리케이션의 실제 코드와 관계없는 테스트코드에 사용한다.
- 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용한다.


4. 일반 메서드 주입

- 일반 메서드를 통해서 주입을 받을 수 있다.
- 한꺼번에 여러 필드를 주입 받을 수 있다. 일반적으로는 잘 사용하지 않는다.

 

 

 

 

 

 

 

 

@Autowired 옵션처리

1. @Autowired(required = false)

- 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출이 안된다.

 

 

@Autowired(required = false)의 예시

 

- 위의 setNoBean1 메서드의 파라메터에 있는 Member는 스프링 빈에 등록된 객체가 아니다. 그때 그냥 @Autowired를 한다면 스프링 빈이 아닌 객체를 의존관계 자동주입을 하려고 했으니 오류가 발생한다. 아마 NoSuchBeanDefinitionException이 나올 것이다. required = false 옵션을 넣어주면 호출 자체가 안되기 때문에 Exception이 터지지 않고 아예 print값이 찍히지 않는다.

 

 

 

 

 


2. @Nullable

- 자동 주입할 대상이 없으면 null이 입력된다.

 

@Nullable의 예시

- 스프링 빈으로 등록되지 않은 것을 아는데 찍어주어야 할 상황이 생길 수도 있을 것이다. 위의 setNoBean2처럼 파라메터가 한개라면 그럴일이 없지만 예를 들어 파라메터가 3개인데 마지막 변수는 스프링 빈이 아니지만 테스트를 위해서 null이라도 찍어보고 싶다. 그럴 때 사용할 수 있다.

 

 

- 결과는 이처럼 null로 찍힌다.

 

 

 

 

 

 


3. Optional<>

- 자동 주입할 대상이 없으면 Optional.empty가 입력된다.

 

Optional&amp;amp;lt;&amp;amp;gt;의 예시

- 자바 8 문법인 Optional을 사용할 수도 있다. Optional은 “존재할 수도 있지만 안 할 수도 있는 객체”, 즉, “null이 될 수도 있는 객체”를 감싸고 있는 래퍼 클래스이다. 

 

 

- 결과는 이처럼 Optional.empty로 얻을 수 있다.

 

 

 

 


@Nullable, Optional<>은 스프링 전반에 걸쳐서 지원이 된다. 예를 들어서 생성자 자동 주입에서 마지막 파라메터가 스프링빈에 없는데 생성은 하고 싶다? 그러면 넣을 수도 있는 것이다.

 

 

 

 

 

 

 

 

 생성자 주입을 선택해야 하는 이유

최근에는 스프링을 포함한 DI프레임워크 대부분이 생성자 주입을 권장한다.

 

1. 불변

- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분 의존관계는 애플리케이션 종료전까지 변하면 안된다.
- 수정자 주입을 사용한다고 하면 메서드를 public으로 열어두어야 하고 누군가가 실수로 변경할 수도 있기 때문에 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.

 

2. 누락

- 프레임워크 없이 순수한 자바 코드 단위 테스트 하는 경우가 많은데 수정자 주입인 경우 setter를 통해 한개의 필드라도 값을 넣어주지 않으면 NullPointerException이 발생한다. 
생성자 주입을 사용하면 생성자를 통해서 값을 넣어야하는데 값이 누락되면 컴파일 에러로 자바 컴파일러단에서 해결할 수 있다.  또한, 생성자 주입을 사용하면 final 키워드를 사용할수 있다. 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에서 막아줄 수 있다.

 

 

- 생성자 주입은 프레임 워크에 유지하지 않고 순수한 자바 언어의 특징을 잘 살리는 방법이다.
- 기본으로 생성자 주입을 사용하고, 필수값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다. 생성자 주입과 수정자 주입을 동시에 사용할 수 있다.
- .. 그냥 항상 생성자 주입을 선택하자 !

 

 

 

 

 

 

 Lombok 롬복

 

- @Getter, @Setter

: POJO클래스에 이 어노테이션들은 붙여주면 알아서 게터 세터를 만들어준다.

 


- @ToString

: toString메서드를 Override해서 필드들을 출력해준다. 

클래스명(필드1명=필드1값,필드2명=필드2값,...)  와 같이 출력이 된다.

 


- @RequiredArgsConstructor
: final 키워드가 붙은 필드(Required한)를 파라메터로 하는 생성자를 자동으로 생성해준다.

 

@RequiredArgsConstructor 의 예시

 

 

 



최근에는 생성자 1개를 두고 @Autowired를 생략하는 방법을 주로 사용한다. 여기에 롬복 라이브러리에서 제공하는 @RequiredArgsConstructor 를 함께 사용하면 기능은 다 제공하면서 코드를 깔끔하게 사용할 수 있다.

 

 

 

 

 

 

 

 

조회 빈이 2개 이상일 때

1. @Autowired 필드 명 매칭

- @Autowired는 타입기반으로 DI를 한다. 만약에 타입이 같은 스프링 빈이 여러개 있다면, 필드명을 찾거나 또는 생성자의 파라메터의 변수 이름과 빈의 이름이 같을 경우 그 클래스를 주입해준다.

 

 

 

@Autowired 필드 명 매칭예시

- DiscountPolicy에는 rateDiscountPolicy와 fixDiscountPolicy 2개의 구현체가 있다. 같은 타입을 @Autowired하기 때문에 변수명을 discountPolicy로 하면 오류가 나지만 구현체의 이름과 맞춰서 rateDiscountPolicy로 하니까 오류가 나지 않는다.

 

 

 

 

 

2. @Qulifier -> @Qulifier끼리 매칭 -> 빈 이름 매칭

- @Qulifier는 빈 이름을 변경하는 것이 아니라 추가적인 구분자를 붙여주는 방법이다.
@Qulifier("name")으로 구분해준 빈은 생성자, 수정자, 필드 주입 시 앞에 @Qulifier("name")를 붙여주는 방식으로 빈을 찾아준다.
만약, @Qulifier("name") 에서 name이라는 구분자로 등록된 빈이 없다면, name이라는 이름의 스프링빈을 추가로 찾는다. 
헷갈리지 않아야 하는 것은 빈 이름을 @Qulifier로 바꾸는게 아니라 구분자를 붙여주는 것이고 그것으로 찾지 못했을 때 추가적으로 @Qulifier로 와 같은 이름의 빈을 검색한다는 것이다.

 

 

RateDiscountPolicy

 

RateDiscountPolicy에 @Qualifier("mainPolicy")라고 설정해주었다.

 

 

 

 

@Qualifier 예시

 

조회빈이 2개이상인 파라메터 앞에 @Qualifier("mainPolicy")를 붙여줌으로서 DiscountPolicy는 RateDiscountPolicy가 되었다.

 

 

 

 

 

 


3. @Primary 사용

- 우선순위를 정하는 방법이다. 여러개의 빈이 매칭이 되면 @Primary가 우선권을 가진다.

 



RateDiscountPolicy

 

RateDiscountPolicy에 @Primary 라고 설정해주었다.

 

 

 

@Primary 예시

 

조회 빈이 2개 이상이지만 @Primary 애노테이션 덕분에 오류가 나지 않고 DiscountPolicy의 구현체는 RateDiscountPolicy가 되었다.

 

 

 

 

 

 

 

그런데.

뭔가 이상하지 않은가..

 

 

우리는 지금까지 SOILD 원칙을 최대한 지키면서 객체지향적인 설계와 프로그램을 짜왔다. 

그런데, DiscountPolicy에 두 개의 빈이 찾아지기 때문에 우리는 특정 빈을 찾을 수 있도록 인자의 파라미터 이름을 수정한다던지(@Autowired 필드명 방식), @Qualifier이나 @Primary 애노테이션을 붙이기 위해서 클라이언트 코드를 직접 수정하였다. 이는 개방에는 열려있고 수정에는 닫혀있어야하는 OCP원칙을 어긋났다. 

또 한, DiscountPolicy를 각각의 방법으로 인해 RateDiscountPolicy라는 구체화에 의존하도록 명시하였기 때문에 추상화에 의존해야지 구체화에 의존하면 안된다DIP원칙을 어긋났다.

 

이는 김영한 님의 해당 문제에 대한 답변.

기존 구현 클래스의 애노테이션도 변경하지 않으면 더 좋겠지만, 이 부분까지는 컴포넌트 스캔의 한계입니다. @Bean을 사용하면 확실하게 되지만 약간은 불편하지요. 따라서 둘의 트레이드 오프로 이해하시면 됩니다.

 

 

 

 

 

 


@Primary, @Qualifier 활용

- 코드에서 자주사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈이 있고, 코드에서 특별한 기능으로 가끔 사용하는 서브 데이터베이스의 커넥션을 획득하는 스프링빈이 있다고 생각해보자. 메인 데이터베이스의 커넥션을 획득하는 스프링빈은@Primary를 적용해서 조회하는 곳에서 @Qualifier 지정없이 편리하게 조회하고, 서브 데이터베이스 커넥션 빈을 획득할때는 @Qualifier를 지정해서 명시적으로 획득하는 방식으로 사용하면 코드를 깔끔하게 유지할 수 있다. 물론 이때 메인 데이터베이스의 스프링빈을 등록할때 @Qualifier를 지정해주는 것은 상관없다.

 

@Primary, @Qualifier 우선순위

- 스프링은 항상 더 자세한 것이 우선순위를 가져간다. 자동보다는 수동이, 넓은 범위의 선택권보다는 좁은 범위의 선택권이 우선권이 높다. 따라서 @Qualifier가 우선권이 높다.

 

 

 

 

 

 

 

 조회한 빈이 모두 필요할 때

 

조회한 빈이 모두 필요할 때는 어떻게 각각의 빈들을 모두 조회할 수 있을까? 이는 스프링에서 특별하게 제공하는 Map의 기능을 이용하면 된다. 

 

 

 

DiscountService

 

예를 들어 DiscountService라는 클래스가 있다고 하자. 이는 Map의 key값으로 String(DiscountPolicy의 이름이 될 것이다.), value값으로 DiscountPolicy인터페이스를 받고 있다.

 

이 클래스를 스프링 빈으로 등록하고 생성자 주입을 해주었다.

 

 

 

 

 

 

조금 알아보기 힘들지만, 위에 생성자에서 찍은 map과 list의 결과이다.

map에는 key로 클래스의 이름이 들어있고 value에는 해당 클래스의 객체참조값이 들어있다. DiscountPolicy 라는 빈은 RateDiscountPolicy와 FixDiscountPolicy라는 2개의 빈이 의존하고 있었기 때문에 자동 의존관계가 주입되어 이 2개가 map에는 key, value값으로 list에는 배열로서 나타내 진것을 확인할 수 있었다.

 

 

 

 

DicountService 의 Test

 

위의 DiscountService의 생성자 들이 자동주입이 될 수 있도록 AnnotaionConfigApplicationContext를 사용하여 스프링 빈을 등록할 때 DiscountService와 AutoAppconfig를 등록하였다. AutoAppConfig는 위에 예제들에서 사용한 모든 빈들을 ComponentScan해주는 설정파일이다. 물론 RateDiscountPolicy와 FixDiscountPolicy가 포함되어 있다. 

 

ac.getBean하여 빈을 받아온 후, discount라는 메서드를 먼저 TDD방식으로 만들었다. 이는 member와 price, disCountCode를 넘겨 어떤 할인 정책이냐에 따라 price가 얼마가 반환되는지 테스트하기 위한 메서드 이다.

 

2번의 테스트는 각각 rateDiscountPolicy와 fixDiscountPolicy를 넘겨서 해당 할인정책에 따라 가격이 잘 return이 되는지 확인하였다.

 

 

DicountService에 discount메서드를 추가함

 

discount메서드를 추가하였다. 

map에는 DiscountPolicy 빈을 의존하고 있는 RateDiscountPolicy와 FixDiscountPolicy라는 빈이 들어있기 때문에 get하여 해당 빈을 꺼내고 그 빈에 맞는 discount값을 return해주었다. 

 

 

 

테스트가 정상적으로 통과하였다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'JAVA > Spring' 카테고리의 다른 글

빈 스코프  (0) 2021.12.23
빈 생명주기 콜백  (0) 2021.12.18
컴포넌트 스캔 (@ComponentScan)  (0) 2021.12.17
싱글톤 패턴과 스프링 컨테이너  (0) 2021.12.15
Spring Socket에 대해 알아보자! - 2. WebSocket 만들어보자  (0) 2020.06.23

@ComponentScan

 

- @ComponentScan은 @Component가 붙은 모든 클래스를 빈으로 등록해준다. 이 때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자는 소문자를 사용한다. @ComponentScan("service1") 이런식으로 빈의 이름을 설정해줄 수도 있다.

 

- 수동으로 빈을 등록할 때에는 빈 설정파일을 만들어서 @Configuration을 붙여주고 그 안에 @Bean을 만들어서 빈을 생성해었다. @ComponentScan은 설정파일을 굳이 만들지 않아도 자동으로 @Component 애노테이션이 붙은 클래스를 찾아서 빈으로 등록해주는 역할을 한다. 


- @ComponentScan은 스캔의 용도 뿐만 아니라 다음 애노테이션들이 있으면 부가적인 기능도 수행한다.


   1. @Contorller         : 스프링 MVC의 컨트롤러로 인식
   2. @Repository        : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 스프링 예외로 변환해준다.
   3. @Configuration    : 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가처리를 한다. 

                                (SpringCGLB이용하여 싱글톤 로직)
   4. @Service            : 사실 @Service는 특별한 기능이 있지 않지만, 개발자들이 이 애노테이션을 보면

                                핵심 비즈니스 로직이 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움을 준다.

 

 

 

@Autowired (의존관계 자동주입)


- 생성자에 @Autowired를 지정하면 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입해준다. 
- @Autowired는 타입기반으로 주입을 해주며 생성자에 파라메터가 많아도 다 알아서 찾아서 자동으로 주입해준다.

 

 

 

@ComponentScan 설명 다음에 @Autowired 설명을 한 이유가 있다.

스프링 컨테이너가 동작하는 순서대로 적은 것이다. 스프링 컨테이너는 처음에 생성이 되면 빈을 등록한다. @ComponentScan이 있으면 @Componen 애노테이션이 있는 클래스를 모두 찾아서 빈으로 등록하고, 그 빈들의 의존관계를 주입한다. 의존관계를 @Autowired로 자동주입하게 설정했으면 스프링은 @Autowired를 보고 빈들의 의존관계를 자동으로 설정해준다.

 

 

 

 

 

 

@ComponentScan의 Filter

 

 

- @ComponentScan은 includeFilters와 excludeFilters를 이용해서 스캔대상을 포함하거나 제외할 수 있다.

 

 

@ComponentScan의 Filter

 

 

- basePackages : 탐색할 패키지의 시작 위치를 설정한다. 이 패키지를 포함한 하위 패키지를 모두 탐색한다. 

- baseClasses: 탐색할 클래스의 시작 위치를 설정한다.

- 만약 아무 설정이 없으면 @ComponentScan이 붙은 클래스가 포함된 패키지가 시작위치가 된다.

 

- excludeFilters : ComponentScan에서 제외할 대상을 지정한다. 

- includeFilters : excludeFilters와 사용코드는 똑같다. ComponentScan의 대상을 추가로 지정한다.

 

 

권장하는 방법?

패키지 위치를 지정하지 않고, 설정 정보클래스의 위치를 프로젝트 최상단에 두는 것이다. 최근 스프링부트도 이 방법을 기본으로 제공한다.

 

 

 

 

싱글톤 패턴이란?

- 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.

 

싱글톤 패턴 클래스 예시

 

- 자바가 뜰 때,  SingletonPattern클래스의 static 영역에 new 가 있으니까 자기자신을 생성해서 instance변수에 넣어 놓는다.
- 조회할 때는 getInstance() 메소드를 사용해서 가져올 수 있다.
- private생성자를 이용해서 외부에서 new로 생성하는 것을 막아준다.

 

 

 

 

- 스프링 DI컨테이너는 위와 같은 싱글톤 방식으로 빈을 관리한다. 요청마다 새로운 객체를 생성하는 것이 아니라 이미 만들어진 객체를 공유해서 효율적으로 재사용할 수 있다. 

 

스프링 DI 컨테이너의 Bean 관리

 

 

 

 

 

싱글톤 방식의 주의점

-> 싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다. 무상태(stateless)로 설계해야 한다.

 


- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다 (값을 수정하면 안된다)
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal등을 사용해야 한다.
- 스프링 빈의 필드에 공유값을 설정하면 정말 큰 장애가 발생할 수 있다..

 

 

 

직접 코드를 보면서 이해해보자.

 

Stateful 하게 설계된 클래스

 

이렇게 설계된 클래스가 있다고 가정해보자. order라는 메소드로 이름과 가격을 받는데 여기서 중요한건 private생성자로 int형 price가 선언되어 있음으로 인해서 상태를 유지할 수 있는 필드가 선언되어 있다는 것이다.

 

이 서비스 클래스의 order메소드를 사용해보자

 

아래 static class TestConfig는 빈으로 등록해주기 위해서 그냥 테스트용으로 간단하게 선언한 것이다.

 

StatefulServiceSingleton()의 중간에 보면 분명히 우리는 getBean을 사용하여 statefulService1, statefulService2를 각각 선언해서 member1과 member2가 서로 다른 가격을 주문한것으로 테스트를 하였다. 그런데 결과는...?

 

price1과 price2의 결과

결과는 price1과 price2가 모두 20000원이 나온다.

 

이유는.. statefulService1과 statefulService2는 같은 객체이기 때문이다. 맨윗줄에서 AnnotationConfigApplicationContext(TestConfig.class)를 해줌으로써 스프링 컨테이너가 만들어진다. 스프링 컨테이너는 Bean을 디폴트로 싱글톤객체로 관리하기 때문에 statefulService1과 statefulService2는 같은 객체이다.

 

같은 객체를 2번 가져다가 쓰니까 member2를 order할때 중간에 끼어드는 형태가 되어버린 것이고 결론적으로 price1과 price2는 20000으로 같게 나와버렸다.

 

이 문제를 해결하기 위해서 위에 설명한대로 우리는 스프링 빈을 무상태(stateless)로 설계를 해야한다. 즉, 특정 클라이언트에 의존적인 필드가 있게 설계하면 안된다는 뜻이다.

 

StatefulService클래스를 고쳐보자.

 

수정된 StatefulService

 

order 메소드를 int형으로 수정하고 상태를 유지하는 필드를 사용하는 것이 아니라 메소드 안에서 바로 return을 해주는 식으로 변경하였다. price를 얻기 위해 만들었던 getPrice메소드도 삭제하였다.

 

 

테스트 코드도 이렇게 바꾸어서 찍어보면 우리가 원하는대로 price1과 price2는 각각 member1과 member2의 가격이 나온다.

 

 

 

 

 

 

 


@Configuration 과 @Bean


-> @Bean만 사용해도 스프링 빈으로 등록이 되기는 하지만, 싱글톤이 보장이 되지 않는다. (의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤이 보장이 되지 않는다는 의미)

 

 

-> @Bean으로 등록한 빈은 자바코드로 return new 구현클래스(); 와 같은 방식으로 되어 있어서 여러번 이 객체를 사용하면 매번 다른 객체가 호출되어야 할 것 같지만 이상하게도 싱글톤을 보장한다.. 왜?!

 

 

-> @Configuration과 @Bean을 사용하여 스프링빈을 등록하면 (예를 들어 AppConfig 클래스가 스프링빈으로 등록되었다고 한다면,) 스프링 컨테이너에는 바로 내가만든 AppConfig를 등록하는 것이 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig를 상속받은 임의의 다른 클래스를 만들어버리고 그 다른 클래스를 빈으로 등록한다.
(ex. AppConfig$$CGLIB$$bd23232)

 

 

-> AppConfig@CGLIB는 호출한 빈이 스프링 컨테이너에 등록이 되어있으면 스프링컨테이너에서 찾아서 반환하고 그렇지 않으면 기존 로직을 호출해서 빈을 생성하고 컨테이너에 등록 하는 과정이 코드로 정의가 되어있을 것이다. 이러한 과정에서 등록되어있는 빈은 새로 만드는 것이 아니라 등록되어있는 것을 찾기 때문에 싱글톤이 보장이 되는 것이다.

 

pure한(SockJS를 사용하지 않은 W3C표준 WebSocket) Spring WebSocket을 이용해서 양방통신이 가능한 채팅기능을 구현해보려고 하였으나... 어떤 이유인지 ws프로토콜 연결이 안된다... 추후에 다시 해보고.. 안되면 나중에 Java Socket 공부하면서 다시 해보도록 하겠다

 

SockJS를 사용하면 연결이 된다. (?) 

SockJS를 사용하면 http프로토콜을 이용하니까 되는것 같기도.. spring ws 버전 문제인가.. 

SockJS를 사용해서 브라우저와 서버를 연결하여 채팅기능으로서 활용이 가능한지 테스트를 해보겠다.

이번 예제는 테스트로 진행하고, 다음에 STOMP에서는 프로젝트에 적용할수 있도록 개발할 것이다.

 

 

 

dependency 추가

 

위와 같이 의존성을 추가하였다. SockJS도 맨 아래 추가~

(? 지금보니까 SockJS가 webjar이다.... 어쩐지 잘 안되어서 그냥 CDN을 추가했는데 .. ㅎㅎ;;)

 

 

Spring WebSocket을 이용하기 위해선 Configuration파일과 Handler파일이 필요하다 한개씩 알아보자.

 

WebSocketHandler.java

 

Socket의 Connect와 Disconnect, Message처리를 해주는 핸들러이다. TextWebSocketHandler 메소드를 상속받는다. 내가 사용할것이 단순한 채팅에 필요한 메세지형태이기 때문에 TextWebSocketHandler를 상속받았지만, 이미지와 같은 다른 리소스를 통신으로 받는다고 한다면 BinaryWebSocketHandler를 상속받으면 되겠다.

 

afterConnectionEstablished 는 소켓연결이 완료되었을 때 소환되는 메소드이다! 연결 잘 되었는지 session을 찍어봤다.

afterConnectionClosed 는 소켓연결이 끊겼을 때 소환되는 메소드이다! 이 역시 그냥 찍어봤다.

handleTextMessage 는 TextMessage형태로 메세지를 받아온다. 위에서 해준처리는 여러개의 세션이 연결되었을 때, (브라우저가 여러개 연결되었을때.. 즉 채팅방에 user가 여러명일 때) WebSocketSession단위로 List에 담아놓고, 그 리스트에 담겨있는 session을 for문돌리면서 연결된 모든 세션에게 메세지를 보낸다.

이는 브로드캐스팅 방식이다. 안드로이드와 같은(ios도 마찬가지) 앱에서 공지?와 같은 기능에서 자주 사용된다!!

 

이렇게 하면 현재 채팅방에 있는 모든 사람이 내가 보낸 메세지를 볼 수 있고, 또 그에 대한 답변을 모든 사람이 볼 수 있을 것이다. 다만, 아직 채팅방(RoomNum)이 구현이 안되어있기 때문에 방 별로 채팅이 아니라 연결된 모든 session에게 보내는 상황이다.

 

WebSocketConfig.java

 

웹소켓 설정파일이다. WebSocketConfigurer 인터페이스를 구현한다.

 

registerWebSocketHandlers를 오버라이딩해서 사용해야한다. 이는 사용자의 커스텀 핸들러를 사용하여 endPoint를 지정할 수 있다. setAllsetAllowedOrigins를 astarisk로 지정해주면 모든 브라우저에서 사용가능하다.

withSockJS()를 해줌으로써 SockJS를 사용하여 소켓을 만들것을 설정해주는 것이다.

 

chat.jsp

 

클라이언트가 사용할 채팅하는 페이지이다. 

아래 connect() 함수부터 보자. new SockJS()를 사용하여 아까 핸들러에서 지정해준 endPoint를 사용하여 연결을 해준다.

소켓이 열렸으면 onopen이 실행된다 (로그에 성공메세지 찍기)

 

성공적으로 연결되어 메세지를 보냈다고 치자. 그러면 onmessage에서 메세지를 받을 수 있다. event.data로 찍어봤다.

oncloseonerror. 뭐 그렇다.

 

위에 ready는 뭐냐?

준비상태가 되면 connect함수를 실행시켜서 소켓을 열어준다. 열려있는 상태에서, 클라이언트가 메세지를 입력(input형이며 id가 msg이겠지?) 하여 btnSend 버튼을 누르면? send() 함수를 이용하여 서버로 전송한다!!!

 

그러면 서버에서는 아까 위에서 본 핸들러의 handleTextMessage에서 TextMessage매개변수로 받아지는 것이다.

 

 

 

페이지를 열면 connection이 open된다. 

이때 Response Header를 보면 Connection이 upgrade로 되어 있다. 웹소켓 요청이다.

 

Response Headers

 

 

 

내가 보낸 메세지가 서버를 거쳐서~ 다시 ReceiveMessage로 잘 출력이 되는 모습이다.

 

 

 

브라우저를 한개 더 열어서 확인해보니,

서로 다른 세션이 서로의 메세지가 모두 출력되게 보면서 실시간 채팅이 가능한 모습이다.

 

 

 

 

 

이제, STOMP를 이용해서 조금은 더 가벼운 소켓채팅 기능을 프로젝트에 넣어볼 예정이다.

 

<생각해야 할것들>

1. 채팅방 구현 

2. STOMP의 Topic방식과 Queue방식

3. User개인별 소켓 DisConnect

 

+ webjar로 dependency 받는법. springio참고

'JAVA > Spring' 카테고리의 다른 글

컴포넌트 스캔 (@ComponentScan)  (0) 2021.12.17
싱글톤 패턴과 스프링 컨테이너  (0) 2021.12.15
Spring Socket에 대해 알아보자! - 1. WebSocket  (0) 2020.06.18
Spring WebSocket에 대한 내용 정리  (0) 2020.06.11
MyBatis  (0) 2020.04.21

Spring4.0이상부터 지원하는 Spring Socket을 이용하여 내가 만든 프로젝트에 실시간 채팅이 가능한 메뉴를 추가해보려고 한다. web socket에 대해서 공부해본적이 없어서 여러 자료들을 찾아보았다..

먼저, 3가지의 Spring Socket 종류에 대해서 알아보자.

 

1. WebSocekt

WebSocket은 W3C표준이다. 브라우저와 서버의 소켓통신을 할때 사용한다. WebSocket은 http상에 존재한다. 

프로토콜은 ws를 사용하지만 이 안에서는 http를 충족한다.

WebSocket은 IE10 이상버전부터 지원이 가능하다. 훨씬 이전에 개발된 방식이지만 여러가지 문제로 10버전 이상부터 지원한다고 한다...

 

2. SocketJS

SocketJS는 브라우저의 소켓을 사용한다. sockjs-client library를 사용하며, IE8 이상버전부터 지원이 가능하다. 

 

 

3. STOMP(Streaming Text Oriented Messaging Protocol) : 토픽 구독방식

스트리밍 텍스트 기반의 간단한 스트리밍 메세지 프로토콜로서 topic/queue방식이다. 토픽에 서브토픽을 주어서 채팅방의 room이름으로서 사용할수 있다. 

SockJS의 Sub Protocol으로서 SockJS를 올려놓고 사용한다. Client는 stomp js library를 쓴다. 이는 spring에 종속적인 방식이라고 할 수 있다.

 

 

 

 

일단 기본적인 WebSocket에 대해서 조금더 자세하게 알아보았다.

 

WebSocket은 사용자의 브라우저와 서버 사이의 인터렉티브 통신 세션을 설정할 수 있게 하는 고급(?)기술이다.

웹 소켓은 HTTP 위에서 돌아간다. (Socket over HTTP) 따라서 https://의 소켓은 wss:// 으로 사용할 수 있다.

wss는 https의 SSL인증서로 암호화 된것이다!!

 

WebSocket은 Long Polling 방식이다.. 폴링과 롱폴링에 대해서 조금 알아보았다.

 

Polling방식이란?

폴링방식은 요청을 주기적으로 하여 결과를 확인하는 방식이다. 원래 HTTP의 통신방식에 대해서 생각해보면... 클라이언트가 요청을 서버에게 보내고, 이를 받은 서버는 요청에 대한 응답을 보내고 응답을 받으면 연결을 끊는다.

이는 단방향 통신이다. HTTP로는 양방향 통신을 할 수 없다.

폴링방식은 주기적으로 요청을 주어 결과를 받기 때문에 양방향 통신이 가능하지만 실시간으로 주는것은 불가능하다.

실시간처럼 비슷하게 만들 수는 있지만... 그렇게 하려면 요청간격을 줄여햐하는데 이런 방식이면 서버와 클라이언트 모두에게 부담이 된다. 그래서 폴링 방식을 조금 개선한것이 롱폴링 방식이다.

 

Long Polling방식이란?

기본적으로 클라이언트가 계속적(주기적)으로 요청을 하는것은 기존 폴링방식이랑 비슷하다. 차이점은 폴링방식은 주기적으로 요청을 하였다면 롱폴링은 time out이 될 때까지 그 요청에 대한 응답을 기다린다...

임의적으로 끊기 전까지 항상 연결은 유지가 되어있기 때문에 실시간으로 통신이 가능하게 된다.

 

 

         Client ---------(Handshake)------------>server
        <--------(connection open)------

         <---(Bi-directional messages)--->
        <---(one side closes channel)--->

 

 

클라이언트가 서버에게 Handshake방식으로 요청을 하면 그 요청을 받은 server는 connection open을 한다. 웹 소켓으로 연결이 된 클라이언트와 서버는 양방향 통신(Bi-directional)을 하면서 메세지를 주고 받는다. 

server측에서 연결을 끊으면 client의 연결도 끊긴다. client측에서 연결을 끊으면 해당 session에 해당하는 연결이 끊긴다.

 

왜 요청방식이 Handshake일까? Handshake가 무엇일까?????

 

WebSocket Handshake

HTTP에서 WebSocket으로의 프로토콜 전환을 WebSocket Handshake라고 한다. 브라우저가 HTTP에서 WebSocket으로 프로토콜을 전환하려는 요청을 할 때, Header에 Upgrade속성을 추가하여 보내게 된다.

서버는 Upgrade속성을 통해 프로토콜의 전환 요청을 받고 이에 동의하게 되면 서버와 브라우저는 ws프로토콜을 사용하게 되는 것이다.

 

핸드쉐이크는 나중에 실제 코드에서 어떻게 사용하여 헤더에 속성이 어떻게 담겨져서 보내지는지 직접 알아보도록 하자..

'JAVA > Spring' 카테고리의 다른 글

싱글톤 패턴과 스프링 컨테이너  (0) 2021.12.15
Spring Socket에 대해 알아보자! - 2. WebSocket 만들어보자  (0) 2020.06.23
Spring WebSocket에 대한 내용 정리  (0) 2020.06.11
MyBatis  (0) 2020.04.21
Spring JDBC  (0) 2020.04.20

https://wondongho.tistory.com/28?category=672073

 

Springframework WebSocket 정리 해놓은 사이트들

handler 이용해서 채팅 http://blog.naver.com/PostView.nhn?blogId=scw0531&logNo=221052774287&categoryNo=28&parentCategoryNo=0&viewDate=¤tPage=2&postListTopCurrentPage=&from=postList&userTopListO..

wondongho.tistory.com

출처 Tistory - web(사용자 원동호) : Springframework WebSocket 정리 해놓은 사이트들
https://wondongho.tistory.com/28?category=672073

 

개념부터 열심히 공부해보자!

'JAVA > Spring' 카테고리의 다른 글

Spring Socket에 대해 알아보자! - 2. WebSocket 만들어보자  (0) 2020.06.23
Spring Socket에 대해 알아보자! - 1. WebSocket  (0) 2020.06.18
MyBatis  (0) 2020.04.21
Spring JDBC  (0) 2020.04.20
@AspectJ 어노테이션  (0) 2020.04.17

+ Recent posts