Spring cloud Zookeeper 系列二 之实现服务远程调用(feign)

一、项目总体介绍

        1、项目背景

                   我们在上一节用zookeer实现了将我们的微服务应用注册到zookeeper上并在程序中进行相关的应用信息的查询,在一个大的系统中每个微服务大多需要相互调用各种服务提供的API进行响应的操作,作为两个独立的应用他们是如何进行通讯的,当下的RPC框架有不少解决方案。

     2、功能介绍

                 今天我们用spring cloud组件Feign进行服务间的通讯。Feign内部实现了负载均衡,我们可以选择对应的负载均衡方式进行服务的转发。

3、项目模块介绍

                本次项目演示分为两个模块

                        1、spring-cloud-user-client

                                     用户客户端应用                                                                                                                                                                    

                        2、spring-cloud-user-server

                                    用户服务端应用

          

二、项目核心代码展示

             2.1、spring-cloud-user-service 模块介绍

                         2.1.1、项目目录结构

                              

                      2.1.2、项目核心代码展示

                              

------------------------项目依赖---------------------------------
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
 <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-zookeeper-all</artifactId>
      <version>2.1.2.RELEASE</version>
 </dependency>

 <dependency>
       <groupId>org.apache.zookeeper</groupId>
       <artifactId>zookeeper</artifactId>
       <version>3.4.12</version>
</dependency>
   
-------------------------数据库配置文件------------------------------
#应用名称
spring.application.name=spring-cloud-user-server

#HTTP访问端口
server.port=9095

------------------------启动文件application-------------------------
package com.gpdi;

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

@EnableDiscoveryClient
@SpringBootApplication
public class SpringCloudUserServerApplication {

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

}
--------------------------Controller文件---------------------------------------

package com.gpdi.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @GetMapping(value = "getMessage")
    public String getMessage(String name) {
        System.out.println("ServerController 接收到消息 - getMessage : " + name);

        return "Hello," + name;
    }

    @GetMapping(value = "sayHello")
    public String sayHello(String name) throws InterruptedException {
        System.out.println("ServerController 接收到消息 - say : " + name);
        return "Hello, " + name;
    }


    @Autowired
    private DiscoveryClient discoveryClient;


}

              2.2、spring-cloud-user-client

 

                          2.2.1、项目目录结构

                                       

                         2.2.2、项目核心代码

                                   

--------------------------项目依赖文件-----------------------------
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.0.0.RELEASE</version>
</dependency>

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


<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-zookeeper-all</artifactId>	
</dependency>

<dependency>
	 <groupId>org.apache.zookeeper</groupId>
	 <artifactId>zookeeper</artifactId>
</dependency>

-----------------------application配置文件------------------------------------
	
	
#服务名称
spring.application.name=spring-cloud-user-client

#HTTP访问端口
server.port=8082


-----------------------项目启动文件----------------------------------------------

package com.gpdi;
import com.gpdi.service.feign.UserService;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableFeignClients(clients = UserService.class) // 引入 FeignClient
@EnableDiscoveryClient
@SpringBootApplication
@ComponentScan(basePackages = {"com.gpdi.service.feign","com.gpdi.controller"})
@EnableScheduling
public class SpringCloudUserClientApplication {

	public static void main(String[] args) {
        new SpringApplicationBuilder(SpringCloudUserClientApplication.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
	}


}


----------------------用Feign实现调用的第一种方式(用RestTemplate进行调用)-------------


 //注入Ribbon的RestTemplate
    @LoadBalanced
    @Bean
    public RestTemplate loadBalancedRestTemplate() {
        return new RestTemplate();
    }


    @Autowired
    private RestTemplate restTemplate;



    /**
     * @desc:通过Ribbon的方式进行调用
     */
    @GetMapping(value = "/getMessage")
    public String getMessage(String name) {
        //服务名称+接口名称+参数 (/spring-cloud-user-server/getUser?name=1)
        return restTemplate.getForObject("http://spring-cloud-user-server/getMessage? 
                  name=" + name, String.class);
    }

------------------用Feign实现调用的第二种方式(用Feign发布接口进行调用)--------------

package com.gpdi.service.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "spring-cloud-user-server")
public interface UserService {

     @GetMapping(value="/sayHello")
     String sayHello(@RequestParam("name") String name);
}


 /**
    @desc:通过Feign的方式进行调用,
  */
    @GetMapping(value = "/sayHello")
    public String sayHello(@RequestParam String name) {
        return helloService.sayHello(name);
    }


