1.Feign客户端调用重构
在上一篇文章中提到,order服务要通过Feign客户端调用member服务的/member/getmember接口,就必须在order服务中定义一个相同的Feign客户端接口,如果有很多个接口,这样的重复代码就很多了,有没有什么办法可以让这个接口定义只写一遍呢?答案是有的。将之前的项目结构改成这样:
- springcloud2.0-feign-parent
所有项目的父项目,package类型选pom,存放公共的依赖 - springcloud2.0-feign-api-services 子项目,同时也是一个聚合项目,即自己又作为父项目,package类型选pom, 该项目下面放所有服务的接口项目
springcloud2.0-feign-api-memberservice 定义member服务中的所有接口
springcloud2.0-feign-api-orderservice 定义order服务中的所有接口 - springcloud2.0-feign-api-memberservice-impl member服务
- springcloud2.0-feign-api-orderservice-impl order服务
这时order服务要调用member服务,只需要依赖springcloud2.0-feign-api-memberservice项目就可以了。
此外,还需要一个common工程,创建出来的项目结构如下:
各个工程依赖如下:
- 聚合项目springcloud2.0-feign-parent,有三个子模块
<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.0</modelVersion>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- SpringBoot整合fegnin客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<modules>
<module>springcloud2.0-feign-api-services</module>
<module>springcloud2.0-feign-api-memberservice-impl</module>
<module>springcloud2.0-feign-api-orderservice-impl</module>
</modules>
</project>
- 子模块springcloud2.0-feign-api-services(也定义为一个聚合项目)
<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.0</modelVersion>
<parent>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>springcloud2.0-feign-api-services</artifactId>
<packaging>pom</packaging>
<modules>
<module>springcloud2.0-feign-api-memberservice</module>
<module>springcloud2.0-feign-api-orderservice</module>
</modules>
</project>
- 子模块springcloud2.0-feign-api-memberservice
<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.0</modelVersion>
<parent>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-api-services</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>springcloud2.0-feign-api-memberservice</artifactId>
</project>
- 子模块springcloud2.0-feign-api-orderservice
<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.0</modelVersion>
<parent>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-api-services</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>springcloud2.0-feign-api-orderservice</artifactId>
</project>
- 子模块springcloud2.0-feign-api-memberservice-impl
<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.0</modelVersion>
<parent>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>springcloud2.0-feign-api-memberservice-impl</artifactId>
</project>
- 子模块springcloud2.0-feign-api-orderservice-impl
<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.0</modelVersion>
<parent>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>springcloud2.0-feign-api-orderservice-impl</artifactId>
</project>
注:eureka注册 中心使用单注册中心,将其他eureka server项目配置改一下即可。
2. 服务提供者
2.1 服务提供者接口定义
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;
}
2.2 服务提供者接口实现
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
3.服务消费者
3.1服务消费者接口定义
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();
}
3.2 服务消费者接口实现
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.0</modelVersion>
<parent>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>springcloud2.0-feign-api-orderservice-impl</artifactId>
<dependencies>
<!-- 引入api-order的依赖,作为业务,实现接口 -->
<dependency>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-api-orderservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 引入会员的接口项目 -->
<dependency>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-api-memberservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</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工程,用来封装接口的响应,如下:
- 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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lchtest</groupId>
<artifactId>springcloud2.0-feign-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>springcloud2.0-feign-common</artifactId>
</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 + "]";
}
}