非SpringCloud如何調用SpringCloud服務

前言

SpringCloud 其良好的背景以及社區非常高的活躍度,使其發展迅速,成爲微服務實施的首選框架。
如果是新的業務考慮使用SpringCloud來進行實現,面臨的一個比較嚴峻的問題就是老的應用如何訪問SpringCloud微服務,因爲目前可見的SpringCloud客戶端無論是Ribbon還是Feign都必須在SpringCloud中使用,但是老應用的架構什麼樣的都有,因此實現一個簡單的通過API訪問SpringCloud的應用是當務之急。

設計目標

1:可以動態更新Eureka的服務信息,和Eureka的信息基本同步。
2:可以發現服務是否可用。
3:可以發現服務實例是否可用。
4:客戶端可以實現簡單負載均衡(類似Ribbon,隨機路由,哈希路由)
5:客戶端和服務器端規範通接口通信。
6:客戶端動態代理實現接口調用。

研究階段

如何同Eureka進行通信是關鍵,可以通過Eureka獲得服務URL,這樣就可以發送請求。
關鍵接口EurekaClient。這個接口的實例過程會自動加載EurekaClient配置文件。會主動同Eureka建立連接,定時任務啓動,去和Eureka同步信息。有了這個其他的都好辦了。
問我如何找到這個EurekaClient的,那就去http://bbs.springcloud.cn 去和大家交流,一定會有幫助的。
找到這個,那就得看如何獲取實例,目前獲取實例的方法有些土,等有時間再詳細研究更科學方法。

//最關鍵的代碼,加載配置文件,向Eureka發送請求,獲取服務列表。
DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(), new DefaultEurekaClientConfig());
ApplicationInfoManager.getInstance().setInstanceStatus(InstanceStatus.UP);
EurekaClient client = DiscoveryManager.getInstance().getEurekaClient();

//獲取從Eureka獲取的全部的應用列表
Applications apps = client.getApplications();
//根據應用的名稱獲取已經可用的應用對象,可能是註冊了多個。
Application app=apps.getRegisteredApplications(serviceName);
String reqUrl=null;
if(app!=null)
{       
    List<InstanceInfo> instances = app.getInstances();      
    if(instances.size()>0)
    {
        //獲取其中一個應用實例,這裏可以添加路由算法
        InstanceInfo instance = instances.get(0);               
        //獲取公開的地址和端口           
        reqUrl="http://"+instance.getIPAddr()+":"+instance.getPort();                       
    }
}123456789101112131415161718192021

基本上前期的工作就完成了,啓動後,可以看到客戶端定時去Eureka進行更新。

完整代碼:TestClientMain.java

public class TestClientMain
{
    public static void main(String[] args)
    {
        //這個名稱需要用你的服務的名稱替換
        String serviceName="TESTSERVICE";
        //最關鍵的代碼,加載配置文件,向Eureka發送請求,獲取服務列表。
        DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(), new DefaultEurekaClientConfig());
        ApplicationInfoManager.getInstance().setInstanceStatus(InstanceStatus.UP);
        EurekaClient client = DiscoveryManager.getInstance().getEurekaClient();

        //獲取從Eureka獲取的全部的應用列表
        Applications apps = client.getApplications();
        //根據應用的名稱獲取已經可用的應用對象,可能是註冊了多個。
        Application app=apps.getRegisteredApplications(serviceName);
        String reqUrl=null;
        if(app!=null)
        {       
            List<InstanceInfo> instances = app.getInstances();      
            if(instances.size()>0)
            {
                //獲取其中一個應用實例,這裏可以添加路由算法
                InstanceInfo instance = instances.get(0);               
                //獲取公開的地址和端口
                reqUrl="http://"+instance.getIPAddr()+":"+instance.getPort();                       
            }
        }
        System.out.println("輸出URL="+reqUrl);
    }
}123456789101112131415161718192021222324252627282930

eureka-client.properties

eureka.region=default
eureka.name=sampleEurekaClient
eureka.preferSameZone=true
eureka.shouldUseDns=false
eureka.serviceUrl.default=http://localhost:1111/eureka/12345

可以發現這條日誌

DEBUG org.apache.http.wire - >> “GET /eureka/apps/ HTTP/1.1[\r][\n]”

也可以發現類似

