龙空技术网

openfeign的实际应用

漫谈互联网技术 112

前言:

现在小伙伴们对“前端把token加到请求头”大致比较注意,各位老铁们都想要分析一些“前端把token加到请求头”的相关文章。那么小编也在网络上收集了一些关于“前端把token加到请求头””的相关资讯,希望大家能喜欢,小伙伴们一起来了解一下吧!

1、openfeign简介

在谈openfeign之前,我们先说下另一个框架:feign。feign是一个声明式的web服务客户端框架,它提供了一些注解,只要将它们加到某个接口和接口内部的方法上,那就可以很方便地直接用这个接口来开发http客户端代码。再细化来说,就是能让我们写更少的代码来访问服务端的http接口。拿我们熟知的jdk自带的HttpURLConnection类来说,我们要是使用它来访问http接口,加上解析http接口响应的数据,写个十几二十几行代码都是很正常的,就算是用apache的httpClient框架,也是如此。而通过使用feign,我们只需要调用接口的某个方法,就能完成http接口调用。

我们再来说下openfeign,它是Spring Cloud官方提供的,是在feign框架的基础上加了一些对spring mvc注解的支持,同时也支持我们开发spring web应用时所使用的一系列http消息转换器(当然这些基本都是spring框架内置的功能)。而且作为Spring Cloud官方的产品,它天然支持使用eureka、Spring Cloud CircuitBreaker和Spring Cloud LoadBalancer等组件,能使feign接口具备负载均衡的能力,就像咱们之前聊的restTemplate那样。

当然,feign和openfeign都是用来开发客户端代码的,意思就是这部分代码只能给客户端用,客户端通过这些代码来访问相应的服务端接口,而且无论是使用feign,还是使用openfeign,这部分代码基本都是要由服务端来完成开发,然后提供给客户端使用。就像我们开发dubbo服务一样,会提供相应的包含服务接口的jar包给客户端使用(后续会写文章来聊聊dubbo相关的知识)。

2、openfeign实战

和以前一样,针对技术讲解,我们会先聊聊技术的实际应用,然后再说原理。毕竟我们先会用,才更容易去理解它的原理。那么本部分我们来说下openfeign在实际工程中的应用。

2.1、创建openfeign工程

通过idea工具创建一个名为openfeign的Maven工程,工程的pom文件内容如下:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="; xmlns:xsi=";  xsi:schemaLocation=" ;>  <modelVersion>4.0.0</modelVersion>  <groupId>com.xk</groupId>  <artifactId>openfeign</artifactId>  <version>1.0</version>  <packaging>pom</packaging>  <name>openfeign</name>  <!-- FIXME change it to the project's website -->  <url>;/url>  <modules>    <module>springcloud-openfeign-provider</module>    <module>springcloud-openfeign-consumer</module>  </modules>  <properties>    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    <maven.compiler.source>8</maven.compiler.source>    <maven.compiler.target>8</maven.compiler.target>  </properties>  <dependencies>    <dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>      <version>4.11</version>      <scope>test</scope>    </dependency>  </dependencies>          <dependencyManagement>        <!--版本关系  -->        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>2021.0.3</version>                <type>pom</type>                <scope>import</scope>            </dependency>            <dependency>                <groupId>com.alibaba.cloud</groupId>                <artifactId>spring-cloud-alibaba-dependencies</artifactId>                <version>2021.0.4.0</version>                <type>pom</type>                <scope>import</scope>            </dependency>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter-parent</artifactId>                <version>2.7.0</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies>    </dependencyManagement></project>

然后我们再在该工程下面创建两个子模块:

模块备注springcloud-openfeign-provider服务提供者,采用springboot开发,用来提供http接口,供消费者调用springcloud-openfeign-consumer服务消费者,采用springboot开发,用来通过openfeign调用服务提供者的http接口

2.2、创建工程子模块2.2.1、创建springcloud-openfeign-provider模块

provider的pom文件如下:

<?xml version="1.0" encoding="UTF-8"?><project xmlns=";         xmlns:xsi=";         xsi:schemaLocation=" ;>    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.xk</groupId>        <artifactId>springcloud-openfeign-provider</artifactId>        <version>1.0</version>    </parent>    <artifactId>openfeign-provider-api</artifactId>    <properties>        <maven.compiler.source>8</maven.compiler.source>        <maven.compiler.target>8</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <!--lombok包-->        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.24</version>        </dependency>    </dependencies></project>

我们在该模块下面创建以下三个子模块:

模块备注openfeign-provider-apiapi模块,提供一些公共的dto类、util类和过滤器供client模块和web模块使用openfeign-provider-clientclient模块,需要依赖api模块,通过openfeign封装web模块提供的http接口,然后供客户端调用openfeign-provider-webweb模块,需要依赖api模块,是个完整的springboot web应用,对外提供http服务

