在上一篇文章中提到,order服务要通过Feign客户端调用member服务的/member/getmember接口,就必须在order服务中定义一个相同的Feign客户端接口,如果有很多个接口,这样的重复代码就很多了,有没有什么办法可以让这个接口定义只写一遍呢?答案是有的。将之前的项目结构改成这样:
各个工程依赖如下:
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>pompackaging>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.3.RELEASEversion>
parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Finchley.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
<modules>
<module>springcloud2.0-feign-api-servicesmodule>
<module>springcloud2.0-feign-api-memberservice-implmodule>
<module>springcloud2.0-feign-api-orderservice-implmodule>
modules>
project>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>springcloud2.0-feign-api-servicesartifactId>
<packaging>pompackaging>
<modules>
<module>springcloud2.0-feign-api-memberservicemodule>
<module>springcloud2.0-feign-api-orderservicemodule>
modules>
project>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-api-servicesartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>springcloud2.0-feign-api-memberserviceartifactId>
project>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-api-servicesartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>springcloud2.0-feign-api-orderserviceartifactId>
project>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>springcloud2.0-feign-api-memberservice-implartifactId>
project>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>springcloud2.0-feign-api-orderservice-implartifactId>
project>
注:eureka注册 中心使用单注册中心,将其他eureka server项目配置改一下即可。
package com.lchtest.api.service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.lchtest.api.entity.UserEntity;
import com.lchtest.common.base.ResponseBase;
/**
* memberservice接口定义
* @author pc
*
*/
public interface IMemberService {
// 实体类放在接口项目里面比较好,可能其他项目也会依赖该实体类,代码实现放到接口的实现里面
@RequestMapping("/getMember")
public UserEntity getMember(@RequestParam("name") String name);
@RequestMapping("/getUserInfo")
public ResponseBase getUserInfo();
}
上面使用@RequestParam接收请求参数(order服务调用member服务时带请求参数); order服务要调用member服务的/getMember接口,只需要引入springcloud2.0-feign-api-memberservice的依赖就可以了,代码都是一样的
package com.lchtest.api.entity;
import lombok.Data;
@Data
public class UserEntity {
private String name;
private int age;
}
pom引入springcloud2.0-feign-api-memberservice 依赖
package com.lchtest.api.service.impl;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lchtest.api.entity.UserEntity;
import com.lchtest.api.service.IMemberService;
import com.lchtest.common.base.BaseApiService;
import com.lchtest.common.base.ResponseBase;
/**
* memberService的业务实现
* @author pc
*
*/
@RestController
public class MemberServiceImpl extends BaseApiService implements IMemberService {
@Value("${server.port}")
private String serverPort;
@GetMapping("/")
public String index(HttpServletRequest req) {
System.out.println("我是首页.....");
return "我是member服务" + serverPort;
}
@RequestMapping("/getMember")
@Override
public UserEntity getMember(String name) {
UserEntity userEntity = new UserEntity();
userEntity.setName(name);
return userEntity;
}
}
实现类里面定义的接口也要加上@RequestMapping("/getMember")注解!
配置文件:
#服务启动端口号
server:
port: 8001
#服务名称(服务注册到eureka名称)
spring:
application:
name: app-member
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
#把自己注册到注册中心
register-with-eureka: true
# 从eureka上获取注册信息
fetch-registry: true
启动类:加上注解@EurekaClient @EnableFeignClients
package com.lchtest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class AppMember {
public static void main(String[] args) {
SpringApplication.run(AppMember.class, args);
}
}
启动eureka注册中心和member服务并访问,说明member服务ok
package com.lchtest.api.service;
import org.springframework.web.bind.annotation.RequestMapping;
import com.lchtest.common.base.ResponseBase;
public interface IOrderService {
// 订单服务调用会员服务接口
@RequestMapping("/orderToMember")
public String orderToMember(String name);
// 订单服务调用会员服务接口
@RequestMapping("/orderToMemberUserInfo")
public ResponseBase orderToUserInfo();
@RequestMapping("/orderInfo")
public ResponseBase orderInfo();
}
pom:
引入springcloud2.0-feign-api-orderservice,实现该项目定义的接口;
引入springcloud2.0-feign-api-memberservice,目的是为了调用member服务的接口
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>springcloud2.0-feign-api-orderservice-implartifactId>
<dependencies>
<dependency>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-api-orderserviceartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-api-memberserviceartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
dependencies>
project>
3.2.1 Feign客户端
原来的写法是,member服务中会有这么一个接口声明,order服务要调用 member服务的对应接口,也得把这两行代码重复写一遍
@RequestMapping("/getMember")
public UserEntity getMember(String name);
项目结构重构之后,Feign客户端接口不用再写重复代码,只需要继承IMemberService,类名上面加上@FeignClient(name = “app-member”)注解,就可以使用Feign客户端调用member服务的 /getMember 接口了!
由于orderservice调用memberService的getMember接口时带了请求参数,因此IMemberService的getMember方法要使用@RequestParam注解接收请求参数,如下:
package com.lchtest.api.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.lchtest.api.entity.UserEntity;
import com.lchtest.api.fallback.MemberServiceFallback;
import com.lchtest.api.service.IMemberService;
/**
* Feign客户端定义,继承IMemberService,这样就可以避免类似下面这样的重复代码了: @RequestMapping("/getMember")
* public UserEntity getMember(String name);
*/
@FeignClient(name = "app-member")
public interface MemberServiceFeign extends IMemberService {
}
Tips: Feign能否写在会员服务的接口IMemberService 里面,答案是否定的,
因为Feign客户端一般需要做服务降级,@FeignClient(name = “app-member”, fallback = MemberServiceFallback.class) 注解中通过fallback 指定服务降级要调用的类,而服务降级只能在接口的调用端完成!
orderservice接口的实现:需要自动注入 MemberServiceFeign
package com.lchtest.api.service.impl;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lchtest.api.entity.UserEntity;
import com.lchtest.api.fallback.MemberServiceFallback;
import com.lchtest.api.feign.MemberServiceFeign;
import com.lchtest.api.service.IOrderService;
import com.lchtest.common.base.BaseApiService;
import com.lchtest.common.base.ResponseBase;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
/**
* 订单服务继承会员服务接口,用来实现Feign客户端,减少重复接口代码!
*
* @author pc
*
*/
@RestController
public class OrderServiceImpl extends BaseApiService implements IOrderService {
@Autowired
private MemberServiceFeign memberServiceFeign;
@Value("${server.port}")
private String serverPort;
@GetMapping("/")
public String index(HttpServletRequest req) {
System.out.println("我是首页.....");
return "我是order服务" + serverPort;
}
/**
* http://localhost:8005/orderToMember?name=admin
*/
@RequestMapping("/orderToMember")
@Override
public String orderToMember(String name) {
UserEntity user = memberServiceFeign.getMember(name);
return user == null ? "no user find" : user.toString();
}
}
启动类:
package com.lchtest.api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients //开启Feign客户端
public class AppOrder {
public static void main(String[] args) {
SpringApplication.run(AppOrder.class, args);
}
}
启动注册中心,member服务,order服务,测试ok:
上面所有代码,漏了一个common工程,用来封装接口的响应,如下:
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-feign-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>springcloud2.0-feign-commonartifactId>
project>
package com.lchtest.common.base;
public class BaseApiService {
public ResponseBase setResultError(Integer code, String msg) {
return setResult(code, msg, null);
}
// 返回错误,可以传msg
public ResponseBase setResultError(String msg) {
return setResult(500, msg, null);
}
// 返回成功,可以传data值
public ResponseBase setResultSuccess(Object data) {
return setResult(200, "处理成功", data);
}
// 返回成功,沒有data值
public ResponseBase setResultSuccess() {
return setResult(200, "处理成功", null);
}
// 返回成功,沒有data值
public ResponseBase setResultSuccess(String msg) {
return setResult(200, msg, null);
}
// 通用封装
public ResponseBase setResult(Integer code, String msg, Object data) {
return new ResponseBase(code, msg, data);
}
}
package com.lchtest.common.base;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
//服务接口有响应 统一规范响应服务接口信息
@Data
@Slf4j
public class ResponseBase {
private Integer rtnCode;
private String msg;
private Object data;
public ResponseBase() {
}
public ResponseBase(Integer rtnCode, String msg, Object data) {
super();
this.rtnCode = rtnCode;
this.msg = msg;
this.data = data;
}
@Override
public String toString() {
return "ResponseBase [rtnCode=" + rtnCode + ", msg=" + msg + ", data=" + data + "]";
}
}