1. 개요

어떤 경우에는 시스템을 여러 프로세스로 분해해야하며 각 프로세스는 애플리케이션의 다른 측면을 담당합니다. 이러한 시나리오에서 프로세스 중 하나가 다른 프로세스에서 동 기적으로 데이터를 가져와야하는 것은 드문 일이 아닙니다.

Spring Framework는 마치 적어도 어느 정도는 로컬에서 사용 가능한 것처럼 원격 서비스를 호출 할 수있는 Spring Remoting 이라는 포괄적 인 도구 를 제공합니다.

이 기사에서는 클라이언트와 서버 애플리케이션 간의 원격 메소드 호출을 제공하기 위해 네이티브 Java 직렬화 및 HTTP를 활용하는 Spring의 HTTP 호출자를 기반으로 애플리케이션을 설정합니다 .

2. 서비스 정의

사용자가 택시를 예약 할 수있는 시스템을 구현해야한다고 가정 해 보겠습니다.

또한 이 목표를 달성 하기 위해 두 개의 개별 애플리케이션빌드하기로 선택했다고 가정 해 보겠습니다 .

  • 택시 요청을 처리 할 수 ​​있는지 확인하기위한 예약 엔진 애플리케이션
  • 고객이 차량을 예약 할 수있는 프론트 엔드 웹 애플리케이션으로 택시의 가용성이 확인되었습니다.

2.1. 서비스 인터페이스

HTTP 호출자 와 함께 Spring Remoting사용할 때 , Spring이 원격 호출의 기술을 캡슐화하는 클라이언트와 서버 측에서 프록시를 생성 할 수 있도록 인터페이스를 통해 원격 호출 가능한 서비스를 정의해야합니다. 이제 택시를 예약 할 수있는 서비스의 인터페이스부터 시작하겠습니다.

public interface CabBookingService {
    Booking bookRide(String pickUpLocation) throws BookingException;
}

서비스가 택시를 할당 할 수 있는 경우 예약 코드와 함께 Booking 개체를 반환합니다 . Spring의 HTTP 호출자가 서버에서 클라이언트로 인스턴스를 전송해야하기 때문에 예약 은 직렬화 가능해야합니다.

public class Booking implements Serializable {
    private String bookingCode;

    @Override public String toString() {
        return format("Ride confirmed: code '%s'.", bookingCode);
    }

    // standard getters/setters and a constructor
}

서비스가 택시를 예약 할 수없는 경우 BookingException 이 발생합니다. 이 경우 Exception이 이미 구현 했기 때문에 클래스를 Serializable 로 표시 할 필요가 없습니다 .

public class BookingException extends Exception {
    public BookingException(String message) {
        super(message);
    }
}

2.2. 서비스 패키징

인수, 반환 유형 및 예외로 사용되는 모든 사용자 정의 클래스와 함께 서비스 인터페이스는 클라이언트 및 서버의 클래스 경로에서 모두 사용할 수 있어야합니다. 이를 수행하는 가장 효과적인 방법 중 하나는 나중에 서버 및 클라이언트의 pom.xml에 의존성으로 포함될 수 있는 .jar 파일 에 모두 압축하는 것 입니다.

따라서 모든 코드를 "api"라고하는 전용 Maven 모듈에 넣어 보겠습니다. 이 예제에서는 다음 Maven 좌표를 사용합니다.

<groupId>com.baeldung</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>

3. 서버 애플리케이션

Spring Boot를 사용하여 서비스를 노출하는 예약 엔진 애플리케이션을 빌드 해 보겠습니다.

3.1. Maven 의존성

먼저 프로젝트가 Spring Boot를 사용하고 있는지 확인해야합니다.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
</parent>

여기 에서 마지막 Spring Boot 버전을 찾을 수 있습니다 . 그런 다음 웹 스타터 모듈이 필요합니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

그리고 이전 단계에서 조립 한 서비스 정의 모듈이 필요합니다.

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

3.2. 서비스 구현

먼저 서비스의 인터페이스를 구현하는 클래스를 정의합니다.

public class CabBookingServiceImpl implements CabBookingService {

    @Override public Booking bookPickUp(String pickUpLocation) throws BookingException {
        if (random() < 0.3) throw new BookingException("Cab unavailable");
        return new Booking(randomUUID().toString());
    }
}

이것이 가능한 구현이라고 가정합시다. 랜덤의 값이있는 테스트를 사용하면 사용 가능한 택시가 발견되고 예약 코드가 반환 된 경우 성공한 시나리오와 사용 가능한 택시가 없음을 나타내는 BookingException이 발생하는 경우 실패한 시나리오를 모두 재현 할 수 있습니다.

3.3. 서비스 노출

그런 다음 컨텍스트에서 HttpInvokerServiceExporter 유형의 Bean을 사용하여 애플리케이션을 정의해야합니다 . 나중에 클라이언트가 호출 할 웹 애플리케이션에서 HTTP 진입 점을 노출하는 작업을 처리합니다.

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Server {

    @Bean(name = "/booking") HttpInvokerServiceExporter accountService() {
        HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
        exporter.setService( new CabBookingServiceImpl() );
        exporter.setServiceInterface( CabBookingService.class );
        return exporter;
    }

    public static void main(String[] args) {
        SpringApplication.run(Server.class, args);
    }
}

