一、使用场景:在本人第一次接触微服务项目时,感觉真的很高大上,我们的项目是根据功能来划分的模块,一个父工程下有很多子模块(子工程)。
有一次我在system模块想调用admin模块里面的一个接口方法时,我直接在system模块的impl下使用
@Autowired
@Lazy
private SysAdminCompanyService companyService;
直接引入了admin模块里面的方法,这样,在system的pom文件中就新增了admin模块的依赖,后来有同事告诉我不能这样用。既然是微服务开发,分模块做,再这样引入不同块的依赖,就失去了解耦合的意义。因此这里就用到了feign远程调用的技术:
请看项目的工程图解:
二、了解feign:
Feign是一个声明式WebService客户端。
在SpringCloud用于实现微服务之间的互相调用。
服务消费者无需知道服务提供者的ip和端口,只需要 指定服务名,即可通过注册中心调用到目标服务。
{所以这里从字面意思上讲,就是不同服务之间的调用,只需要指定服务名就可以调到了}
下面我们来实战下如何实现服务间的接口调用:
三、实战操作
步骤:1、创建两个微服务的模块,一个作为提供者,一个作为消费者
2、导入feign依赖,在启动类中开启feign
3、在application.yml中配置feign
4、利用feignclient调用远程接口
这里首先创建注册中心的模块:
eureka:
pom.xml
<!---->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!--注册中的依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
application.yml:
#配置注册中心1的端口,可以随意设置,不能跟其他端口冲突
server:
port: 10010
#配置注册中心1的名字,可以随意起
spring:
application:
#通过查看注册中心查看有哪些模块注册进了这个注册中心
name: EurekaServer1
eureka:
client:
service-url:
#配置访问路径
defaultZone: http://127.0.0.1:10010/eureka
#允许注册中心注册自己,分布式web模块、网关、
#包括注册中心自己也都要注册(到注册中心)进来,如果注册中心多可以相互注册
#register-with-eureka: true
eurekaStart:启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* 注册中心启动类
*/
@SpringBootApplication
@EnableEurekaServer//开启注册中心的职务
public class EurekaStart1 {
public static void main(String[] args) {
SpringApplication.run(EurekaStart1.class);
}
}
admin模块:提供者模块
pom.xml
<!--Eureka客户端-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
application.yml
#配置后台admin的端口号
server:
port: 8999
#给后台admin模块设置名字
spring:
application:
name: hbuy-admin
#将后台模块注册到注册中心
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10010/eureka
adminStart启动类:
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* admin模块的启动类
*/
@EnableEurekaClient
@SpringBootApplication(scanBasePackages = "com.java.admin")
@MapperScan(basePackages = "com.java.admin.mapper")
public class AdminStart {
public static void main(String[] args) {
SpringApplication.run(AdminStart.class);
}
}
提供者接口类
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: LoginController
* @Description:
* @Version: v1.0.0
* @Author: Fu
* @Date: 2019/12/19 0019 下午 3:54
* Modification History:
* Date Author Version Description
* -------------------------------------------------------------
* 2019/12/19 0019 Fu v1.0.0 创建
*/
@RestController
@RequestMapping("/admin")
public class LoginController {
@GetMapping("/user")
public String getUser(String age) {
System.out.println("小明"+age);
return "xiaoming";
}
}
消费者模块:
CustomController:
<!---->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!--Eureka客户端-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!--feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
application.yml:
#配置后台admin的端口号
server:
port: 8100
#给后台admin模块设置名字
spring:
application:
name: hbuy-autosell
#将后台模块注册到注册中心
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10010/eureka
启动类Custome:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @ClassName: CustomStart
* @Description:
* @Version: v1.0.0
* @Author: Fu Hao
* @Date: 2019/12/19 0019 下午 4:27
* Modification History:
* Date Author Version Description
* -------------------------------------------------------------
* 2019/12/19 0019 Fu v1.0.0 创建
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class CustomStart {
public static void main(String[] args) {
SpringApplication.run(CustomStart.class,args);
}
}
feign远程调用接口:
import com.joyoung.cloud.fallback.LoginFallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @ClassName: LoginService
* @Description:
* @Version: v1.0.0
* @Author: Fu Hao
* @Date: 2019/12/19 0019 下午 4:52
* Modification History:
* Date Author Version Description
* -------------------------------------------------------------
* 2019/12/19 0019 Fu v1.0.0 创建
*/
@FeignClient(value = "hbuy-admin",fallback = LoginFallBack.class)
public interface LoginService {
@GetMapping("/user")
public String getUser(@RequestParam("age") String age);
}
fallback远程调用接口实现类打印日志信息:
import com.joyoung.cloud.feign.LoginService;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @ClassName: LoginFallBack
* @Description:
* @Version: v1.0.0
* @Author: Fu Hao
* @Date: 2019/12/19 0019 下午 4:49
* Modification History:
* Date Author Version Description
* -------------------------------------------------------------
* 2019/12/19 0019 Fu v1.0.0 创建
*/
@Slf4j
@Service
public class LoginFallBack implements LoginService {
@Override
public String getUser(@RequestParam("age") String age) {
log.error("调用{}异常{}", "age", age);
return null;
}
}
调用接口:
import com.joyoung.cloud.feign.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: AutoSellController
* @Description:
* @Version: v1.0.0
* @Author: Fu Hao
* @Date: 2019/12/19 0019 下午 4:41
* Modification History:
* Date Author Version Description
* -------------------------------------------------------------
* 2019/12/19 0019 Fu v1.0.0 创建
*/
@RestController
@RequestMapping("com")
public class AutoSellController {
@Autowired
private LoginService service;
public String test(){
String age="12";
return service.getUser(age);
}
}
下面截几段代码展示我们项目中的远程调用:
调用接口:
import com.joyoung.cloud.moudles.system.Fallback.SysAdminUserServiceBack;
import com.joyoung.cloud.security.common.config.FeignConfig;
import com.joyoung.cloud.security.common.entity.admin.SysUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
/**
* @ClassName: IAdminService
* @Description:
* @Version: v1.0.0
* @Author: Fu Hao
* @Date: 2019/11/11 0011 下午 2:47
* Modification History:
* Date Author Version Description
* -------------------------------------------------------------
* 2019/11/11 0011 Fu v1.0.0 创建
*/
@FeignClient(value = "cloud-admin",fallback = SysAdminUserServiceBack.class,configuration = {FeignConfig.class})
public interface SysAdminUserService{
/**
* @return void
* @Description 修改用户信息
* @Date 2019/6/18
* @Param [entity, coIds]
**/
@PutMapping("/userAdmin")
public Object edit(SysUser user);
/***
* @Description 添加用户信息
* @author Fu Hao on 2019/11/13 0013 下午 2:05
* @return
**/
@PostMapping("/userAdmin")
public Object add(SysUser user);
/***
* @Description 删除一条用户
* @author Fu Hao on 2019/11/13 0013 下午 2:08
* @return
**/
@DeleteMapping(value = "/userAdmin/{id}")
public Object del(@PathVariable(value = "id") String id);
}
FeignConfig配置信息:
import com.joyoung.cloud.security.common.context.BaseContextHandler;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Component
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
BaseContextHandler.setToken(token);
}
}
@Bean
Logger.Level feignLevel() {
return Logger.Level.FULL;
}
}
实现类:
import com.joyoung.cloud.moudles.system.feign.SysAdminUserService;
import com.joyoung.cloud.security.common.entity.admin.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @ClassName: IAdminBack
* @Description:
* @Version: v1.0.0
* @Author: Fu Hao
* @Date: 2019/11/12 0012 下午 3:46
* Modification History:
* Date Author Version Description
* -------------------------------------------------------------
* 2019/11/12 0012 Fu v1.0.0 创建
*/
@Service
@Slf4j
public class SysAdminUserServiceBack implements SysAdminUserService {
@Override
public Object edit(SysUser sysUser) {
log.error("调用{}异常{}", "sysUser", sysUser);
return null;
}
@Override
public Object add(SysUser user) {
log.error("调用{}异常","user");
return null;
}
@Override
public Object del(@RequestParam("id") String id) {
log.error("调用{}异常{}","id",id);
return null;
}
}
提供方法的接口:
import com.github.pagehelper.PageInfo;
import com.joyoung.cloud.moudles.admin.service.SysAdminUserService;
import com.joyoung.cloud.security.common.entity.admin.SysCompany;
import com.joyoung.cloud.security.common.entity.admin.SysUser;
import com.joyoung.cloud.security.common.msg.RestRes;
import com.joyoung.cloud.security.common.validatedGroup.Add;
import com.joyoung.cloud.security.common.validatedGroup.Modify;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @ClassName: SysAdminUserController
* @Description: 用户管理
* @Version: v1.0.0
* @Author: yan
* @Date: 2019-05-15 14:47
* Modification History:
* Date Author Version Description
* -------------------------------------------------------------
* 2019-08-15 t v1.0.0 创建
*/
@Api(tags = "平台用户接口")
@RestController
@RequestMapping("/userAdmin")
public class SysAdminUserController {
@Autowired
private SysAdminUserService service;
/**
* @param user description
* @return java.lang.Object
* @Description 添加一条记录 同时绑定公司与角色
* @author tengyun on 2019-08-20 16:51
*/
@ApiOperation(value = "添加一条记录 同时绑定公司与角色")
@PostMapping("")
public Object add(@Validated({Add.class}) @RequestBody SysUser user) {
service.addOrModify(user, user.getCoIdsList());
return 1;
}
/**
* @param user description
* @return java.lang.Object
* @Description 编辑信息 同时绑定公司与角色
* @author tengyun on 2019-08-20 16:51
*/
@ApiOperation(value = "编辑信息 同时绑定公司与角色")
@PutMapping("")
public Object edit(@Validated({Modify.class}) @RequestBody SysUser user) {
service.addOrModify(user, user.getCoIdsList());
return 1;
}
/**
* @param id description
* @return java.lang.Object
* @Description 删除信息
* @author tengyun on 2019-08-20 16:51
*/
@ApiOperation(value = "删除信息")
@DeleteMapping("/{id}")
public Object del(@PathVariable(value = "id") String id) {
service.deleteById(id);
return 1;
}
/**
* @param id description
* @return java.lang.Object
* @Description 查询单个用户
* @author tengyun on 2019-08-20 16:51
*/
@ApiOperation(value = "查询单个用户")
@GetMapping("/{id}")
public Object get(@PathVariable String id) {
return service.selectById(id);
}
}
总结:
Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix
Feign 是一个声明式的伪RPC的REST客户端,它用了基于接口的注解方式,很方便的客户端配置,Spring Cloud 给 Feign 添加了支持Spring MVC注解,并整合Ribbon及Eureka进行支持负载均衡。
坑点:远程调用的时候可能不起作用,检查熔断器,将熔断器注释掉,再启动项目调用。