[关闭]
@coldxiangyu 2017-06-02T13:59:55.000000Z 字数 6020 阅读 2145

对协议适配的探索(Spring boot)

WEB框架


前两天,领导下达指示,探索协议适配的可行性。
所谓的协议适配,就是服务层提供的多协议服务,比如webservice(soap)、http接口、FTP、socket(TCP/IP)等,对外无差别暴露统一的IP和端口,而客户端由此统一IP端口接收请求之后进行适配调用不同的服务。
听上去有那么点意思。
当然,首先要搭建一个工程试验一下才行。
试验的话,肯定不能在搭建项目上花费太多时间,spring boot是不二之选。
源码已上传github:https://github.com/coldxiangyu/protocoladapter
我们要研究多协议,所以肯定要加入webservice,webservice采用cxf发布。
我们除了引入spring boot的包还要引入cxf的包:

  1. <dependency>
  2. <groupId>org.apache.cxf</groupId>
  3. <artifactId>cxf-rt-frontend-jaxws</artifactId>
  4. <version>3.1.6</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.cxf</groupId>
  8. <artifactId>cxf-rt-transports-http</artifactId>
  9. <version>3.1.6</version>
  10. </dependency>

这时候,开发条件就已经满足了。
我们来实现一个webservice版的hello,这和之前spring整合cxf通过配置XML是不一样的。

1.定义接口方法:

  1. package com.lxy.webservice;
  2. import javax.jws.WebMethod;
  3. import javax.jws.WebService;
  4. @WebService
  5. public interface HelloService {
  6. @WebMethod
  7. String getName(String param);
  8. }

2.定义接口实现方法:

  1. package com.lxy.webservice.impl;
  2. import javax.jws.WebService;
  3. import com.lxy.webservice.HelloService;
  4. @WebService(targetNamespace="http://webservice.lxy.com/",endpointInterface = "com.lxy.webservice.HelloService")
  5. public class HelloServiceImpl implements HelloService {
  6. @Override
  7. public String getName(String param) {
  8. return "hello "+param;
  9. }
  10. }

3.这时候我们就可以对我们定义的webservice进行发布了。发布的方式有两种,一种是默认发布,还有一种就是自定义发布。
我们看看默认发布:

  1. package com.lxy.cxf;
  2. import javax.xml.ws.Endpoint;
  3. import org.apache.cxf.Bus;
  4. import org.apache.cxf.bus.spring.SpringBus;
  5. import org.apache.cxf.jaxws.EndpointImpl;
  6. import org.apache.cxf.transport.servlet.CXFServlet;
  7. import org.springframework.boot.SpringApplication;
  8. import org.springframework.boot.autoconfigure.SpringBootApplication;
  9. import org.springframework.boot.web.servlet.ServletRegistrationBean;
  10. import org.springframework.context.annotation.Bean;
  11. import com.lxy.webservice.HelloService;
  12. import com.lxy.webservice.impl.HelloServiceImpl;
  13. @SpringBootApplication
  14. public class CxfConfig {
  15. public static void main(String[] args) {
  16. SpringApplication.run(CxfConfig.class, args);
  17. }
  18. @Bean
  19. public ServletRegistrationBean dispatcherServlet() {
  20. return new ServletRegistrationBean(new CXFServlet(), "/webservices/*");
  21. }
  22. @Bean(name = Bus.DEFAULT_BUS_ID)
  23. public SpringBus springBus() {
  24. return new SpringBus();
  25. }
  26. @Bean
  27. public HelloService helloService() {
  28. return new HelloServiceImpl();
  29. }
  30. @Bean
  31. public Endpoint endpoint() {
  32. EndpointImpl endpoint = new EndpointImpl(springBus(), helloService());
  33. endpoint.publish("/hello");
  34. return endpoint;
  35. }
  36. }