輸出URL=http://...:3333

獲得了Application 的Instance的URL,就可以發送REST請求了。OK基於這思路開始我們的實現過程。

實現階段

包括了5個不同的maven工程。

1:MyRibbon
2:MyRibbonInterface
3:MyRibbonService
4:EurekaServer
5:MyRibbonClient

MyRibbon這個工程是我們實現的公共API,提供了對SpringCloud引用的調用。
MyRibbonInterface這個工程師定義了服務接口API,面向接口進行編程,具體服務提供者(MyRibbonService)需要實現這些接口。
MyRibbonService具體的服務實現,註冊在Eureka,爲客戶端提供調用。
EurekaServer一個Eureka的服務器
MyRibbonClient這個應用就是我們測試的客戶端應用。


MyRibbon工程

pom.xml 

<modelVersion>4.0.0</modelVersion>
<groupId>org.lipengbin.spring</groupId>
<artifactId>MyRibbon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.5.RELEASE</version>
        <relativePath />
</parent>
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty</artifactId>
            <version>6.1.26</version>
        </dependency>
</dependencies>
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Brixton.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</depende
定義兩個註解 
MyService,MyMethod

MyService.java

package org.lipengbin.myribbon.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定義接口服務的時候,需要指定這個接口服務對應在Eureka上對應的服務名稱。
 * 需要根據這個服務的名稱獲取對應的Application。
 * @author Administrator
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService
{
    String value() default "";

}1234567891011121314151617181920

MyMethod.java

package org.lipengbin.myribbon.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 在服務接口的方法上使用。
 * 主要是爲了拼裝URL和參數
 * 
 * http://ip:port/MyMethod?a=10&b=20
 * 
 * @author Administrator
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethod
{
    String value() default "";
}12345678910111213141516171819202122

MyEurekaClient.java

package org.lipengbin.myribbon.eureka;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;

import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.MyDataCenterInstanceConfig;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.DefaultEurekaClientConfig;
import com.netflix.discovery.DiscoveryManager;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;

/**

  • 同EurekaServer建立連接
  • 負責定時更新
  • 負責獲取指定的Service
  • 外部不需要調用這個類
  • 這個類是個單例
  • @author Administrator
  • */br/>@SuppressWarnings("deprecation")
    class MyEurekaClient
    {

    
    private static final Logger logger = LoggerFactory.getLogger(MyEurekaClient.class);
    
    private EurekaClient client;
    
    @Autowired
    private  RestTemplate restTemplate;
    
    protected MyEurekaClient()
    {       
        if(restTemplate==null)
        {
            restTemplate=new RestTemplate();
        }
        init();
    }
    
    protected void init()
    {
        DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(), new DefaultEurekaClientConfig());
        ApplicationInfoManager.getInstance().setInstanceStatus(InstanceStatus.UP);
        client = DiscoveryManager.getInstance().getEurekaClient();
    }
    
    /**
     * 根據Service名稱和請求的,獲取返回內容!
     * @param serviceName
     * @param url
     * @return
     */
    public <T> T request(String serviceName,String url,Class<T> returnClaz)
    {
        Applications apps = client.getApplications();
        Application app=apps.getRegisteredApplications(serviceName);
        if(app!=null)
        {
            List<InstanceInfo> instances = app.getInstances();      
            if(instances.size()>0)
            {
                try
                {
                    InstanceInfo instance = instances.get(0);               
                    String reqUrl="http://"+instance.getIPAddr()+":"+instance.getPort()+"/"+url;                
                    return restTemplate.getForEntity(reqUrl, returnClaz).getBody();
                }
                catch(Exception e)
                {
                    logger.error("request is error。["+serviceName+"]",e);
                    return null;
                }
            }
            else
            {
                logger.error("Application instance not exist。["+serviceName+"]");
                return null;
            }
        }
        else
        {
            logger.error("Target Application not exist。["+serviceName+"]");
            return null;
        }
    
    }
    }1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495

MyEurekaHandler.java

package org.lipengbin.myribbon.eureka;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;

import org.lipengbin.myribbon.annotation.MyMethod;
import org.lipengbin.myribbon.annotation.MyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;

