Dubbo源码学习--Rest协议(五)

版权声明:欢迎转载 https://blog.csdn.net/qq924862077/article/details/79945674

Dubbo的rest协议是采用的org.jboss.resteasy框架实现的,dubbo项目重新被维护之后才添加的功能,之前当当的dubbox对dubbo进行扩展支持Rest

在dubbo中开发一个REST风格的服务会比较简单,下面以一个注册用户的简单服务为例说明。

这个服务要实现的功能是提供如下URL(注:这个URL不是完全符合REST的风格,但是更简单实用):

http://localhost:8080/users/register

而任何客户端都可以将包含用户信息的JSON字符串POST到以上URL来完成用户注册。

首先,开发服务的接口:

public class UserService {    
   void registerUser(User user);
}

然后,开发服务的实现:

@Path("users")
public class UserServiceImpl implements UserService {

    @POST
    @Path("register")
    @Consumes({MediaType.APPLICATION_JSON})
    public void registerUser(User user) {
        // save the user...
    }
}

上面的服务实现代码非常简单,但是由于REST服务是要被发布到特定HTTP URL,供任意语言客户端甚至浏览器来访问,所以这里要额外添加了几个JAX-RS的标准annotation来做相关的配置:

@Path("users"):指定访问UserService的URL相对路径是/users,即http://localhost:8080/users

@Path("register"):指定访问registerUser()方法的URL相对路径是/register,再结合上一个@Path为UserService指定的路径,则调用UserService.register()的完整路径为http://localhost:8080/users/register

@POST:指定访问registerUser()用HTTP POST方法

@Consumes({MediaType.APPLICATION_JSON}):指定registerUser()接收JSON格式的数据。REST框架会自动将JSON数据反序列化为User对象

最后,在spring配置文件中添加此服务,即完成所有服务开发工作:

<!-- 用rest协议在8080端口暴露服务 -->
<dubbo:protocol name="rest" port="8080"/>

<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="xxx.UserService" ref="userService"/>

<!-- 和本地bean一样实现服务 -->
<bean id="userService" class="xxx.UserServiceImpl" />

接下来我们看看RestProtocol中的实现:

RestProtocol也是提供了两个接口doExport和doRefer对外暴露服务和引用服务。

(1)doExport:RestProtocol即可以通过servlet暴露服务,也可以通过netty等暴露服务

 protected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException {
        String addr = getAddr(url);
        Class implClass = ServiceClassHolder.getInstance().popServiceClass();
        RestServer server = servers.get(addr);
		//创建服务提供者,服务提供者即可以是servlet容器,或者直接是netty等tcp服务
        if (server == null) {
            server = serverFactory.createServer(url.getParameter(Constants.SERVER_KEY, "jetty"));
            server.start(url);
            servers.put(addr, server);
        }
		//如果是serlvet提供服务就会有向下文
        String contextPath = getContextPath(url);
        if ("servlet".equalsIgnoreCase(url.getParameter(Constants.SERVER_KEY, "jetty"))) {
            ServletContext servletContext = ServletManager.getInstance().getServletContext(ServletManager.EXTERNAL_SERVER_PORT);
            if (servletContext == null) {
                throw new RpcException("No servlet context found. Since you are using server='servlet', " +
                        "make sure that you've configured " + BootstrapListener.class.getName() + " in web.xml");
            }
            String webappPath = servletContext.getContextPath();
            if (StringUtils.isNotEmpty(webappPath)) {
                webappPath = webappPath.substring(1);
                if (!contextPath.startsWith(webappPath)) {
                    throw new RpcException("Since you are using server='servlet', " +
                            "make sure that the 'contextpath' property starts with the path of external webapp");
                }
                contextPath = contextPath.substring(webappPath.length());
                if (contextPath.startsWith("/")) {
                    contextPath = contextPath.substring(1);
                }
            }
        }

        final Class resourceDef = GetRestful.getRootResourceClass(implClass) != null ? implClass : type;
		//暴露服务
        server.deploy(resourceDef, impl, contextPath);

        final RestServer s = server;
		//创建线程,服务销毁时卸载服务
        return new Runnable() {
            public void run() {
                // TODO due to dubbo's current architecture,
                // it will be called from registry protocol in the shutdown process and won't appear in logs
                s.undeploy(resourceDef);
            }
        };
    }

(2)doRefer:简单来说就是创建http请求进行远程调用。

//通过http请求进行接口调用
    protected <T> T doRefer(Class<T> serviceType, URL url) throws RpcException {
        if (connectionMonitor == null) {
            connectionMonitor = new ConnectionMonitor();
        }

        // TODO more configs to add
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        // 20 is the default maxTotal of current PoolingClientConnectionManager
        connectionManager.setMaxTotal(url.getParameter(Constants.CONNECTIONS_KEY, 20));
        connectionManager.setDefaultMaxPerRoute(url.getParameter(Constants.CONNECTIONS_KEY, 20));

        connectionMonitor.addConnectionManager(connectionManager);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT))
                .setSocketTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT))
                .build();

        SocketConfig socketConfig = SocketConfig.custom()
                .setSoKeepAlive(true)
                .setTcpNoDelay(true)
                .build();
		//创建http连接
        CloseableHttpClient httpClient = HttpClientBuilder.create()
                .setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
                    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                        HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                        while (it.hasNext()) {
                            HeaderElement he = it.nextElement();
                            String param = he.getName();
                            String value = he.getValue();
                            if (value != null && param.equalsIgnoreCase("timeout")) {
                                return Long.parseLong(value) * 1000;
                            }
                        }
                        // TODO constant
                        return 30 * 1000;
                    }
                })
                .setDefaultRequestConfig(requestConfig)
                .setDefaultSocketConfig(socketConfig)
                .build();

        ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient/*, localContext*/);
		//创建rest客户端
        ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build();
        clients.add(client);

        client.register(RpcContextFilter.class);
        for (String clazz : Constants.COMMA_SPLIT_PATTERN.split(url.getParameter(Constants.EXTENSION_KEY, ""))) {
            if (!StringUtils.isEmpty(clazz)) {
                try {
                    client.register(Thread.currentThread().getContextClassLoader().loadClass(clazz.trim()));
                } catch (ClassNotFoundException e) {
                    throw new RpcException("Error loading JAX-RS extension class: " + clazz.trim(), e);
                }
            }
        }

        // TODO protocol
		//创建服务代理类
        ResteasyWebTarget target = client.target("http://" + url.getHost() + ":" + url.getPort() + "/" + getContextPath(url));
        return target.proxy(serviceType);
    }

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页