Spring의 HTTP 호출자HTTP 엔드 포인트 URL의 상대 경로로 HttpInvokerServiceExporter의 이름을 사용 한다는 점은 주목할 가치가 있습니다.

이제 서버 애플리케이션을 시작하고 클라이언트 애플리케이션을 설정하는 동안 계속 실행할 수 있습니다.

4. 클라이언트 응용 프로그램

이제 클라이언트 애플리케이션을 작성해 보겠습니다.

4.1. Maven 의존성

서버 측에서 사용한 것과 동일한 서비스 정의와 동일한 Spring Boot 버전을 사용합니다. 웹 스타터 의존성이 여전히 필요하지만 임베디드 컨테이너를 자동으로 시작할 필요가 없기 때문에 의존성에서 Tomcat 스타터를 제외 할 수 있습니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

4.2. 클라이언트 구현

클라이언트를 구현해 보겠습니다.

@Configuration
public class Client {

    @Bean
    public HttpInvokerProxyFactoryBean invoker() {
        HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
        invoker.setServiceUrl("http://localhost:8080/booking");
        invoker.setServiceInterface(CabBookingService.class);
        return invoker;
    }

    public static void main(String[] args) throws BookingException {
        CabBookingService service = SpringApplication
          .run(Client.class, args)
          .getBean(CabBookingService.class);
        out.println(service.bookRide("13 Seagate Blvd, Key Largo, FL 33037"));
    }
}

@Bean 어노테이션 호출자 () 메소드의 인스턴스 생성 HttpInvokerProxyFactoryBean를 . setServiceUrl () 메서드 를 통해 원격 서버가 응답하는 URL을 제공해야합니다 .

서버에 대해 수행 한 것과 유사하게 setServiceInterface () 메소드를 통해 원격으로 호출하려는 서비스의 인터페이스도 제공해야합니다 .

HttpInvokerProxyFactoryBean 은 Spring의 FactoryBean을 구현 합니다. 의 FactoryBean은 빈으로 정의되지만, Spring IoC 컨테이너는 생성 객체가 아닌 공장 자체를 주입합니다. FactoryBean대한 자세한 내용은 Factory Bean 기사 에서 찾을 수 있습니다 .

의 main () 메소드 부트 스트랩 독립형 애플리케이션 및 얻 인스턴스 CabBookingService 맥락에서. 내부적으로이 객체는 원격 호출 실행과 관련된 모든 기술을 처리 하는 HttpInvokerProxyFactoryBean에 의해 생성 된 프록시 일뿐 입니다. 덕분에 이제 우리는 서비스 구현이 로컬에서 사용 가능했던 것처럼 프록시를 쉽게 사용할 수 있습니다.

응용 프로그램을 여러 번 실행하여 여러 원격 호출을 실행하여 cab을 사용할 수있을 때와 사용할 수 없을 때 클라이언트가 어떻게 동작하는지 확인하겠습니다.

5. 경고 엠프 터

원격 호출을 허용하는 기술로 작업 할 때 우리가 잘 알아야 할 몇 가지 함정이 있습니다.

5.1. 네트워크 관련 예외주의

신뢰할 수없는 리소스를 네트워크로 사용할 때 항상 예상치 못한 상황을 예상해야합니다.

클라이언트가 서버에 도달 할 수없는 동안-네트워크 문제 또는 서버가 다운 되었기 때문에-서버를 호출한다고 가정하면 Spring Remoting은 RuntimeException 인 RemoteAccessException 을 발생 시킬 것 입니다.

컴파일러는 try-catch 블록에 호출을 포함하도록 강제하지 않지만 네트워크 문제를 적절하게 관리하기 위해 항상이를 고려해야합니다.

5.2. 객체는 참조가 아닌 값으로 전송됩니다.

Spring Remoting HTTP는 메소드 인수와 반환 값을 마샬링하여 네트워크에서 전송합니다. 즉, 서버는 제공된 인수의 복사본에 대해 작동하고 클라이언트는 서버에 의해 생성 된 결과의 복사본에 대해 작동합니다.

예를 들어 클라이언트와 서버간에 공유 객체가 없기 때문에 결과 객체에 대해 메서드를 호출하면 서버 측에서 동일한 객체의 상태가 변경 될 것으로 예상 할 수 없습니다.

5.3. 세분화 된 인터페이스주의

네트워크 경계를 넘어 메서드를 호출하는 것은 동일한 프로세스의 개체에서 호출하는 것보다 훨씬 느립니다.

이러한 이유로 더 복잡한 인터페이스를 희생하더라도 더 적은 상호 작용을 필요로하는 비즈니스 트랜잭션을 완료 할 수있는 더 거친 인터페이스를 사용하여 원격으로 호출해야하는 서비스를 정의하는 것이 일반적입니다.

6. 결론

이 예제를 통해 Spring Remoting으로 원격 프로세스를 호출하는 것이 얼마나 쉬운 지 확인했습니다.

이 솔루션은 REST 또는 웹 서비스와 같은 다른 널리 퍼진 메커니즘보다 약간 덜 개방적이지만 모든 구성 요소가 Spring으로 개발되는 시나리오에서는 실행 가능하고 훨씬 빠른 대안을 나타낼 수 있습니다.

평소처럼 GitHub 에서 소스 찾을 수 있습니다 .