文章目录
- 前言
- 准备
- 流程初测
- 定义nacos-product子服务
- 定义服务的消费方 cloudalibaba-openfeign-server
- 初步测试
- 结论
- 设置cloudalibaba-openfeign-server中的feign
- 总结
前言
在实际开发过程中,服务与服务之间都会有比较频繁的通信操作。其次不同用户所需要查询的数据信息不同,此时为了区别不同的用户操作信息,就需要使用到token
令牌机制。
之前的博客 GateWay——向其他服务传递参数数据(思路) 中,已经说了在gateway进行token解析和传递下级服务的操作思路和实现过程。
接下来,说明使用openfeign
时,传递token的思路。
准备
准备两个服务cloudalibaba-openfeign-server
、nacos-product
。
这两个服务的用处为:
nacos-product
:请求子服务,对外暴露接口。cloudalibaba-openfeign-server
请求主服务,做主要的业务处理,会使用openfeign
调用nacos-product
服务的暴露接口。
流程初测
定义nacos-product子服务
主要的暴露接口如下所示:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@RestController
public class TestController {
@Value("${server.port}")
private String port;
@RequestMapping("/product/getProduct/{id}")
public String getOrder(@PathVariable("id") String id) {
// 获取 header 中的数据
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
String token = request.getHeader("x-client-token-user");
return "this is product services,port:"+port+"\t id="+id+" \t token = "+token;
}
}
定义服务的消费方 cloudalibaba-openfeign-server
编写一个请求接口,在接口中模拟gateway设定token
,并使用openfeign
调用nacos-product子服务
的指定暴露接口,查看nacos-product子服务
中能否获取对应的token信息。
其中cloudalibaba-openfeign-server
接口如下所示:
import linkpower.servive.IOrderService;
import org.apache.tomcat.util.http.MimeHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
@RestController
public class OrderController {
Logger log = LoggerFactory.getLogger(OrderController.class);
@Autowired
private IOrderService oserService;
@RequestMapping("/test/{id}")
public String getOrder(@PathVariable("id") String id) throws Exception {
log.info("----getOrder---- id={}",id);
setRequestHeaderToken();
return oserService.getOrder(id);
}
/**
* 模拟 gateway
* 将用户登录的token信息,写入request中
* @throws Exception
*/
private void setRequestHeaderToken() throws Exception {
// 设置 token 值,模拟 gateway 设置请求信息
// 上下文获取 request 对象
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
Class<? extends HttpServletRequest> requestClass = request.getClass();
// 获取 org.springframework.web.context.request.ServletRequestAttributes.request 属性
Field requestField = requestClass.getDeclaredField("request");
requestField.setAccessible(true);
// 获取实际对象值
Object requestObj = requestField.get(request);
Field coyoteRequestField = requestObj.getClass().getDeclaredField("coyoteRequest");
coyoteRequestField.setAccessible(true);
Object coyoteRequestObj = coyoteRequestField.get(requestObj);
Field headersField = coyoteRequestObj.getClass().getDeclaredField("headers");
headersField.setAccessible(true);
MimeHeaders headersObj = (MimeHeaders)headersField.get(coyoteRequestObj);
headersObj.removeHeader("x-client-token-user");
headersObj.addValue("x-client-token-user").setString("xiangjiao token 6666");
log.info("设定token信息成功");
}
}
调用fengn接口,并指定feign的fallback处理类
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "nacos-product",fallback = IOrderServiceFallback.class)
public interface IOrderService {
@RequestMapping("/product/getProduct/{id}")
public String getOrder(@PathVariable("id") String id);
}
import org.springframework.stereotype.Component;
/**
* IOrderService 降级处理类
*/
//org.springframework.beans.factory.BeanCreationException:
// Error creating bean with name 'cn.linkpower.service.IOrderService':
// FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException:
// No fallback instance of type class cn.linkpower.service.IOrderServiceFallback found for
// feign client nacos-product
@Component
public class IOrderServiceFallback implements IOrderService {
/**
* 针对 getOrder的降级处理
* @param id
* @return
*/
@Override
public String getOrder(String id) {
return "fallback --- IOrderServiceFallback";
}
}
初步测试
debug
启动两个服务,都注册至nacos中,请求cloudalibaba-openfeign-server
中的接口,观察nacos-product子服务
是否能获取到cloudalibaba-openfeign-server
接口中设定的token信息。
http://localhost:8500/test/2
断点调试,在cloudalibaba-openfeign-server
中能够看到设置token成功。
继续下一步,查看nacos-product
是否可以获取到。
结论
发现模拟gateway设置浏览器请求token信息,但在子服务中并没有获取到。
【原因:】feign是一个新的请求,与原来的请求不是同一个!!!
设置cloudalibaba-openfeign-server中的feign
增加一个配置类,主要功能是将原请求对象中的header参数值,设置到新的请求对象中。逻辑代码如下所示:
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
/**
*
*/
public class MyFeignConfig implements RequestInterceptor {
private static final Logger log = LoggerFactory.getLogger(MyFeignConfig.class);
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
String name;
// 将原有请求中的header,数据转移至新的请求中
if (headerNames != null) {
while(headerNames.hasMoreElements()) {
name = (String)headerNames.nextElement();
String values = request.getHeader(name);
if (!name.equalsIgnoreCase("content-length")) {
requestTemplate.header(name, new String[]{values});
}
}
}
}
}
}
在@FeignClient
中增加配置类引用,如下所示:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "nacos-product",
fallback = IOrderServiceFallback.class,
configuration = MyFeignConfig.class)
//@FeignClient(value = "nacos-product",fallback = IOrderServiceFallback.class)
public interface IOrderService {
@RequestMapping("/product/getProduct/{id}")
public String getOrder(@PathVariable("id") String id);
}
重启cloudalibaba-openfeign-server
服务,再次请求测试:
http://localhost:8500/test/2
能够成功获取!
总结
思路就是增加一个openfeign的配置类,在程序业务代码调用feign时,会经过事先配置的类,进行原request
对象中header
属性,拷贝至
新的RestTemplate
的请求对象中。