• 06 RestTemplate负载均衡


      本例将模仿拦截器机制,实现一个简单的ReatTemplate,以便更清楚地展示@LoadBalanced以及RestTemplate的原理。

    1.@LoadBalanced注解概述

      RestTemplate本是spring-web项目中的一个REST客户端,它遵循REST的设计原则,提供简单的API让我们去调用HTTP服务。RestTemplate本身并不具备负载均衡的功能,该类也与SpringCloud没有关系,但是为什么加入@LoadBalanced注解后,一个RestTemplate实例就具有了负载均衡的功能呢?实际上这要得益于RestTemplate的拦截器的功能。在Spring Cloud中,使用@LoadBalanced注解修饰后的RestTemplate,在Spring容器启动时,会为这些被修饰过的RestTemplate添加拦截器,拦截器中使用了LoadBalancerClient来处理请求,LoadBalancerClient本来就是Spring封装的负载均衡客户端,通过这样的间接处理,值得RestTemplate拥有了负载均衡的功能。

    2.项目创建及测试

      新建一个项目,项目的目录结构如图所示

       

      2.1编写自定义注解以及拦截器

      模仿@LoadBalanced注解,编写一个自定义注解,代码清单如下

    package com.triheart.resttemplatetest;
    
    import org.springframework.beans.factory.annotation.Qualifier;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author 阿遠
     * Date: 2018/8/27
     * Time: 21:56
     */
    @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface MyLoadBalanced {
    }

      MyLoadBalanced 注解中使用了@Qualifier限定注解,接下来编写自定义的拦截器,代码清单如下

    package com.triheart.resttemplatetest;
    
    import org.springframework.http.HttpRequest;
    import org.springframework.http.client.ClientHttpRequestExecution;
    import org.springframework.http.client.ClientHttpRequestInterceptor;
    import org.springframework.http.client.ClientHttpResponse;
    
    import java.io.IOException;
    
    /**
     * 自定义拦截器
     * @author 阿遠
     * Date: 2018/8/27
     * Time: 21:57
     */
    public class MyInterceptor implements ClientHttpRequestInterceptor {
        public ClientHttpResponse intercept(HttpRequest request, byte[] body,
                                            ClientHttpRequestExecution execution) throws IOException {
            System.out.println("=============  这是自定义拦截器实现");
            System.out.println("               原来的URI:" + request.getURI());
            // 换成新的请求对象(更换URI)
            MyHttpRequest newRequest = new MyHttpRequest(request);
            System.out.println("               拦截后新的URI:" + newRequest.getURI());
            return execution.execute(newRequest, body);
        }
    }

      在自定义拦截器MyInterceptor中,实现了intercept方法,该方法会将原来的HttpRequest对象转换为我们自定义的MyHttpRequest,MyHttpRequest是一个自定义的请求类,实现请求的代码清单如下

    package com.triheart.resttemplatetest;
    
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.HttpRequest;
    
    import java.net.URI;
    
    /**
     * 自定义请求类
     * @author 阿遠
     * Date: 2018/8/27
     * Time: 21:59
     */
    public class MyHttpRequest implements HttpRequest {
    
        private HttpRequest sourceRequest;
    
        public MyHttpRequest(HttpRequest sourceRequest) {
            this.sourceRequest = sourceRequest;
        }
    
        public HttpHeaders getHeaders() {
            return sourceRequest.getHeaders();
        }
    
        public HttpMethod getMethod() {
            return sourceRequest.getMethod();
        }
    
        /**
         * 将URI转换
         */
        public URI getURI() {
            try {
                URI newUri = new URI("http://localhost:8080/hello");
                return newUri;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return sourceRequest.getURI();
        }
    
    }

      在MyHttpRequest类中,会将原来请求的URI进行改写,只要使用了这个对象,所有的请求都会被转发到http://localhost:8080/hello这个地址。Spring Cloud在对RestTemplate进行拦截的时候也做了同样的事情,只不过并没有像这里一样固定了URI,而是对“源请求”进行了更灵活的处理。

      2.2使用自定义拦截器及注解

      编写一个Spring的配置类,在初始化的Bean中为容器中的RestTemplate实例设置自定义拦截器,自动配置类代码清单如下

    package com.triheart.resttemplatetest;
    
    import org.springframework.beans.factory.SmartInitializingSingleton;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * @author 阿遠
     * Date: 2018/8/27
     * Time: 22:01
     */
    @Configuration
    public class MyAutoConfiguration {
    
        @Autowired(required=false)
        @MyLoadBalanced
        private List<RestTemplate> myTemplates = Collections.emptyList();
    
        @Bean
        public SmartInitializingSingleton myLoadBalancedRestTemplateInitializer() {
            System.out.println("====  这个Bean将在容器初始化时创建    =====");
            return new SmartInitializingSingleton() {
    
                public void afterSingletonsInstantiated() {
                    for(RestTemplate tpl : myTemplates) {
                        // 创建一个自定义的拦截器实例
                        MyInterceptor mi = new MyInterceptor();
                        // 获取RestTemplate原来的拦截器
                        List list = new ArrayList(tpl.getInterceptors());
                        // 添加到拦截器集合
                        list.add(mi);
                        // 将新的拦截器集合设置到RestTemplate实例
                        tpl.setInterceptors(list);
                    }
                }
            };
        }
    }

      在配置类中定义了RestTemplate实例的集合,并且使用了@MyLoadBalanced以及@Autowired注解进行修饰,@MyLoadBalanced中含有@Qualifier注解。简单的说就是在Spring容器中使用了@MyLoadBalanced修饰的RestTemplate实例,该实例将会被加入到配置类的RestTemplate集合中。在容器初始化时,Spring会调用myLoadBalancedRestTemplateInitializer方法来创建Bean,该Bean在初始化完成后,会遍历RestTemplate集合并为它们设置“自定义拦截器”。

      2.3在控制器中使用RestTemplate

      控制器代码清单如下

    package com.triheart.resttemplatetest;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.MediaType;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @author 阿遠
     * Date: 2018/8/27
     * Time: 22:03
     */
    @RestController
    @Configuration
    public class InvokerController {
    
        @Bean
        @MyLoadBalanced
        public RestTemplate getMyRestTemplate() {
            return new RestTemplate();
        }
    
        /**
         * 浏览器访问的请求
         */
        @RequestMapping(value = "/router", method = RequestMethod.GET,
                produces = MediaType.APPLICATION_JSON_VALUE)
        public String router() {
            RestTemplate restTpl = getMyRestTemplate();
            // 根据名称来调用服务,这个URI会被拦截器所置换
            String json = restTpl.getForObject("http://my-server/hello", String.class);
            return json;
        }
    
        /**
         * 最终的请求都会转到这个服务
         */
        @RequestMapping(value = "/hello", method = RequestMethod.GET)
        public String hello() {
            return "Hello World";
        }
    }

      注意控制器中的hello方法,前面实现的拦截器会将全部的请求都转到这个服务中。控制器的RestTemplate使用了@MyLoadBalanced注解进行修饰。在控制器中router方法中,使用这个被拦截过的RestTemplate发送请求。打开浏览器,访问http://localhost:8080/router,可以看到如下界面

     

      可以看到,实际上是调用了hello服务。

    3.总结

      本例展示了RestTemplate的原理,Spring Cloud对RestTemplate的拦截器实现更加复杂,并且在拦截器中使用LoadBalancerClient来实现请求的负载均衡功能。我们在实际环境中,并不需要实现自定义的注解以及拦截器,Spring已经帮我们提供了现成的API。

      一般情况下,Spring已经帮我们封装好了Ribbon,我们直接调用RestTemplate等API来访问服务即可实现负载均衡。

  • 相关阅读:
    SpringMVC使用ResponseEntity实现文件下载,及图片base64的字节数组上传于下载
    File类的createNewFile()和mkdirs() mkdir()
    为什么Java里的Arrays.asList不能用add和remove方法?
    java如何向数组里添加元素
    java里getPath、 getAbsolutePath、getCanonicalPath的区别
    MultipartFile 类
    File类的createNewFile()与createTempFile()的区别
    IntelliJ Idea解决Could not autowire. No beans of 'xxxx' type found的错误提示
    FileInputStream读取的两种方法:逐字节读;以字节数组读取
    JCE cannot authenticate the provider BC
  • 原文地址:https://www.cnblogs.com/a-yuan/p/9545344.html
Copyright © 2020-2023  润新知