默认发布实现非常简单,只需定义webservice发布路径,通过endpoint.publish发布方法即可。
如果默认的发布不满足我们的需求怎么办呢,比如默认的发布端口是8080,我们想用8081进行发布。也非常简单,我们只需让此类实现EmbeddedServletContainerCustomizer接口,然后通过重写customize方法,进行container的重新设定即可。

  1. @SpringBootApplication
  2. public class Application implements EmbeddedServletContainerCustomizer {
  3. public static void main(String[] args) {
  4. SpringApplication.run(Application.class, args);
  5. }
  6. @Override
  7. public void customize(ConfigurableEmbeddedServletContainer container) {
  8. container.setPort(8081);
  9. }
  10. @Bean
  11. public ServletRegistrationBean cxfServlet() {
  12. return new ServletRegistrationBean(new CXFServlet(), "/webservices/*");
  13. }
  14. @Bean(name = Bus.DEFAULT_BUS_ID)
  15. public SpringBus springBus() {
  16. return new SpringBus();
  17. }
  18. @Bean
  19. public Endpoint endpoint() {
  20. EndpointImpl endpoint = new EndpointImpl(springBus(),new HelloServiceImpl());
  21. endpoint.publish("/hello");
  22. return endpoint;
  23. }

现在我们通过main方法启动,通过我们定义的webservice地址:http://localhost:8080/webservices/hello?wsdl查看是否发布成功。
image_1bhj7qb00dcecveodd3m1nni9.png-70.9kB
我们可以看到,webservice已经成功发布,通过http://localhost:8080/webservices我们可以查看所有的webservice:
image_1bhj7sghu15l9cq01bnj1b428jb13.png-27.7kB
我们再编写cxf客户端代码调用一下:

  1. package com.lxy.client;
  2. import org.apache.cxf.endpoint.Client;
  3. import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
  4. public class CxfClient {
  5. public static void main(String[] args) throws Exception{
  6. JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
  7. Client client = dcf.createClient("http://localhost:8080/webservices/hello?wsdl");
  8. Object[] objects = client.invoke("getName", "tom");
  9. System.out.println(objects[0].getClass());
  10. System.out.println(objects[0].toString());
  11. }
  12. }

image_1bhj84c88oq639hb2u1i04n2q1g.png-25.6kB
调用成功!
OK,webservice我们进行到这里。


接下来要进行socket编程了。
到这里就引发了我的思考,刚刚发布的webservice已经将8080端口占用了,如果socket继续监听8080肯定会引发端口冲突,我们如何进行统一端口之后无差别的进行协议适配呢?
感觉不太现实,不过还是先写个socket服务再说。
首先定义socket server服务端:

  1. package com.lxy.socket;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.InputStreamReader;
  6. import java.io.OutputStream;
  7. import java.io.PrintWriter;
  8. import java.net.ServerSocket;
  9. import java.net.Socket;
  10. public class SocketServer {
  11. public static void main(String[] args) throws IOException{
  12. ServerSocket serverSocket = new ServerSocket(8080);
  13. Socket socket = serverSocket.accept();
  14. InputStream is = socket.getInputStream();
  15. InputStreamReader isr = new InputStreamReader(is);
  16. BufferedReader br = new BufferedReader(isr);
  17. String info = "";
  18. while((info = br.readLine())!=null){
  19. System.out.println("Hello,我是服务器,客户端说:"+info);
  20. }
  21. socket.shutdownInput();
  22. OutputStream os = socket.getOutputStream();
  23. PrintWriter pw = new PrintWriter(os);
  24. pw.write("Hello World!");
  25. pw.flush();
  26. pw.close();
  27. os.close();
  28. br.close();
  29. isr.close();
  30. is.close();
  31. socket.close();
  32. serverSocket.close();
  33. }
  34. }

服务端编写完毕,直接启动。
image_1bhj8ag2s1p93n8l1v0f1i3q31h1t.png-25.7kB
不出所料,端口占用,我们姑且先改成8081,重新启动,启动成功!
我们继续编写客户端socket:

  1. package com.lxy.client;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.InputStreamReader;
  6. import java.io.OutputStream;
  7. import java.io.PrintWriter;
  8. import java.net.Socket;
  9. public class SocketClient {
  10. public static void main(String[] args) throws IOException{
  11. Socket socket = new Socket("127.0.0.1",8080);
  12. OutputStream os = socket.getOutputStream();
  13. PrintWriter pw = new PrintWriter(os);
  14. pw.write("用户名:admin;密码:admin");
  15. pw.flush();
  16. socket.shutdownOutput();
  17. InputStream is = socket.getInputStream();
  18. BufferedReader br = new BufferedReader(new InputStreamReader(is));
  19. String info = null;
  20. while((info=br.readLine()) != null){
  21. System.out.println("Hello,我是客户端,服务器说:"+info);
  22. }
  23. br.close();
  24. is.close();
  25. pw.close();
  26. os.close();
  27. socket.close();
  28. }
  29. }

启动客户端,查看server与client的打印日志:
客户端:
image_1bhj8f76jfqdi8mv6urrv1h7b2n.png-5.8kB
服务端:
image_1bhj8fmvskee16o6lokktb4ut34.png-5.9kB
那我们用socket客户端请求一下webservice的8080端口呢?
image_1bhj8ddnb1a318vkslo1i1a1p092a.png-16.8kB
我们可以看到,webservice不识别此请求,返回400报错。
webservice服务后台也对应报错:
image_1bhj8s553v6d1ih571runnpv63h.png-47.3kB

到现在为止,我感觉从代码层面来考虑这个问题已经不太现实。

考虑到实际项目是nginx负载均衡,对外暴露统一IP、端口,nginx有没有相关配置呢?

我了解到nginx 1.9.0版本开始增加了stream模块用于一般的TCP代理和负载均衡,但是也是要知道该IP和端口是用于TCP协议的才可以。nginx无法通过统一的IP、端口判断协议。

再有就是考虑是否可以在整个项目的维度上进行所有请求的拦截呢?包括http、socket、soap、ftp等请求。我们知道SpringMVC只是拦截所有的http请求,也就是浏览器请求,拦截TCP等协议的请求,一般都要动用防火墙或者黑客攻击技术,所以这点也没有办法实现。

对协议适配研究到这里,我给了我们领导一个NO的回复,很遗憾。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注