/**

  • 利用動態代理封裝了接口的遠程調用。
  • @author Administrator
  • */
    public class MyEurekaHandler implements InvocationHandler
    {

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

    /**

    • 代理的目標接口類
      */
      private Class<?> target;

    /**

    • Eureka中定義的Service的名稱
      */
      private String serviceName;

    private MyEurekaClient client;

    MyEurekaHandler(MyEurekaClient client)
    {
    this.client=client;
    }

    @SuppressWarnings("unchecked")
    public <T> T create(Class<T> target)
    {
    this.target=target;
    MyService s=target.getAnnotation(MyService.class);
    if(s!=null)
    {
    serviceName=s.value().toUpperCase();
    return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{target}, this);
    }
    else
    {
    logger.error(target.getName()+",沒有定義 @MyService!");
    return null;
    }
    }

    /**

    • 獲取服務的名稱
    • @return
      */
      public String getService()
      {
      return this.serviceName;
      }

    /**

    /**

    • @param method
    • @param args
    • @return
      */
      protected String parseBySpring(Method method, Object[] args)
      {
      StringBuilder builder=new StringBuilder();
      //以下這種獲取參數名稱的解析,需要在編譯的時候設置一下。見圖
      ParameterNameDiscoverer parameterNameDiscoverer =new DefaultParameterNameDiscoverer();
      // new LocalVariableTableParameterNameDiscoverer(); 這個不知道爲什麼不好使?ASM5以上。設置都是OK的
      try
      {
      System.out.println(method.getName());

      String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
      for(int i=0;i<args.length;i++)
      {
          if(i>0)
          {
              builder.append("&");
          }               
      
          builder.append(parameterNames[i]);
          builder.append("=");
          builder.append(args[i]);
      }
      return builder.toString();

      }
      catch(Exception e)
      {
      e.printStackTrace();
      return null;
      }
      }
      }

這裏需要說一下兩個參數Discoverer。
DefaultParameterNameDiscoverer:這個使用的Java8新提供的Parameter獲取參數名稱。
這種方式和parseByJava8基本上是一樣的。
需要在Eclipse中進行設置。

LocalVariableTableParameterNameDiscoverer:這個是使用ASM獲取參數名稱。
需要在Eclipse進行設置。

MyEurekaFactory.java
這個類是爲外界提供調用的,使用方式很簡單,後面會寫一個完整的demo例子.

package org.lipengbin.myribbon.eureka;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**

  • 這個工廠類管理了當前應用需要調用或者已經調用的接口實例。
  • 因爲通過動態代理對服務進行了封裝。
  • 因此對於一個應用來講,是需要對服務實例進行管理的,否則每次都會進行創建。
  • @author Administrator
  • */

public class MyEurekaFactory
{
private static final Logger logger = LoggerFactory.getLogger(MyEurekaFactory.class);
/**

  • 當前應用對應的服務Map
    */
    public Map<Class<?>,Object> serviceMap=new HashMap<>();

    private static MyEurekaFactory factory;

    private static ReentrantLock lock=new ReentrantLock();

    private MyEurekaClient client;

    /**

  • 這個是當前的啓動入口
    */
    private MyEurekaFactory()
    {
    client=new MyEurekaClient();
    }
    /**
  • 單例模式創建對象,當然可以通過註解的形式進行創建
  • @return
    */
    public static MyEurekaFactory gi()
    {
    if(factory==null)
    {
    lock.lock();
    if(factory==null)
    {
    factory=new MyEurekaFactory();
    }
    lock.unlock();
    }
    return factory;
    }

    @SuppressWarnings("unchecked")
    public <T> T createService(Class<T> serviceInterface)
    {
    if(serviceMap.containsKey(serviceInterface))
    {
    logger.debug("Service存在" +serviceInterface);
    return (T)serviceMap.get(serviceInterface);
    }
    else
    {
    logger.debug("Service不存在" +serviceInterface);
    return add(serviceInterface);
    }
    }
    /**

  • 此處未做同步,因爲創建多個實例會被覆蓋,不會出現問題!
  • 只會影響首次的創建效率
  • @param serviceInterface
  • @return
    */
    private <T> T add(Class<T> serviceInterface)
    {
    MyEurekaHandler handler=new MyEurekaHandler(client);
    T t=handler.create(serviceInterface);
    serviceMap.put(serviceInterface, t);
    return t;
    }
    }1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586

