openfeign是一个java的http客户端,用来简化http调用,先看一个最简单的demo:
这是服务端接口:
@RestControllerpublic class DemoController {@GetMapping("/hello")public String hello(){return "hello";}}
openfeign是如何来调用这个接口的呢?
public interface Demo {@RequestLine("GET /hello")String hello();}public static void main(String[] args) {Demo demo = Feign.builder().target(Demo.class, "http://localhost:8080/");String result = demo.hello();System.out.println(result);}
先定义了一个Demo接口,在接口中的方法上添加对应的注解,然后直接调用接口方法就行了,是不是跟retrofit非常相似,猜一下它里面肯定是用动态代理来实现的,就跟mybatis里面用接口去访问数据库的道理是一样一样的,这里是用接口去访问网络。
使用还是非常简单的,我们看下它都有哪些注解。
@RequestLine:作用在方法上,定义了uri模板,参数来自@Param
@Param:作用与参数,定义uri模板变量
@Headers:作用于类和方法,定义header模板
@QueryMap:作用与参数,Map参数,放在url中
@HeaderMap:作用与参数,Map参数,放在header中
@Body:作用于方法,定义模板
@Param做URI模板传参
public interface Demo {@RequestLine("GET /uriTemplate/{id}")String id(@Param("id")String id);}
GET /uriTemplate/{id}就是个模板,其中{id}里面的id就是个模板变量,跟参数中的@Param("id")相对应。
Expander参数扩展
//服务端方法@GetMapping("/expander/{username}")public String expander(@PathVariable("username") String username){return username;}//接口定义public interface Demo {@RequestLine("GET /expander/{username}")String expander(@Param(value = "username", expander = UserExpander.class)User user);public class UserExpander implements Param.Expander{@Overridepublic String expand(Object value) {return ((User)value).getUsername();}}}//客户端调用User u = new User(20, "Joshua", "123456");//JoshuaSystem.out.println(demo.expander(u));
@QueryMap做URL传参
@GetMapping("/paramMap")public String paramMap(String username, String password, Integer age){return username+":"+password+":"+age;}//这是接口public interface Demo {@RequestLine("GET /paramMap")String paramMap(@QueryMap Map<String, String> map);}//客户端调用Map<String, String> map = new HashMap<>();map.put("username", "Joshua");map.put("password", "");map.put("age", null);//或者不传,效果一样//Joshua::nullSystem.out.println(demo.paramMap(map));
@QueryMap后面可以是Map,也可以是POJO对象,如果参数传递空字符串服务端收到的也是空串,参数不传或者传null服务端收到也是null。
还可以使用QueryMapEncoder做完全的自定义,比如:
//服务端接口@GetMapping("/queryMapEncoder")public String queryMapEncoder(String username, String password){return "queryMapEncoder:" + username+":"+password;}//feign 接口public interface Demo {@RequestLine("GET /queryMapEncoder")String queryMapEncoder(@QueryMap User user);}//客户端调用Demo demo = Feign.builder().queryMapEncoder(new UserQueryMapEncoder()).target(Demo.class, "http://localhost:8080/");System.out.println(demo.queryMapEncoder(new User(20, "Joshua", "123456")));//输出结果:queryMapEncoder:Joshua:123456public static class UserQueryMapEncoder implements feign.QueryMapEncoder {@Overridepublic Map<String, Object> encode(Object object) {User user = (User)object;Map<String, Object> map = new HashMap<>();map.put("username", user.getUsername());map.put("password", user.getPassword());return map;}}
@Body做POST请求
就是把@Body里面的字符串放到http请求体里面,比如:
//服务端接口@PostMapping("/bodyJson")public String bodyJson(@RequestBody User user){return "bodyJson:" + user.getUsername()+":"+user.getPassword();}//feign 接口public interface Demo {@RequestLine("POST /bodyJson")@Body("%7B\"username\":\"{username}\",\"password\":\"{password}\"%7D")@Headers("Content-Type: application/json")String bodyJson(@Param("username")String username, @Param("password")String password);}//客户端调用System.out.println(demo.bodyJson("Joshua", "123456"));//输出结果:bodyJson:Joshua:123456
不过正常人应该不会想去这么调用吧。
Encoder与Decoder做POST请求与响应
一般接口数据都是json格式的,此时需要定义编码器和解码器,Encoder用来把请求参数编码成在网络上传输的字符串,Decoder用于把服务端的响应字符串解码成应用使用的对象,比如:
//这是服务端接口@RestController@RequestMapping("/encoder")public class EncoderController {private static AtomicInteger AGE = new AtomicInteger(20);@PostMapping("/add")public User add(@RequestBody User user){user.setAge(AGE.incrementAndGet());return user;}}//这个Feign客户端接口public interface Encoder {@RequestLine("POST /encoder/add")//注意一定要添加上这个header@Headers("Content-Type: application/json")User add(User user);}//客户端使用public static void main(String[] args) {Encoder demo = Feign.builder().encoder(new GsonEncoder()).decoder(new GsonDecoder()).target(Encoder.class, "http://localhost:8080/");User u = demo.add(new User(null, "Johusa", "xjs"));System.out.println(u);}//这里需要添加下依赖:<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-gson</artifactId><version>11.0</version></dependency>
请求拦截
可给对请求做统一的拦截处理,比如可以添加请求头:
//服务端方法@RequestMapping("/itcpt")@RestControllerpublic class ItcptController {@GetMapping("/getByUsername/{username}")public String getByUsername(@PathVariable("username") String username,@RequestHeader("token")String token){return "getByUsername:"+username+",token:"+token;}}//feign 接口public interface Itcpt {@RequestLine("GET /itcpt/getByUsername/{username}")public String getByUsername(@Param("username") String username);}//客户端调用public static void main(String[] args) {Itcpt itcpt = Feign.builder()// 添加了拦截器.requestInterceptor(new TokenInterceptor()).target(Itcpt.class, "http://localhost:8080/");System.out.println(itcpt.getByUsername("Joshua"));//因此这里会输出:getByUsername:Joshua,token:TK-1234567890}public class TokenInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {template.header("token", "TK-1234567890");}}
异常处理
可以使用ErrorDecoder来捕获所有的异常,比如:
//服务端接口@GetMapping("/hello")public String hello(){throw new RuntimeException("服务端异常");}//feign 接口@RequestLine("GET /error/hello")String hello();//客户端调用public static void main(String[] args) {Demo demo = Feign.builder().errorDecoder(new MyErrorDecoder()).target(Demo.class, "http://localhost:8080/");String result = demo.hello();System.out.println(result);}public static class MyErrorDecoder implements ErrorDecoder {@Overridepublic Exception decode(String methodKey, Response response) {int status = response.status();if(status == 500){try{String res = Util.toString(response.body().asReader(Util.UTF_8));JsonElement je = new JsonParser().parse(res);JsonObject jo = je.getAsJsonObject();String message = jo.get("message").getAsString();return new BizException(message);}catch(Exception e){e.printStackTrace();return e ;}}else{return new BizException("服务端异常");}}}
把服务端异常统一包装成BizException。
异步请求
可以使用AsyncFeign来做异步请求,比如:
//feign接口public interface Demo {//注意这里的返回值@RequestLine("GET /async/hello")CompletableFuture<String> hello();}//客户端调用public static void main(String[] args)throws Exception {Demo demo = AsyncFeign.asyncBuilder().target(Demo.class, "http://localhost:8080/");CompletableFuture<String> result = demo.hello();System.out.println(result.get());}
集成hystrix
首先添加feign-hystrix依赖:
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-hystrix</artifactId><version>11.0</version></dependency>
然后使用HystrixFeign来调用接口,比如:
//服务端接口@RestController@RequestMapping("/hystrix")public class HystrixController {@GetMapping("/hello/{id}/{username}")public String hello(@PathVariable("id") int id, @PathVariable("username")String username){return id+","+username;}}//feign接口public interface Demo {//同步调用@RequestLine("GET /hystrix/hello/{id}/{username}")String sync(@Param("id") int id, @Param("username")String username);//异步调用@RequestLine("GET /hystrix/hello/{id}/{username}")HystrixCommand<String> async1(@Param("id") int id, @Param("username")String username);//异步调用@RequestLine("GET /hystrix/hello/{id}/{username}")CompletableFuture<String> async2(@Param("id") int id, @Param("username")String username);}//客户端调用public static void main(String[] args)throws Exception {Demo demo = HystrixFeign.builder().target(Demo.class,"http://localhost:8080/");String result = demo.sync(100, "Joshua");System.out.println(result);HystrixCommand<String> command = demo.async1(100, "Joshua");System.out.println(command.execute());CompletableFuture<String> future = demo.async2(100, "Joshua");System.out.println(future.get());}
Group和Command key
默认的groupKey就是target的名字,而target的名字默认是baseurl。
默认的Command key跟logging key是一样的。
以demo.sync()为例子,它的groupKey是http://localhost:8080/,而Command key是Demo#sync(int,String)。但是可以通过HystrixFeign.Builder#setterFactory(SetterFactory)来修改:
public static class MySetterFactory implements SetterFactory{@Overridepublic HystrixCommand.Setter create(Target<?> target, Method method) {String groupKey = target.name();String commandKey = method.getName()+"_"+method.getAnnotation(RequestLine.class).value();System.out.println(groupKey+","+commandKey);return HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)).andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));}}public static void main(String[] args)throws Exception {Demo demo = HystrixFeign.builder().setterFactory(new MySetterFactory()).target(Demo.class,"http://localhost:8080/");String result = demo.sync(100, "Joshua");System.out.println(result);}
Fallback支持
只需要在HystrixFeign.Builder.target()的最后一个参数传递fallback或者fallback factory即可,比如:
@RestController@RequestMapping("/hystrix")public class HystrixController {@GetMapping("/getById/{id}")public String fallback(@PathVariable("id") Integer id){throw new RuntimeException();}}//feign接口public interface Demo {@RequestLine("GET /hystrix/getById/{id}")String getById(@Param("id") Integer id);}//客户端调用public static class MyFallbackFactory implements FallbackFactory<Demo>{@Overridepublic Demo create(Throwable throwable) {System.out.println(throwable.getClass().getName());return new Demo() {@Overridepublic String sync(int id, String username) {return null;}@Overridepublic HystrixCommand<String> async1(int id, String username) {return null;}@Overridepublic CompletableFuture<String> async2(int id, String username) {return null;}@Overridepublic String getById(Integer id) {return "fallback";}};}}public static void main(String[] args)throws Exception {Demo demo = HystrixFeign.builder().target(Demo.class,"http://localhost:8080/",new MyFallbackFactory());String result = demo.getById(100);System.out.println(result);}
输出fallback,同时可以看到异常被feign给包装了一下,类型是feign.FeignException$InternalServerError,feign抛出的异常都是继承于FeignException。
如果想直接把异常抛出而不走fallback该如何处理呢?只需要让feign抛出HystrixBadRequestException就行了:
//服务端接口@GetMapping("/getByName/{username}")public String getByName(@PathVariable(value="username") String username,@RequestHeader(value="token", required = false)String token){if(StringUtils.isEmpty(token)){throw new BizException(1, "参数token不能为空");}return "getByName:"+username;}public class BizException extends RuntimeException{public BizException(int errorType, String msg){//把异常类型添加到异常信息里面去super(errorType+":"+msg);}}//feign接口@RequestLine("GET /hystrix/getByName/{username}")String getByName(@Param("username") String username);//客户端调用:public static void main(String[] args)throws Exception {Demo demo = HystrixFeign.builder().errorDecoder(new MyErrorDecoder()).target(Demo.class,"http://localhost:8080/",new MyFallbackFactory());String name = demo.getByName("Joshua");//这里会收到feign包装以后的HystrixBadRequestExceptionSystem.out.println(name);}//主要就是MyErrorDecoderpublic static class MyErrorDecoder implements ErrorDecoder{@Overridepublic Exception decode(String methodKey, Response response) {try{String json = Util.toString(response.body().asReader(Util.UTF_8));JsonElement je = new JsonParser().parse(json);JsonObject jo = je.getAsJsonObject();String message = jo.get("message").getAsString();//从异常信息中解析出来异常的类型String type = message.substring(0,message.indexOf(":"));String msg = message.substring(message.indexOf(":")+1);//如果是特定类型,则包装成HystrixBadRequestException//那么hystrix将不再走fallback和熔断逻辑if("1".equals(type)){return new HystrixBadRequestException(msg);}return FeignException.errorStatus(methodKey, response);}catch (Exception e){e.printStackTrace();return e;}}}
测试代码下载:https://github.com/xjs1919/enumdemo下面的openfeign。