下面我们分开讲解各个子模块的写法。

2.2.1.1、openfeign-provider-api

api模块作为一个公共的模块,主要提供一些dto类、util工具类和过滤器,供client模块和web模块使用。

image-20230715151449950

api模块的主要类定义如下:

(1)OrderDTO类

package com.xk.openfeign.springcloud.provider.api.dto;public class OrderDTO {    private Long id;    private String name;    public Long getId() {        return id;    }    public void setId(Long id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

(2)TokenFilter类

TokenFilter主要是用来获取请求头中的token,然后将token设置到ThreadLocal对象中,方便后面直接从ThreadLocal中获取到token。ThreadLocal对象是和当前处理任务的线程绑定的,该线程可以在任何代码位置获取到和本线程相关的ThreadLocal对象中存储的信息,而不必将相关信息通过方法参数一步一步地往下传递。

package com.xk.openfeign.springcloud.provider.api.filter;import com.xk.openfeign.springcloud.provider.api.util.WebThreadLocalUtil;import org.springframework.util.StringUtils;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import java.io.IOException;/** * token过滤器 * 用于往线程本地变量工具类中设置token * 同时也可以借助请求头是否存在token来完成对用户是否登录的校验 * @author xk * @date 2023-07-07 8:16 */public class TokenFilter implements Filter {    @Override    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) servletRequest;        //这里假设用户登录后,前端将用户token放到了请求头中        String token = request.getHeader("token");        //如果token为空,可以做相应的处理        if(!StringUtils.hasText(token)){        }        //为了实验,此处暂时不考虑token为空        WebThreadLocalUtil.setToken(token);        filterChain.doFilter(servletRequest, servletResponse);    }}

(3)WebThreadLocalUtil类

package com.xk.openfeign.springcloud.provider.api.util;import java.util.HashMap;import java.util.Map;/** * 本地线程工具类 * @author xk * @date 2023-07-07 8:14 */public class WebThreadLocalUtil {    private static final String TOKEN = "token";    private static final String USER = "user";    protected static ThreadLocal<Map<String,Object>> localMap = new ThreadLocal<Map<String, Object>>(){        @Override        protected Map<String, Object> initialValue() {            return new HashMap<>();        }    };    /**     * 根据key获取对应的值对象     * @param key key     * @return     */    public static Object getValue(String key) {        return localMap.get().get(key);    }    /**     * 设置键值对     * @param key key     * @param value value     */    public static void setValue(String key,String value){        localMap.get().put(key,value);    }    /**     * 获取当前登录用户的token     * @return     */    public static String getToken() {        return (String) localMap.get().get(TOKEN);    }    /**     * 设置token     * @param value token值     * @return     */    public static void setToken(String value) {        Map<String, Object> objectMap = localMap.get();        objectMap.put(TOKEN,value);    }}
2.2.1.2、openfeign-provider-web模块

web模块依赖api模块,它就是一个普通的采用springboot开发的web应用,它本身和openfeign没什么关系,也不需要依赖openfeign相关的jar包,所以我们就像开发正常的web应用一样,来进行本模块的开发。另外,web模块的server.context-path被设置为/。

image-20230715151528601

web模块的pom文件内容如下:

<?xml version="1.0" encoding="UTF-8"?><project xmlns=";         xmlns:xsi=";         xsi:schemaLocation=" ;>    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.xk</groupId>        <artifactId>springcloud-openfeign-provider</artifactId>        <version>1.0</version>    </parent>    <artifactId>openfeign-provider-web</artifactId>    <properties>        <maven.compiler.source>8</maven.compiler.source>        <maven.compiler.target>8</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <dependency>            <groupId>com.xk</groupId>            <artifactId>openfeign-provider-api</artifactId>            <version>1.0</version>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <!--数据库驱动-->        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.29</version>        </dependency>        <!--mybatis启动器-->        <dependency>            <groupId>org.mybatis.spring.boot</groupId>            <artifactId>mybatis-spring-boot-starter</artifactId>            <version>2.2.2</version>        </dependency>        <!--lombok包-->        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.24</version>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>                           </plugin>        </plugins>    </build></project>

可以看到,pom文件中依赖了openfeign-provider-api模块,并没有别的特别的东西。因为这次我们只关心http接口层的调用,所以我们就只看下controller层,关于mybatis的使用,这里就先不介绍了。

(1)OrderController类

package com.xk.openfeign.springcloud.provider.core.controller;import com.xk.openfeign.springcloud.provider.api.dto.OrderDTO;import com.xk.openfeign.springcloud.provider.core.entity.Order;import com.xk.openfeign.springcloud.provider.core.service.OrderService;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @author xk * @since 2023.04.24 14:25 */@RequestMapping("/order")@RestControllerpublic class OrderController{    @Autowired    private OrderService orderService;    @GetMapping("{id}")    public OrderDTO findById(@PathVariable Long id){        Order order = orderService.findById(id);        OrderDTO orderDTO = new OrderDTO();        BeanUtils.copyProperties(order,orderDTO);        return orderDTO;    }}

OrderController类就提供了一个简单的http查询接口,根据id从数据库查询数据。这里我们也可以简化一下,直接创建一个OrderDTO对象。

web模块我们就先说这块,接下来就说下咱们的重点,如何用openfeign开发client模块,并且能通过client模块直接访问到上面的http接口。

2.2.1.3、openfeign-provider-client模块

client模块是供客户端(消费者)使用的,它依赖api模块,并且将web模块的http接口进行封装,供客户端直接使用。

image-20230715151602179

其中client模块的pom文件内容如下:

<?xml version="1.0" encoding="UTF-8"?><project xmlns=";         xmlns:xsi=";         xsi:schemaLocation=" ;>    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.xk</groupId>        <artifactId>springcloud-openfeign-provider</artifactId>        <version>1.0</version>    </parent>    <artifactId>openfeign-provider-client</artifactId>    <properties>        <maven.compiler.source>8</maven.compiler.source>        <maven.compiler.target>8</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <dependency>            <groupId>com.xk</groupId>            <artifactId>openfeign-provider-api</artifactId>            <version>1.0</version>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <!--lombok包-->        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.24</version>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-openfeign</artifactId>        </dependency>    </dependencies></project>

pom文件中很重要的一点就是引入了spring-cloud-starter-openfeign包,版本号是3.1.3版本。

注意:web模块并不会依赖本模块。

接下来我们来看下client模块要提供哪些东西。

(1)TokenRequestInterceptor类

package com.xk.openfeign.springcloud.provider.client.interceptor;import com.xk.openfeign.springcloud.provider.api.util.WebThreadLocalUtil;import feign.RequestInterceptor;import feign.RequestTemplate;/** * token请求拦截器,用于在请求头放置token,来满足目标系统的校验 * @author xk * @date 2023.07.07 09:06 */public class TokenRequestInterceptor implements RequestInterceptor {    @Override    public void apply(RequestTemplate requestTemplate) {        requestTemplate.header("token", WebThreadLocalUtil.getToken());    }}

TokenRequestInterceptor是个拦截器类,它实现了feign包的RequestInterceptor接口,这个接口主要就是在发起请求前对请求的信息做一下预处理。我们此处使用这个,是为了在请求头中写入token参数,服务提供者可以根据请求头中的token来判断客户端是否有权限访问目标http接口。当然此处我们只是演示,而且是假定服务提供者就是通过获取请求头中的token字段的值来获取用户信息。

(2)OrderClient接口

package com.xk.openfeign.springcloud.provider.client;import com.xk.openfeign.springcloud.provider.api.dto.OrderDTO;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.ResponseBody;/** * 客户端接口 * url的值可以写死,比如: * 为了使用的灵活性,此处已变量形式注入 */@FeignClient(name="order",url="${app.order.url}")public interface OrderClient{    @GetMapping("/order/{id}")    @ResponseBody    OrderDTO findById(@PathVariable Long id);}

OrderClient接口就是消费者要真正直接用到的,它内部的findById方法上面加了我们很熟悉的spring mvc注解。然后接口上面写了@FeignClient注解,openfeign包会对加了@FeignClient注解的接口进行解析,其中的url属性的值我们就以外部配置的方式来提供,当然配置项的名称已经由我们固定了,就是app.order.url,消费者在自己的配置文件中(也可以是启动参数)就需要声明这么一个配置项。

前面说过,openfeign是可以结合注册中心一起使用的,也就是可以通过提供服务的名称而不是url来完成对目标服务的访问。但是出于快速入门的需要,而且考虑到一些简单的服务可能并不需要依赖注册中心,所以本篇我们就先讲解openfeign直接通过目标服务的url进行调用的方式。

值得注意的是:当前3.1.3版本的openfeign不支持在标记@FeignClient注解的接口上使用@RequestMapping注解,及时我们用了,也不会生效。同时,@FeignClient的name属性也必须赋值,不可以为空。

(3)ClientAutoConfiguration配置类

package com.xk.openfeign.springcloud.provider.client;import com.xk.openfeign.springcloud.provider.api.filter.TokenFilter;import com.xk.openfeign.springcloud.provider.client.interceptor.TokenRequestInterceptor;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.cloud.openfeign.EnableFeignClients;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.Collections;/** * feign客户端配置 */@EnableFeignClients@Configurationpublic class ClientAutoConfiguration {    @Bean    public TokenRequestInterceptor tokenRequestInterceptor(){        return new TokenRequestInterceptor();    }    @Bean    public FilterRegistrationBean<TokenFilter> tokenFilter() {        FilterRegistrationBean<TokenFilter> bean = new FilterRegistrationBean<>();        bean.setFilter(new TokenFilter());        bean.setUrlPatterns(Collections.singletonList("/*"));        return bean;    }}

该配置类创建了基于feign的请求拦截器对象,以及过滤器对象。另外配置类上面加了一个很重要的注解@EnableFeignClients。

(4)resources目录下的META-INF/spring.factories文件

在client模块的resources资源目录下,创建META-INF目录,并在META-INF目录下创建spring.factories文件,内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xk.openfeign.springcloud.provider.client.ClientAutoConfiguration

当消费者(客户端)引入openfeign-provider-client模块后,ClientAutoConfiguration配置类就会生效,同时配置类中的bean也会生效,而且openfeign接口也会被解析,并将生成的代理对象注入到消费者的spring容器中,方便消费者直接使用。

2.2.1.4、小结

到此,我们完成了openfeign服务提供者模块的编写,接下来就需要将client模块和api模块安装到本地仓库,方便消费者模块引用。当然,实际应用中,我们会把这两个模块打包部署到内网的maven私服,也方便团队人员使用。

2.2.2、创建springcloud-openfeign-consumer模块

本模块是个独立的springboot应用,主要用来演示如何调用其他服务的http接口,因此代码层面会简化,只写controller层。

image-20230715151630018

模块的pom文件内容如下:

<?xml version="1.0" encoding="UTF-8"?><project xmlns=";         xmlns:xsi=";         xsi:schemaLocation=" ;>    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.xk</groupId>        <artifactId>openfeign</artifactId>        <version>1.0</version>    </parent>    <artifactId>springcloud-openfeign-consumer</artifactId>    <properties>        <maven.compiler.source>8</maven.compiler.source>        <maven.compiler.target>8</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <dependency>            <groupId>com.xk</groupId>            <artifactId>openfeign-provider-client</artifactId>            <version>1.0</version>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <!--lombok包-->        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.24</version>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>                           </plugin>        </plugins>    </build></project>

pom文件中我们是直接引用了openfeign-provider-client模块,引入之后,也会自动将openfeign相关的包导入到项目中。

2.2.2.1、使用openfeign接口进行调用

我们来看下怎么使用openfeign-provider-client模块,来完成对目标服务的调用。

(1)代码

我们新建一个ConsumerController类,用于暴露http接口,用于自测。

内容如下:

package com.xk.openfeign.springcloud.consumer.core.controller;import com.xk.openfeign.springcloud.provider.api.dto.OrderDTO;import com.xk.openfeign.springcloud.provider.client.OrderClient;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @author xk * @since 2023.07.07 09:01 */@RequestMapping("/consumer")@RestControllerpublic class ConsumerController {    @Autowired    private OrderClient orderClient;    @GetMapping("{id}")    public OrderDTO findById(@PathVariable Long id){        return orderClient.findById(id);    }}

里面的内容很简单,直接将OrderClient对象注入到当前的Controller中,然后就像调用本地方法一样,完成对目标服务的调用,客户端在调用的时候,写法就是这么超级简单。

(2)配置文件

我们之前在写openfeign-provider-client模块的时候,提到过OrderClient要依赖外部的配置项,为了使服务正常使用,我们还需要在消费者(客户端)配置文件中写入下列信息:

yml格式:

app:  order:    #order服务的访问地址前缀    url: 

或properties格式:

app.order.url=

这里的url的值就是openfeign-provider-web服务所在服务器的ip和端口号。到这里,我们的consumer模块就能正常的通过openfeign接口访问服务提供者的http接口了。

2.3、小结

以前我们开发了一个http接口,会告诉别人具体的http接口地址,供别人调用,一旦对方写错了url地址,就会导致调用出错。而openfeign的出现,能大大简化这个调用的过程。别人不需要再记住我们的接口地址是哪个,只需要给别人一个jar包,然后告诉他调用哪个接口中的哪个方法就可以了。当然,我们自己因为要写只供别人使用的openfeign包,工作量就会稍微多点。但是,换一种角度想想,如果我们大家都这么做,那么彼此间的服务调用就都会更加地方便~

觉得有收获的朋友,可以点击关注我,这样方便接收我后续的文章,多谢支持~

标签: #前端把token加到请求头