MyRibbonInterface工程

pom.xml

<modelVersion>4.0.0</modelVersion>
<groupId>org.lipengbin.spring</groupId>
<artifactId>MyRibbonInterface</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.lipengbin.spring</groupId>
<artifactId>MyRibbon</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>1234567891011

MyValue.java
簡單的返回對象

package org.lipengbin.test;

public class MyValue
{
private String name;

private int value;

public String getName()
{
    return name;
}

public void setName(String name)
{
    this.name = name;
}

public int getValue()
{
    return value;
}

public void setValue(int value)
{
    this.value = value;
}

}12345678910111213141516171819202122232425262728

定義的服務接口

package org.lipengbin.test;

import org.lipengbin.myribbon.annotation.MyMethod;
import org.lipengbin.myribbon.annotation.MyService;

@MyService("TestService")
public interface TestServicebr/>{
@MyMethod("execute")
public MyValue execute(String name,Integer value);
}1234567891011

MyRibbonService工程

pom.xml
1

<modelVersion>4.0.0</modelVersion>
<groupId>org.lipengbin.spring</groupId>
<artifactId>MyRibbonService</artifactId>
<version>0.0.1-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.lipengbin.spring</groupId>
<artifactId>MyRibbon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.lipengbin.spring</groupId>
<artifactId>MyRibbonInterface</artifactId>
<version>0.0.1-SNAPSHOT</version>
<type>jar</type>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

TestServiceImpl.java

package org.lipengbin.spring.test;

import org.lipengbin.test.MyValue;
import org.lipengbin.test.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestServiceImpl implements TestServicebr/>{
@Autowired
private DiscoveryClient client;

@RequestMapping(value = "/execute" ,method = RequestMethod.GET) 
public MyValue execute(@RequestParam  String name,@RequestParam  Integer value)
{
    System.out.println("------------------>");
    ServiceInstance instance = client.getLocalServiceInstance();
    MyValue myvalue=new MyValue();
    myvalue.setName("name="+name);
    myvalue.setValue(100+value);
    return myvalue;
}

}

TestServiceApplication.java

package org.lipengbin.spring.test;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClientbr/>@SpringBootApplication
public class TestServiceApplication
{
public static void main(String[] args)
{
new SpringApplicationBuilder(TestServiceApplication.class).web(true).run(args);
}
}123456789101112131415

application.properties

spring.application.name=TestService
server.port=3333
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/123

EurekaServer工程

pom.xml

<modelVersion>4.0.0</modelVersion>
<groupId>org.lipengbin</groupId>
<artifactId>Eureka-Server</artifactId>
<version>0.0.1-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.26</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

EurekaServer.java

package org.lipengbin;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServerbr/>@SpringBootApplication
public class EurekaServer
{

public static void main(String[] args)
{
    new SpringApplicationBuilder(EurekaServer.class).web(true).run(args);
}

}

MyRibbonClient工程

pom.xml

<modelVersion>4.0.0</modelVersion>
<groupId>org.lipengbin.spring</groupId>
<artifactId>MyRibbonClient</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.lipengbin.spring</groupId>
<artifactId>MyRibbon</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.lipengbin.spring</groupId>
<artifactId>MyRibbonInterface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>12345678910111213141516

eureka-client.properties

eureka.region=default
eureka.name=sampleEurekaClient
eureka.preferSameZone=true
eureka.shouldUseDns=false
eureka.serviceUrl.default=http://localhost:1111/eureka/12345

ClientMain.java

package org.lipengbin.spring.test.client;

import org.lipengbin.myribbon.eureka.MyEurekaFactory;
import org.lipengbin.test.MyValue;
import org.lipengbin.test.TestService;

public class ClientMain
{

public static void main(String[] args)
{       
    TestService service=MyEurekaFactory.gi().createService(TestService.class);
    MyValue value=service.execute("joy", 8);
    System.out.println(value.getName()+","+value.getValue());

}

}

執行過程

1:將MyRibbon執行mvn install安裝到本地倉庫
2:將MyRibbonInterface 執行mvn install安裝到本地倉庫
3:關閉MyRibbon,和MyRibbonInterface,強制讓其他工程引入jar包。
4:啓動EurekaServer
5:啓動MyRibbonService
6:運行客戶端MyRibbonClient

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章