传统Spring项目使用FeignClient组件访问微服务

传统Spring项目使用

这里的传统 Spring项目指的是没有使用 spring bootspring项目,例如 ssm

api 文件

和在spring cloud 项目中使用 FeignClient 一样,不过这里在注解上加上了 url 配置, 注意这里 url 不要写死,采用占位符的形式,通过spring属性进行配置

package com.zyndev.server.user.api;

import com.zyndev.commontool.web.BaseResponse;
import com.zyndev.server.user.hystrix.UserServiceHystrix;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * Desc: 
 * Author: 张瑀楠 zyndev@gmail.com 
 * TODO:
 */
@FeignClient(name = "user-server",url = "${user-server-api.url}",path = "user",fallback = UserServiceHystrix.class)
public interface UserServiceAPI {

    @GetMapping("getUserByPhone")
    BaseResponse getUserByPhone(@RequestParam("phone") String phone);

    @GetMapping("getUserByAccountName")
    BaseResponse getUserByAccountName(@RequestParam("accountName") String accountName);

    @GetMapping("getUserByUID")
    BaseResponse getUserByUID(@RequestParam("uid") String uid);

}

其中通过动态配置 url 来实现在 spring cloud 外部调用接口

FeignClient注解源码

package org.springframework.cloud.netflix.feign;

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

import org.springframework.core.annotation.AliasFor;

/**
 * Annotation for interfaces declaring that a REST client with that interface should be
 * created (e.g. for autowiring into another component). If ribbon is available it will be
 * used to load balance the backend requests, and the load balancer can be configured
 * using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
 *
 * @author Spencer Gibb
 * @author Venil Noronha
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

    /**
     * The name of the service with optional protocol prefix. Synonym for {@link #name()
     * name}. A name must be specified for all clients, whether or not a url is provided.
     * Can be specified as property key, eg: ${propertyKey}.
     */
    @AliasFor("name")
    String value() default "";

    /**
     * The service id with optional protocol prefix. Synonym for {@link #value() value}.
     *
     * @deprecated use {@link #name() name} instead
     */
    @Deprecated
    String serviceId() default "";

    /**
     * The service id with optional protocol prefix. Synonym for {@link #value() value}.
     */
    @AliasFor("value")
    String name() default "";

    /**
     * Sets the <code>@Qualifier</code> value for the feign client.
     */
    String qualifier() default "";

    /**
     * An absolute URL or resolvable hostname (the protocol is optional).
     */
    String url() default "";

    /**
     * Whether 404s should be decoded instead of throwing FeignExceptions
     */
    boolean decode404() default false;

    /**
     * A custom <code>@Configuration</code> for the feign client. Can contain override
     * <code>@Bean</code> definition for the pieces that make up the client, for instance
     * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
     *
     * @see FeignClientsConfiguration for the defaults
     */
    Class<?>[] configuration() default {};

    /**
     * Fallback class for the specified Feign client interface. The fallback class must
     * implement the interface annotated by this annotation and be a valid spring bean.
     */
    Class<?> fallback() default void.class;

    /**
     * Define a fallback factory for the specified Feign client interface. The fallback
     * factory must produce instances of fallback classes that implement the interface
     * annotated by {@link FeignClient}. The fallback factory must be a valid spring
     * bean.
     *
     * @see feign.hystrix.FallbackFactory for details.
     */
    Class<?> fallbackFactory() default void.class;

    /**
     * Path prefix to be used by all method-level mappings. Can be used with or without
     * <code>@RibbonClient</code>.
     */
    String path() default "";

    /**
     * Whether to mark the feign proxy as a primary bean. Defaults to true.
     */
    boolean primary() default true;

}

在源码中可以看到比较有用的四个注解 name , url, fallback , path

  • name 指定微服务的实例名称,唯一,必填,通过实例名称可以得到实例对应的访问地址
  • fallback 配置熔断
  • url 配置一个绝对的地址访问,默认为空字符串,当其不空时,则使用该地址访问
  • path 配置一个所有方法级别的mappings 相当于在类上加 requestMapping, 例如上面的 UserServiceAPI 所有访问地址为 /user/xxx

注意: FeignClient 请求路径和 包名 无关,

/user/xxx1
/user/xxx2
/user/xxx3

如果想访问以上地址,api 有三种实现方式

  1. 在所有的方法上写明全路径 例如 @RequestMapping("/user/xxx1")
  2. 在类上写 @RequestMapping("user") 在对应方法写 @RequestMapping("xxx1")
  3. 使用 FeignClient 的 path 标注

配置

如果使用非 spring cloud,则应该在 api 的 FeignClient 注解上设置 url,例如例子程序

@FeignClient(name = "user-server",url = "${user-server-api.url}",path = "user",fallback = UserServiceHystrix.class)

在项目配置 properties 文件,这里我使用 server.properties

下面是我测试的时候自己起的网关地址

server.properties

user-server-api.url=localhost:8089/api/user-server/

这里配置的 spring mvc 项目,配置servlet 主要为了加载 application.xmlweb.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <display-name>Archetype Created Web Application</display-name>

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/application.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

application.xml

这里主要为了加载属性文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 包扫描,暂时扫描全部包 -->
    <context:component-scan base-package="com.renren" />

    <context:property-placeholder location="classpath:microserver/server.properties"></context:property-placeholder>

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

    <mvc:default-servlet-handler/>
    <mvc:annotation-driven/>

</beans>

添加配置类

