也來談談RPC


前言

RPC,顧名思義即遠程過程調用,能夠說是分部式WEB應用的基礎,也是SOA概念中的核心部分。對於J2EE來講,能夠分爲JVM之間的調用與和其餘平臺之間的調用。前者主要是RMI,然後者則五花八門,好比Apache的Thrift框架就支持多語言系統之間的調用.今天就讓咱們來初窺如下這大名鼎鼎的RPC。java

問題描述

要想實現RPC,本質上是服務發佈方與服務調用方的通訊,主要集中在如下幾個問題上:web

  • 調用方首先須要知道如何找到對應服務所在的遠程Serverspring

  • 調用方與服務提供方能以特定的雙方可以解析的數據格式封裝本身的調用請求和調用結果apache

  • 對多個服務提供方Server提供的不一樣服務以及它們的狀態應該予以有效的管理,好比新增服務須要註冊該服務json

  • 對於不一樣系統間的調用可能還須要必定的容錯和調度服務器

首先咱們給出一個最簡單的解決思路,就是直接利用HTTP協議,將調用方看作客戶端,而服務提供方看作服務器,以JSON來作數據傳輸,默認一套key-value的含義解釋,直接發起請求,而後等待響應並解析返回的JSON串。聽起來彷佛可行,可是卻有許多弊端:網絡

  • 每一個調用者須要提早保有一份服務者以及它提供的相應服務的記錄信息app

  • 大量的Http請求與解析響應的代碼混雜在原有的業務代碼中,對於使用服務的開發者不透明,惡劣的設計框架

  • 對於服務變動和新增的狀況不友好dom

  • 若是有多個提供者提供同一服務,不利於增長均衡策略,不利於擴展

。。。。

還有不少,這就是爲何許多RPC框架和標準存在的緣由,下面咱們就主要從RMI出發來繼續探討這一問題。

咱們以Apache的CXF框架來講明RMI調用,首先介紹一下JAX-WS和JAX-RS的概念。

  • JAX-WS:JavaTM API forXML-Based Web Services,JAX-WS是面向消息的,每次請求的時候指定了請求的方法。J

  • JAX-RS:JavaTM API forRESTful Web Services,AX-RS是面向資源的。後則將網絡上的東西當作一種資源,每次請求都是對該資源進行操做,好比對資源的增刪查改。

SOAP和JAXRS分別表明了這兩種模式。

JAXRS

首先關注服務提供方:

在ApplicatiionContext.xml中加入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jaxrs="http://cxf.apache.org/jaxrs"
    xsi:schemaLocation="http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
    default-lazy-init="true">

    <description>Apache CXF的Restful Web Service配置</description>
    
    <!-- jax-rs endpoint定義  -->
    <jaxrs:server id="serviceContainer" address="/jaxrs">
        <jaxrs:serviceBeans>
            <ref bean="userJaxRsService" />
        </jaxrs:serviceBeans>
        <jaxrs:providers>
            <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider" />
        </jaxrs:providers>
    </jaxrs:server>

    <!-- WebService的實現Bean定義,註冊服務 -->
    <bean id="userJaxRsService" class="jseed.webservice.jaxrs.service.UserJaxRsService" />
</beans>

而後添加服務

/**
 * WebService服務端實現類.
 * 
 * 爲演示方便,直接調用了Service層.客戶端實現見功能測試用例.
 * 
 */
@Path("/user")
public class UserJaxRsService {

    private static Logger logger = LoggerFactory.getLogger(UserJaxRsService.class);

    @Autowired
    private UserService userService;
    
    @GET
    @Path("/{id}.xml")
    @Produces(MediaTypes.APPLICATION_XML_UTF_8)
    public UserDTO getAsXml(@PathParam("id") Long id) {
        Optional<User> user = userService.load(id);
        if (!user.isPresent()) {
            String message = "用戶不存在(id:" + id + ")";
            logger.warn(message);
            throw buildException(Status.NOT_FOUND, message);
        }
        return bindDTO(user.get());
    }
    