    @Autowired
    private UserService helloService;

---------------------第三种方式:自定RestTemplate(包含自定义负载均衡算法)----------------


package com.gpdi.annotation;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.*;

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface CustomizedLoadBalanced {
}



package com.gpdi.annotation;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class RequestMappingMethodInvocationHandler implements InvocationHandler {

    private final ParameterNameDiscoverer parameterNameDiscoverer
            = new DefaultParameterNameDiscoverer();

    private final String serviceName;

    private final BeanFactory beanFactory;

    public RequestMappingMethodInvocationHandler(String serviceName,
                                                 BeanFactory beanFactory) {
        this.serviceName = serviceName;
        this.beanFactory = beanFactory;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 过滤 @RequestMapping 方法
        GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
        if (getMapping != null) {
            // 得到 URI
            String[] uri = getMapping.value();
            // http://${serviceName}/${uri}
            StringBuilder urlBuilder = new StringBuilder("http://").append(serviceName).append("/").append(uri[0]);
            // 获取方法参数数量
            int count = method.getParameterCount();
            // 方法参数是有顺序
            // FIXME
//            String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
            // 方法参数类型集合
            Class<?>[] paramTypes = method.getParameterTypes();
            Annotation[][] annotations = method.getParameterAnnotations();
            StringBuilder queryStringBuilder = new StringBuilder();
            for (int i = 0; i < count; i++) {
                Annotation[] paramAnnotations = annotations[i];
                Class<?> paramType = paramTypes[i];
                RequestParam requestParam = (RequestParam) paramAnnotations[0];
                if (requestParam != null) {
                    String paramName = "";
//                            paramNames[i];
                    // HTTP 请求参数
                    String requestParamName = StringUtils.hasText(requestParam.value()) ? requestParam.value() :
                            paramName;
                    String requestParamValue = String.class.equals(paramType)
                            ? (String) args[i] : String.valueOf(args[i]);
                    // uri?name=value&n2=v2&n3=v3
                    queryStringBuilder.append("&")
                            .append(requestParamName).append("=").append(requestParamValue);
                }
            }

            String queryString = queryStringBuilder.toString();
            if (StringUtils.hasText(queryString)) {
                urlBuilder.append("?").append(queryString);
            }

            // http://${serviceName}/${uri}?${queryString}
            String url = urlBuilder.toString();

            // 获取 RestTemplate , Bean 名称为“loadBalancedRestTemplate”
            // 获得 BeanFactory
            RestTemplate restTemplate = beanFactory.getBean("loadBalancedRestTemplate", RestTemplate.class);

            return restTemplate.getForObject(url, method.getReturnType());

        }
        return null;
    }
}
package com.gpdi.loadbalance;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.stream.Collectors;

public class LoadBalancedRequestInterceptor implements ClientHttpRequestInterceptor {

    // Map Key service Name , Value URLs
    private volatile Map<String, Set<String>> targetUrlsCache = new HashMap<>();

    @Autowired
    private DiscoveryClient discoveryClient;

    @Scheduled(fixedRate = 10 * 1000) // 10 秒钟更新一次缓存
    public void updateTargetUrlsCache() { // 更新目标 URLs
        // 获取当前应用的机器列表
        // http://${ip}:${port}
        Map<String, Set<String>> newTargetUrlsCache = new HashMap<>();
        discoveryClient.getServices().forEach(serviceName -> {
            List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceName);
            Set<String> newTargetUrls = serviceInstances
                    .stream()
                    .map(s ->
                            s.isSecure() ?
                                    "https://" + s.getHost() + ":" + s.getPort() :
                                    "http://" + s.getHost() + ":" + s.getPort()
                    ).collect(Collectors.toSet());
            newTargetUrlsCache.put(serviceName, newTargetUrls);
        });

        this.targetUrlsCache = newTargetUrlsCache;
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        //  URI:  "/" + serviceName + "/say?message="
        URI requestURI = request.getURI();
        String path = requestURI.getPath();
        String[] parts = StringUtils.split(path.substring(1), "/");
        String serviceName = parts[0]; // serviceName
        String uri = parts[1];  // "/say?message="
        // 服务器列表快照
        List<String> targetUrls = new LinkedList<>(targetUrlsCache.get(serviceName));
        int size = targetUrls.size();
        // size =3 , index =0 -2
        int index = new Random().nextInt(size);
        // 选择其中一台服务器
        String targetURL = targetUrls.get(index);
        // 最终服务器 URL
        String actualURL = targetURL + "/" + uri + "?" + requestURI.getQuery();
        // 执行请求

        System.out.println("本次请求的 URL : " + actualURL);

        URL url = new URL(actualURL);

        URLConnection urlConnection = url.openConnection();

        // 响应头
        HttpHeaders httpHeaders = new HttpHeaders();
        // 响应主体
        InputStream responseBody = urlConnection.getInputStream();

        return new SimpleClientHttpResponse(httpHeaders, responseBody);
    }

    private static class SimpleClientHttpResponse implements ClientHttpResponse {

        private HttpHeaders headers;

        private InputStream body;

        public SimpleClientHttpResponse(HttpHeaders headers, InputStream body) {
            this.headers = headers;
            this.body = body;
        }

        @Override
        public HttpStatus getStatusCode() throws IOException {
            return HttpStatus.OK;
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return 200;
        }

        @Override
        public String getStatusText() throws IOException {
            return "OK";
        }

        @Override
        public void close() {

        }

        @Override
        public InputStream getBody() throws IOException {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }


}




    //自定义负载均衡
    @Autowired // 依赖注入自定义 RestTemplate Bean
    @CustomizedLoadBalanced
    private RestTemplate myRestTemplate;

    @Bean
    @Autowired
    @CustomizedLoadBalanced
    public RestTemplate restTemplate() { // 依赖注入
        return new RestTemplate();
    }

 /**
     * @desc:通过自定义RestTemplate的方式进行服务调用
     */
    @GetMapping(value = "/getMessageOfMy")
    public String myLoadBalance(String name) {
        return myRestTemplate.getForObject("/spring-cloud-user-server/getMessage?name=" + name, String.class);

    }



                                    

 

                      

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值