package com.renren.config;

import com.google.gson.Gson;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
import org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author 张瑀楠 wb.yunan.zhang@renren-inc.com
 * @version 1.0
 * time: 2017/12/13 11:40
 * TODO:
 */
@ImportAutoConfiguration({RibbonAutoConfiguration.class, FeignRibbonClientAutoConfiguration.class, FeignAutoConfiguration.class})
@EnableFeignClients(basePackages={"com.zyndev.server.user.api"})
@Configuration
public class Config {

    @Bean
    public HttpMessageConverters customConverters() {

        Collection<HttpMessageConverter<?>> messageConverters = new ArrayList<>();

        GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
        messageConverters.add(gsonHttpMessageConverter);

        return new HttpMessageConverters(true, messageConverters);
    }

}

EnableFeignClients 设置对应的 api 路径,可设置多个,其中要配置一个 HttpMessageConverters 用来解码,可设置其他

测试

package com.renren.controller;

import com.zyndev.commontool.web.BaseResponse;
import com.zyndev.server.user.api.UserServiceAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 张瑀楠 wb.yunan.zhang@renren-inc.com
 * @version 1.0
 * time: 2017/12/13 16:23
 * TODO:
 */
@RestController
public class IndexController {

    @Autowired
    private UserServiceAPI userServiceAPI;

    @RequestMapping("testPhone")
    public Map testApi(@RequestParam("phone") String phone) {
        Map<String, Object> result = new HashMap<>();
        result.put("name", "testApi");
        if (userServiceAPI == null) {
            System.out.println("userServiceAPI is null");
        }
        else {
            BaseResponse baseResponse = userServiceAPI.getUserByPhone(phone);
            result.put("response", baseResponse);
        }
        return result;
    }

    @RequestMapping("testName")
    public Map testName(@RequestParam("name") String name) {
        Map<String, Object> result = new HashMap<>();
        result.put("name", "testApi");
        if (userServiceAPI == null) {
            System.out.println("userServiceAPI is null");
        }
        else {
            BaseResponse baseResponse = userServiceAPI.getUserByAccountName(name);
            result.put("response", baseResponse);
        }
        return result;
    }
}

userServiceAPI直接注入即可

对应pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.renren</groupId>
    <artifactId>spring-feign</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>spring-feign Maven Webapp</name>
    <url>http://maven.apache.org</url>

    <properties>
        <spring.version>4.3.8.RELEASE</spring.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.32</version>
        </dependency>

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>com.zyndev</groupId>
            <artifactId>user-server-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-eureka</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <finalName>spring-feign</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

小结

为了测试方便,这里使用了 springmvc 项目,同时引入一个 spring cloud 项目中已经写好的 feignclient 的 api,通过测试 springmvc 能否调通 feignclient 定义的接口来确定测试是否成功,在引入 feignclient 的依赖时,去除了 eureka 的依赖,起初我在配置 application.xml 时,添加了 mvc 的HttpMessageConverters 的支持,当我调用 feignclient 一直报错,提示我找不到 HttpMessageConverters 的bean,很苦恼,明明配置了HttpMessageConverters 为什么还注入不进入,最后直接 创建了一个 bean 这才成功,通过使用这种方式,可以在原来 ssm 或 ssh 项目结构不改变的情况下,使用 spring cloud 提供的 feignclient ,调用其他服务的接口,减小升级成本和风险

原文发布于微信公众号 - 全栈布道士(gh_773193545262)

原文发表时间:2017-12-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏好好学java的技术栈

SpringMVC+RestFul详细示例实战教程一(实现跨域访问+postman测试)

注意:由于文章篇幅太长,超出了字数,这是文章的第一部分,明天分享文章的第二部分,请见谅!

5572
来自专栏刘君君

一个feign使用不当的问题

以上配置在不熟悉feign-hystrix 或者查看 Feign Hystrix Fallbacks 的可能感觉并没有问题,项目启动也是正常

1483
来自专栏Ryan Miao

Spring-AOP实践 - 统计访问时间

公司的项目有的页面超级慢,20s以上,不知道用户会不会疯掉,于是老大说这个页面要性能优化。于是,首先就要搞清楚究竟是哪一步耗时太多。 我采用spring aop...

4768
来自专栏码匠的流水账

聊聊reactive streams的parallel flux

本文主要研究下reactive streams的flux的parallel运行方式.

1551
来自专栏Android中高级开发

Android开发之漫漫长途 IX——彻底掌握Binder

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

1102
来自专栏Java成神之路

Spring_总结_03_装配Bean(一)之自动装配

(2)当必须要显示配置的时候,再使用类型安全并且比XML更强大的JavaConfig

992
来自专栏ImportSource

Spring5以来注册Bean的各种姿势,特别最后的纯编码注册值得尝试

各位好,今天我们的内容是有关Spring 5以来有关注册bean的几种方式。前面两三个是比较常用的方式,最后两种是只有在特殊的场合下才会被用到和想到。我们会分别...

1.4K7
来自专栏好好学java的技术栈

SpringMVC+RestFul详细示例实战教程(实现跨域访问)

**REST(Representational State Transfer)**,中文翻译叫“表述性状态转移”。是 Roy Thomas Fielding 在...

2044
来自专栏石奈子的Java之路

原 荐 SpringBoot 2.0 系列0

2354
来自专栏有刻

Java 小记 — Spring Boot 注解

37313

扫码关注云+社区

领取腾讯云代金券