    @GET
    @Path("/{id}.json")
    @Produces(MediaTypes.JSON_UTF_8)
    public UserDTO getAsJson(@PathParam("id") Long id) {
        Optional<User> user = userService.load(id);
        if (!user.isPresent()) {
            String message = "用戶不存在(id:" + id + ")";
            logger.warn(message);
            throw buildException(Status.NOT_FOUND, message);
        }
        return bindDTO(user.get());
    }

    private UserDTO bindDTO(User user) {
        UserDTO dto = BeanMapper.map(user, UserDTO.class);
        // 補充Dozer不能自動綁定的屬性
        return dto;
    }

    private WebApplicationException buildException(Status status, String message) {
        return new WebApplicationException(Response.status(status).entity(message).type(MediaTypes.TEXT_PLAIN_UTF_8)
                .build());
    }
    
}

接下來看服務調用方

/**
 * 對基於JAX-RS的實現Restful的測試
 * 
 * @author calvin
 */
public class UserJaxRsFT extends BaseFunctionalTestCase {

    private static String resourceUrl = baseUrl + "/cxf/jaxrs/user";

    private RestTemplate restTemplate = new RestTemplate();

    @Test
    public void getUser() {
        UserDTO user = restTemplate.getForObject(resourceUrl + "/{id}.xml", UserDTO.class, 1L);
        assertThat(user.getLoginName()).isEqualTo("admin");
        assertThat(user.getName()).isEqualTo("管理員");
        assertThat(user.getTeamId()).isEqualTo(1);

        try {
            user = restTemplate.getForObject(resourceUrl + "/{id}.json", UserDTO.class, 1L);
        } catch (HttpStatusCodeException e) {
            fail(e.getMessage());
        }
        assertThat(user.getLoginName()).isEqualTo("admin");
        assertThat(user.getName()).isEqualTo("管理員");
        assertThat(user.getTeamId()).isEqualTo(1);
    }
}

SOAP

首先解釋一下SOAP的概念:一種輕量的、簡單的、基於XML的交換數據協議規範,也就是說它定義了Server之間通訊的規範。SOAP的使用方式較爲複雜,故咱們只截取其中的核心部分闡述。

首先,看服務提供方:

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
    default-lazy-init="true">

    <description>Apache CXF的 SOAP Web Service配置</description>
    
    <!-- jax-ws endpoint定義  -->
    <jaxws:endpoint address="/soap/accountservice">
        <jaxws:implementor ref="accountSoapService" />
    </jaxws:endpoint>

    <!-- WebService的實現Bean定義 -->
    <bean id="accountSoapService" class="jseed.webservice.soap.service.AccountSoapServiceImpl" />
</beans>

而後是服務的實現:

/**
 * WebService服務端實現類.
 * 
 * 爲演示方便,直接調用了Dao層.客戶端實現見功能測試用例.
 * 
 */
// serviceName指明WSDL中<wsdl:service>與<wsdl:binding>元素的名稱, endpointInterface屬性指向Interface類全稱.
@WebService(serviceName = "AccountService", endpointInterface = "org.springside.examples.showcase.webservice.soap.AccountSoapService", targetNamespace = WsConstants.NS)
// 增長inbound/outbound SOAP內容的日誌
@Features(features = "org.apache.cxf.feature.LoggingFeature")
public class AccountSoapServiceImpl implements AccountSoapService {

    private static Logger logger = LoggerFactory.getLogger(AccountSoapServiceImpl.class);

    @Autowired
    private AccountService accountService;

    @Autowired
    private Validator validator;

    /**
     * @see AccountSoapService#getUser(Long)
     */
    @Override
    public GetUserResult getUser(Long id) {
        GetUserResult result = new GetUserResult();
        try {

            Validate.notNull(id, "id參數爲空");

            User user = accountService.getUser(id);

            Validate.notNull(user, "用戶不存在(id:" + id + ")");

            UserDTO dto = BeanMapper.map(user, UserDTO.class);
            result.setUser(dto);

            return result;

        } catch (IllegalArgumentException e) {
            return handleParameterError(result, e);
        } catch (RuntimeException e) {
            return handleGeneralError(result, e);
        }
    }

    /**
     * @see AccountSoapService#searchUser(String, String)
     */
    @Override
    public SearchUserResult searchUser(String loginName, String name) {
        SearchUserResult result = new SearchUserResult();
        try {
            List<User> userList = accountService.searchUser(loginName, name);

            List<UserDTO> dtoList = BeanMapper.mapList(userList, UserDTO.class);
            result.setUserList(dtoList);
            return result;
        } catch (RuntimeException e) {
            return handleGeneralError(result, e);
        }
    }

