* 스프링에서는, 다른 서비스에 원격으로 액세스하기 위해 통신을 해야한다. 이러한 과정에서 몇 가지의 리모팅 기술을 사용한다
1. 스프링 리모팅 개요
리모팅 : 클라이언트 애플리케이션과 서비스 간의 대화. 클라이언트 측에서 기능이 필요하면, 애플리케이션이 그 기능을 제공 가능한 다른 시스템에 접촉을 시도
다른 애플리케이션과 서비스의 통신은, 클라이언트에서 호출하는 원격 프로시저 호출(RPC, Remote Procedure Call)로 시작 됨. 표면적으로 메소드 호출과 유사함.
로컬메소드 호출과는, 인접성에서 차이점을 보임 -> 근거리(대화) vs 원거리(전화통화)
스프링은 여러 RPC 모델에 대해, 리모팅을 지원해줌
* 해당 RPC모델에 관계 없이, 모든 모델에 대해 지원 기능에 한 가지 공통된 테마가 있음(템플릿과 유사)
클라이언트는 마치, 프록시가 해당 서비스를 제공하는 것처럼 호출한다. 그러면 프록시가 클라이언트를 대신하여 원격 서비스와 통신. -> 그 이후 연결에 관련된 세부사항을 처리하고, 원격 서비스를 호출
호출의 결과로 RemoteException이 발생하면, 예외를 처리하여 프록시는, 비검사형인 RemoteAccessException으로 던짐. 따라서 클라이언트는 강제적이 아니라, 선택적으로 예외를 처리할 수 있다.
원격 서비스를 소비하는 코드를 개발하거나, 구현하는 코드를 개발하든... 원격 서비스를 이용하는 작업은 단순히 설정상의 문제 -> 리모팅을 위해 자바 코드를 작성할 필요가 없음. 그리고 서비스 빈은 그 빈이 RPC에 참여하는지도 몰라도 괜찮다.
2. RMI 활용
RMI(Remote Mehod Invocation, 원격 메소드 호출) : 자바 프로그램 간의 통신을 수행하는 수단
* 과거에는 RMI서비스를 개발하고, 액세스 작업또한 거쳐야 했음. 하지만 스프링은 RMI를 로컬 JavaBeans인 것처럼 연결 해주는 프록시 팩토리 빈을 제공하여 RMI모델을 단순화함
2.1 RMI 서비스 익스포트
- 기존 RMI의 개발작업을 단순화(5단계의 복잡한 방법)
스프링에서 RMI 서비스 구성하기
RemoteException(전통적인 방법)을 던지는 메소드를 갖는 클래스를 작성하는 대신, 서비스의 기능을 수행하는 POJO만 작성하면 됨
public interface SpitterService {
List<Spittle> getRecentSpittles(int count);
void saveSpittle(Spittle spittle);
void saveSpitter(Spitter spitter);
Spitter getSpitter(long id);
void startFollowing(Spitter follower, Spitter followee);
List<Spittle> getSpittlesForSpitter(Spitter spitter);
List<Spittle> getSpittlesForSpitter(String username);
Spitter getSpitter(String username);
Spittle getSpittleById(long id);
void deleteSpittle(long id);
List<Spitter> getAllSpitters();
}
* 스프링의 RmiServiceExporter를 사용하면, 해당 클래스의 메소드들에 RemoteException을 던져주지 않아도 됨
@Bean
public RmiServiceExporter rmiExporter(SpitterService spitterService) {
RmiServiceExporter rmiExporter = new RmiServiceExporter();
rmiExporter.setService(spitterService);
rmiExporter.setServiceName("SpitterService");
rmiExporter.setServiceInterface(SpitterService.class);
return rmiExporter;
}
* 빈을 어댑터 클래스 안에 래핑하는 방식으로 작동 -> SpitterService -> 이 어댑터 클래스는, 바인딩된 서비스(SpitterServicelmpl)에 대한 요청을 프록시함
* 기본적 로컬 머신의 1099포트에 레지스트리 바인드를 시도
@Bean
public RmiServiceExporter rmiExporter(SpitterService spitterService) {
RmiServiceExporter rmiExporter = new RmiServiceExporter();
rmiExporter.setService(spitterService);
rmiExporter.setServiceName("SpitterService");
rmiExporter.setServiceInterface(SpitterService.class);
rmiExporter.setRegistryHost("rmi.spitter.com");
rmiExporter.setRegistryPort(1199);
return rmiExporter;
}
* 바인드하는 레지스트리 포트를 변경하는 방법(1199)
2.2 RMI 서비스 와이어링
전통적인 방법으로, RMI 레지스트리에서 서비스를 검색하려면 API Naming 클래스를 사용해야함
try {
String serviceUrl = "rmi:/spitter/SpitterService";
SpitterService spitterService = (SpitterService) Naming.lookup(serviceUrl);
... }
catch (RemoteException e) { ... }
catch (NotBoundException e) { ... }
catch (MalformedURLException e) { ... }
==> 각 Exception이 치명적이며, 회복 불가능하다. 이러한 예외가 나올경우, 사실상 어플리케이션을 재시작하여야함. try/catch를 사실상 할 필요가 없다...
==> DI를 정면으로 위반한다. 이 서비스는 RMI서비스이기에, 다른 서비스에서 구현을 제공할 기회가 없음. 고로 객체도 주입할 수없다.
==> 스프링은 이러한 단점을 극복할 수 있는 RmiProxyFactoryBean으로 RMI 서비스에 대한 프록시를 생성하는 팩토리빈을 제공
@Bean
public RmiProxyFactoryBean spitterService() {
RmiProxyFactoryBean rmiProxy = new RmiProxyFactoryBean();
rmiProxy.setServiceUrl("rmi://localhost/SpitterService");
rmiProxy.setServiceInterface(SpitterService.class);
return rmiProxy;
}
* 서비스 URL은 RmiProxyFactoryBean의 serviceUrl 프로퍼티를 통해 설정
스프링 관리 빈으로 RMI서비스를 선언했으므로, 로컬빈처럼 다른 빈에 종속객체로 연결됨
@Autowired // 서비스 프록시를 클라이언트에 연결
SpitterService spitterService;
public List<Spittle> getSpittles(String userName) {
Spitter spitter = spitterService.getSpitter(userName);
return spitterService.getSpittlesForSpitter(spitter);
}
해당 코드처럼 로컬 빈처럼 연결 가능하다
=> 장점은, 클라이언트 코드가 RMI 서비스를 처리한다는 것을 아예 몰라도 되며, 단지 주입된 Serivce 객체만을 받을 뿐
RMI는 원격 서비스 통신으로는 훌륭하지만 여러 단점이 있다.
- 방화벽을 넘어 작업하는 환경에서는 한계가 있음(대체로 임의 포트를 사용)...
- 인트라넷은 상관없지만, 인터넷상에서는 문제가 된다(터널링 작업이 까다로움)
- RMI는 자바기반이기 때문에, 클라이언트 서비스 둘 다 자바로 작성해야함(자바의 직렬화를 사용하므로, 양쪽의 객체타입이 동일해야함)
===> 이러한 단점을 해결하기 위해 Hessian과 Burlap 을 이용한다.
3. Hessian과 Burlap을 이용한 리모트 서비스 노출
Hessian, Burlap은 Http를 통해, 가벼운 원격서비스를 가능케 한다. 웹서비스 단순화를 목표로 함
- Hessian : RMI와 유사하게, 클라이언트와 서비스간에, 바이너리 메시지를 이용해 통신함 - PHP, Python, C++등 자바 외의 언어에 이식됨
- Burlap : XML기반 리모팅 기술, XML을 파싱 가능한 언어라면, 자동적으로 이식 가능하다. 메시지 구조가 매우 간단
-> 두가지 기술은 대부분 동일함. 바이너리메시지와, XML의 차이이다.
3.1 Hessian과 Burlap을 이용한 빈 기능 노출
Hessian 서비스 익스포트
스프링에서 Hessian 서비를 익스포트하는 것은, RMI와 비슷함. HessianServiceExporter를 사용하면 됨 => 서비스에 대해서는 RmiServiceExporter와 동일한 기능 ==> 그러나, 방식은 약간 다르다
HessianServiceExporter는 Hessian의 요청을 받아, POJO의 메소드 호출로 변환하는 스프링 MVC 컨트롤러
@Bean
public HessianServiceExporter hessianExportedSpitterService(SpitterService service) {
HessianServiceExporter exporter = new HessianServiceExporter();
exporter.setService(service); // * RmiSerivceExporter와 마찬가지로, 서비스로 구현하는 빈에 대한 레퍼런스가 연결
exporter.setServiceInterface(SpitterService.class);
return exporter;
}
* 레지스트리를 갖지 않으므로, serviceName 프로퍼티는 필요가 없음
Hessian 컨트롤러 구성하기
RMI와 Hessian의 주된 차이점은, Hessian이 HTTP기반이기에, HessianServiceExporter가 스프링 MVC로 구현된다는 점.. 따라서 두가지의 설정이 필요하다.
* DispatcherServlet 설정 -> Hessian 서비스를 URL을 적젌한 서비스 빈으로 디스패치하도록 URL 핸들러 설정
/* WebApplicationInitalizer 사용 시 */
ServletRegistration.Dynamic dispatcher = container.addServlet(
"appServlet", new DispatcherServlet(dispatcherServletContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
dispatcher.addMapping("*.service"); // Hessian서비스를 처리하기 위해 *.service URL패턴 서블릿 매핑 추가
/* AbstractDispatcherServletInitializer 또는 AbstractAnnotationConfigDispatcherServletInitializer 사용 시 */
@Override
protected String[] getServletMappings() {
return new String[] { "/", "*.service" }; // URL패턴 서블릿 매핑 추가
}
==> Spittle.service에 관한 매핑은 궁극적으로 hessianSpittleService빈(SpittleServiceImpl의 프록시)에 의해 처리
@Bean
public HandlerMapping hessianMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
Properties mappings = new Properties();
mappings.setProperty("/spitter.service",
"hessianExportedSpitterService");
mapping.setMappings(mappings);
return mapping;
}
* 해당 SimpleUrlHandlerMaping이 실제 URL매핑을 처리하게 된다.
Burlap 서비스 익스포트
BurlapServiceExporter는 XML을 처리하는 것 빼고는 Hessian과 모든 측면에서 동일
@Bean
public BurlapServiceExporter burlapExportedSpitterService(SpitterService service) {
BurlapServiceExporter exporter = new BurlapServiceExporter();
exporter.setService(service);
exporter.setServiceInterface(SpitterService.class);
return exporter;
}
* 유일한 차이점은, 빈의 메소드와, exporter 클래스 ===> 컨트롤러 설정은 동일하다(생략)
3.2 Hessian/Burlap 서비스에 액세스하기
RmiProxyFactoryBean처럼 프록시를 이용하여, Spitter 서비스를 소비하는 클라이언트 코드가 Hessian/Burlap인지 모르게 할 수 있다.
HessianProxyFactoryBean / BurlapProxyFactoryBean 사용
/* Hessian */
@Bean
public HessianProxyFactoryBean spitterService() {
HessianProxyFactoryBean proxy = new HessianProxyFactoryBean();
proxy.setServiceUrl("http://localhost:8080/Spitter/spitter.service");
proxy.setServiceInterface(SpitterService.class);
return proxy;
}
/* Burlap */
@Bean
public BurlapProxyFactoryBean spitterService() {
BurlapProxyFactoryBean proxy = new BurlapProxyFactoryBean();
proxy.setServiceUrl("http://localhost:8080/Spitter/spitter.service");
proxy.setServiceInterface(SpitterService.class);
return proxy;
}
* Hessian/Burlap은 Http를 기반으로 하므로, 방화벽 문제를 겪지 않음
* 하지만 복잡한 데이터모델의 경우, 직렬화 모델이 충분치 않을 수 있음(RMI가 우위)
==> RMI(자바의 직렬화 사용)와 Hessian/Burlap(방화벽 문제 없음)의 장점을 섞은 스프링의 HTTP호출자가 존재
4. 스프링의 HttpInvoker 사용하기
방화벽을 가로질러 사용 + 독자적인 객체 직렬화 매커니즘 => 스프링 Http 호출자(invoker) => HttpInvoker
4.1 빈을 HTTP 서비스로 익스포트
* HttpInvokerServiceExporter를 이용
@Bean
public HttpInvokerServiceExporter httpExportedSpitterService(SpitterService service) {
HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
exporter.setService(service);
exporter.setServiceInterface(SpitterService.class);
return exporter;
}
* 동작 자체도, HessianServiceExporter와 유사하다(스프링 MVC 컨트롤러)
==> 따라서, DispatcherServlet 매핑과, URL 핸들러를 설정해주어야 함 ==> 3.1의 컨트롤러 설정과 동일(생략)
4.2 HTTP를 거쳐 서비스에 액세스하기
* 놀랍게도... 이 부분도 Hessian/Burlap과 거의 동일
... 프록시 팩토리 빈 설정(동일)
@Bean
public HttpInvokerProxyFactoryBean spitterService() {
HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean();
proxy.setServiceUrl("http://localhost:8080/Spitter/spitter.service");
proxy.setServiceInterface(SpitterService.class);
return proxy;
}
스프링 HttpInvoke는, Http통신의 단순함과, 자바에 내장된 객체 직렬화를 결합하여, 두가지의 장점을 모은 리모팅 솔루션
=> 양쪽 모두가, 스프링 프레임워크를 사용해야 가능...
==> 앞에 서술한 모든 리모팅 모델은, 유비쿼터스 리모팅에 관한 웹서비스에는 사용 불가능하다....
===> 스프링에는 SOAP기반 웹서비스를 통해 리모팅을 구현 할 수 있다(처음부터 이걸 알려주던가..)
5. 웹서비스 발행과 소비
SOA(서비스 지향 아키텍쳐) : 각 애플리케이션마다 동일한 기능을 구현하는 대신, 공통 된 핵심 서비스에 의거하도록 설계.
JAX-WS : 웹서비스를 생성하는 JAVA-API.. 어노테이션을 사용하여 쉽게 서버 클라이언트 및 서버 모듈의 개발 및 배포를 담당한다. 발전된 자바 진영의 노력의 산물
자바와 웹서비스는 오랜시간 다양한 옵션을 적용하여 사용 가능하게 개발됐음.
스프링에서는, 일반적으로 알려진 XML 웹서비스나 JAX-XS를 이용하여, SOAP 웹서비스를 발행하고 소비 가능
5.1 스프링을 사용할 수 있는 JAX-WS 엔드포인트 생성
스프링은 JAX-WS 서비스 익스포터인 SimpleJaxWsServiceExporter를 제공한다.
2,3,4절의 방법는 사뭇 다름
다만, 이 방법이 모든 상황의 최선은 아님을 알아야한다. SimpleJaxWsServiceExporter는 JAX-WS 런타임이 특정한 주소에 대한 endpoint의 배포를 지원할 것을 요구한다(JDK 1.6 이상만 가능)
스프링에서의 JAX-WS 엔드포인트 오토와이어링
@WebService : 웹서비스 엔드포인트
@WebMethod : 웹 메소드 작업
- SpringBeanAutowiringSupport를 상속하여, 엔드포인트 프로퍼티에 @Autowired 애너테이션을 적용하여, 종속성을 주입해야한다(그러면, JAX-WS 엔드포인트가 DI의 혜택을 받을 수 있다)
- 스프링에 의해 관리되는 생명주기가 없을 때 사용하기 유용함.
@WebService(serviceName="SpitterService")
public class SpitterServiceEndpoint extends SpringBeanAutowiringSupport { // 오토 와이어링 활성화
@Autowired
SpitterService spitterService; // 서비스 오토와이어링
@WebMethod
public void addSpittle(Spittle spittle) {
spitterService.saveSpittle(spittle); // SpitterService에 위임
}
@WebMethod
public void deleteSpittle(long spittleId) {
spitterService.deleteSpittle(spittleId); // SpitterService에 위임
}
@WebMethod
public List<Spittle> getRecentSpittles(int spittleCount) {
return spitterService.getRecentSpittles(spittleCount); // SpitterService에 위임
}
@WebMethod
public List<Spittle> getSpittlesForSpitter(Spitter spitter) {
return spitterService.getSpittlesForSpitter(spitter); // SpitterService에 위임
}
}
* 오토와이어링 활성화, Service 위임
독립형 JAX-WS 엔드포인트 익스포트
* 위의 SpringBeanAutowiringSupport는, 프로퍼티가 주입된 객체가 스프링에 의해 관리되는 생명주기가 없을 때 유용
* SimpleJaxWsServiceExporter는 스프링에서 관리하는 빈을 익스포트 할 때 사용. 15장 전체에 살펴본 다른 익스포터와 유사한 방식으로 동작. 다만 해당 익스포터는, 익스포트하는 빈에 대한 레퍼런스를 부여하지 않고, 애너테이션으로 대체된다.
@Bean
public SimpleJaxWsServiceExporter jaxWsExporter() {
return new SimpleJaxWsServiceExporter();
}
* 별다른 작업이 필요 없음. 스프링 애플리케이션이 시작될 때, @WebService 애너테이션이 적용된 빈을 찾는다
@Component
@WebService(serviceName="SpitterService") // 기본주소와함께 , JAX-WS 엔드포인트로 발행
public class SpitterServiceEndpoint { // 엔드포인트로 변환
@Autowired
SpitterService spitterService;
@WebMethod
public void addSpittle(Spittle spittle) {
spitterService.saveSpittle(spittle);
}
@WebMethod
public void deleteSpittle(long spittleId) {
spitterService.deleteSpittle(spittleId);
}
@WebMethod
public List<Spittle> getRecentSpittles(int spittleCount) {
return spitterService.getRecentSpittles(spittleCount);
}
@WebMethod
public List<Spittle> getSpittlesForSpitter(Spitter spitter) {
return spitterService.getSpittlesForSpitter(spitter);
}
}
* 완전한 기능의 스프링 빈으로, 어떤 특별 지원 클래스 상속 없이도 오토와이어링 가능
* 기본적으로 http://localhost:8080/SpitterService에 있는 웹서비스이다.
==> 주소를 바꾸고 싶다면?
@Bean
public SimpleJaxWsServiceExporter jaxWsExporter() {
SimpleJaxWsServiceExporter exporter = new SimpleJaxWsServiceExporter();
exporter.setBaseAddress("http://localhost:8888/services/");
}
-> 기본 주소를 바꿔 줄 수 있다
5.2 클라이언트 측에서 JAX-WS 프록시하기
* 스프링을 이용한 웹 서비스 발행 방법은, 다른 리모팅 모델의 방법과 달랐다. 하지만, 클라이언트 측 프록시의 경우에는 크게 다르지 않다.
위의 그림과 같이, JaxWsPortProxyFactoryBean을 설정해준다.
@Bean
public JaxWsPortProxyFactoryBean spitterService() {
JaxWsPortProxyFactoryBean proxy = new JaxWsPortProxyFactoryBean();
proxy.setWsdlDocument("http://localhost:8080/services/SpitterService?wsdl");
proxy.setServiceName("spitterService");
proxy.setPortName("spitterServiceHttpPort");
proxy.setServiceInterface(SpitterService.class);
proxy.setNamespaceUri("http://spitter.com");
return proxy;
}
* 동작하기 위해선, 몇가지 프로퍼티를 설정해야 한다
* wsdlDocument -> 웹 서비스의 정의 파일이 있는 위치.../// 나머지 세 값은, WSDL파일에 따라 결정
* WSDL파일 예시
<wsdl:definitions targetNamespace="http://spitter.com">
...
<wsdl:service name="spitterService">
<wsdl:port name="spitterServiceHttpPort"
binding="tns:spitterServiceHttpBinding">
...
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
==> 다수의 서비스 혹은 포트를 정의한다 ==> wdl:port, wsdl:service 등.. nameSpacesuri의 경우, wsdl:definition의 targetNameSpace에서 찾아 볼 수 있다.
'책 읽기 > 스프링 책 읽기' 카테고리의 다른 글
스프링 책 읽기(Spring in action) - 19. 스프링을 사용하여 이메일 전송하기 (0) | 2020.01.03 |
---|---|
스프링 책 읽기(Spring in action) - 17. 스프링 메시징 (0) | 2019.12.30 |
스프링 책 읽기(Spring in action) - 14. 시큐리티 메소드 (0) | 2019.12.09 |
스프링 책 읽기(Spring in action) - 13. 데이터 캐싱하기 (0) | 2019.12.06 |
스프링 책 읽기(Spring in action) - 12. NoSQL 데이터베이스 사용하기 (0) | 2019.12.02 |