    /**
     * @see AccountSoapService#createUser(UserDTO)
     */
    @Override
    public IdResult createUser(UserDTO user) {
        IdResult result = new IdResult();
        try {
            Validate.notNull(user, "用戶參數爲空");

            User userEntity = BeanMapper.map(user, User.class);
            BeanValidators.validateWithException(validator, userEntity);

            accountService.saveUser(userEntity);

            return new IdResult(userEntity.getId());
        } catch (ConstraintViolationException e) {
            String message = StringUtils.join(BeanValidators.extractPropertyAndMessageAsList(e, " "), "\n");
            return handleParameterError(result, e, message);
        } catch (RuntimeException e) {
            if (Exceptions.isCausedBy(e, DuplicateKeyException.class)) {
                String message = "新建用戶參數存在惟一性衝突(用戶:" + user + ")";
                return handleParameterError(result, e, message);
            } else {
                return handleGeneralError(result, e);
            }
        }
    }

    private <T extends WSResult> T handleParameterError(T result, Exception e, String message) {
        logger.error(message, e.getMessage());
        result.setError(WSResult.PARAMETER_ERROR, message);
        return result;
    }

    private <T extends WSResult> T handleParameterError(T result, Exception e) {
        logger.error(e.getMessage());
        result.setError(WSResult.PARAMETER_ERROR, e.getMessage());
        return result;
    }

    private <T extends WSResult> T handleGeneralError(T result, Exception e) {
        logger.error(e.getMessage());
        result.setDefaultError();
        return result;
    }
}

下面咱們再來看服務調用者:

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"
    default-lazy-init="true">
    <description>Apache CXF Web Service Client端配置</description>

    <context:property-placeholder ignore-resource-not-found="true"
            location="classpath*:/application.functional.properties,
                        classpath*:/application.functional-local.properties" />    
    
    <jaxws:client id="accountWebServiceClient" serviceClass="org.springside.examples.showcase.webservice.soap.AccountSoapService"
        address="${baseUrl}/cxf/soap/accountservice" />
</beans>

調用流程

/**
 * 
 * 以用JAXWS的API, 根據AccountWebService接口自行建立.
 * 使用在Spring applicaitonContext.xml中用<jaxws:client/>,根據AccountWebService接口建立的Client.
 * 
 */
public class AccountWebServiceWithDynamicCreateClientFT extends BaseFunctionalTestCase {
    
    //predefine client
    @Autowired
    private AccountSoapService accountWebServiceClient;


    //dynamic client
    public AccountSoapService creatClient() {
        String address = baseUrl + "/cxf/soap/accountservice";

        JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean();
        proxyFactory.setAddress(address);
        proxyFactory.setServiceClass(AccountSoapService.class);
        AccountSoapService accountWebServiceProxy = (AccountSoapService) proxyFactory.create();

        // (可選)演示從新設定endpoint address.
        ((BindingProvider) accountWebServiceProxy).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                address);

        // (可選)演示從新設定Timeout時間
        Client client = ClientProxy.getClient(accountWebServiceProxy);
        HTTPConduit conduit = (HTTPConduit) client.getConduit();
        HTTPClientPolicy policy = conduit.getClient();
        policy.setReceiveTimeout(600000);

        return accountWebServiceProxy;
    }

    public void getUser() {
        GetUserResult response = accountWebServiceClient.getUser(1L);
        return response;
    }

    /**
     * 測試搜索用戶
     */
    @Test
    public void searchUser() {
        SearchUserResult response = accountWebServiceClient.searchUser(null, null);
        return response;
    }

    /**
     * 測試建立用戶.
     */
    @Test
    public void createUser() {
        User user = UserData.randomUser();
        UserDTO userDTO = BeanMapper.map(user, UserDTO.class);

        IdResult response = accountWebServiceClient.createUser(userDTO);
        assertThat(response.getId()).isNotNull();
        GetUserResult response2 = accountWebServiceClient.getUser(response.getId());
    }

}
相關文章
相關標籤/搜索