入职新公司,用的SpringCloud,重新开始学习一下
简介
之前接触Ribbon,可以知道调用微服务的方法是指定地址,然后通过RestTemplate来实现调用,用起来有点别扭,因为跟使用HttpClient来调用http接口的感觉是一样的,完全不是面向接口编程。
Feign是一个声明性的Web服务客户端。它使编写Web服务客户端更加容易。要使用Feign,只需要创建一个接口 + 注释。比如:
@FeignClient(name = "${feign.name}", url = "${feign.url}") public interface StoreClient { @GetMapping(value = "/stores") ListgetStores(); @GetMapping(value = "/stores") Page getStores(Pageable pageable); @PostMapping(value = "/stores/{storeId}", consumes = "application/json") Store update(@PathVariable("storeId") Long storeId, Store store); }
关于FeignClient注解
public @interface FeignClient {
/**
* 可选协议前缀的服务名称。是name的同义词。无论是否提供url,都必须为所有客户端指定名称。可以指定为属性键,例如:${propertyKey}。
* @return the name of the service with optional protocol prefix
*/
@AliasFor("name")
String value() default "";
/**
* @deprecated 已废弃,请使用name
* @return the service id with optional protocol prefix
*/
@Deprecated
String serviceId() default "";
/**
* 指定bean名称,而不是服务名称
* @return bean name instead of name if present
*/
String contextId() default "";
/**
* @return value的同义词
*/
@AliasFor("value")
String name() default "";
/**
* @return the @Qualifier
value for the feign client.
*/
String qualifier() default "";
/**
* 服务地址
* @return 绝对URL或可解析主机名(协议可选)。
*/
String url() default "";
/**
* @return 是否应该解码404,而不是抛出feignexception
*/
boolean decode404() default false;
/**
* 自定义的feign client配置类,可以配置{@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.等等
* @see FeignClientsConfiguration for the defaults
* @return list of configurations for feign client
*/
Class>[] configuration() default {};
/**
* 指定fallback类。fallback类必须实现由该注解注释的接口,并且必须是一个有效的spring bean。
* @return fallback class for the specified Feign client interface
*/
Class> fallback() default void.class;
/**
* 指定FallbackFactory,FallbackFactory生成的实例必须实现由 @FeignClient 注释的接口,并且必须是一个有效的spring bean。
* @see feign.hystrix.FallbackFactory for details.
* @return fallback factory for the specified Feign client interface
*/
Class> fallbackFactory() default void.class;
/**
* @return 所有方法级映射使用的路径前缀
*/
String path() default "";
/**
* @return whether to mark the feign proxy as a primary bean. Defaults to true.
*/
boolean primary() default true;
}
主要这几个:
name和value:服务名称
contextId:bean名称
url:服务地址
configuration:配置类
fallback:容错处理类,需要实现注解了@FeignClient的接口
path:方法级映射使用的路径前缀
正文
建立父工程
next
next
Finish
删除src目录
建立Eureka工程
给父工程添加module
Next
Next
Next
Finish
修改application.properties,加入
spring.application.name=eureka-registry-center server.port=8888 # 不向注册中心注册自己 eureka.client.register-with-eureka=false # 自己是注册中心 eureka.client.fetch-registry=false eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
修改启动类,加入 @EnableEurekaServer
启动,并访问 http://localhost:8888/
建立公共模块工程
Next
Next
Finish
api的pom文件引入依赖
<dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-openfeignartifactId> <version>2.2.3.RELEASEversion> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <version>1.18.12version> <scope>providedscope> dependency>
新建实体类
package com.example.cloudapi.entity; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.Setter;
@NoArgsConstructor @Getter @Setter public class User { public User(Integer id, String name) { this.id = id; this.name = name; } private Integer id; private String name; }
目录结构如下
建立服务提供者工程
Next
Next
Next
Finish
将API的依赖添加进来
<dependency> <groupId>com.examplegroupId> <artifactId>cloud-apiartifactId> <version>1.0-SNAPSHOTversion> dependency>
修改application.properties
spring.application.name=service-provider server.port=8080 eureka.client.service-url.defaultZone=http://localhost:8888/eureka eureka.instance.instance-id=service-provider eureka.instance.prefer-ip-address=true
修改启动类,增加 @EnableEurekaClient 注解
建立服务接口
package com.example.cloudprovider.service; import com.example.cloudapi.entity.User; import java.util.List; public interface UserService { ListgetList(); }
服务实现
package com.example.cloudprovider.service.impl; import com.example.cloudapi.entity.User; import com.example.cloudprovider.service.UserService; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.List; @Service public class UserServiceImpl implements UserService { @Override public ListgetList() { return Arrays.asList(new User(1, "小红"),new User(2, "小明")); } }
Controller
package com.example.cloudprovider.controller; import com.example.cloudapi.entity.User; import com.example.cloudprovider.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class UserController { private UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @GetMapping("/list") public ListgetList(){ return userService.getList(); } }
目录结构如下:
启动测试
Eureka里出现新的服务
实际上,如果不配置 eureka.instance.instance-id ,有个默认值【IP:spring.application.name:端口】
访问 http://localhost:8080/list
建立服务消费者工程
Next
Next
Next
Finish
将API的依赖添加进来
com.example cloud-api 1.0-SNAPSHOT
在api工程,新建Feign接口
package com.example.cloudapi.api; import com.example.cloudapi.entity.User; import com.example.cloudapi.fallback.UserServiceFallbackFactory; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import java.util.List; @FeignClient(name = "SERVICE-PROVIDER" ,fallbackFactory = UserServiceFallbackFactory.class ) public interface UserService { @GetMapping("/list") ListgetList(); }
再新建一个fallback接口,作用在于:如果提供者服务不可用,客户端可以返回默认内容
package com.example.cloudapi.fallback; import com.example.cloudapi.api.UserService; import com.example.cloudapi.entity.User; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; import java.util.Collections; import java.util.List; @Component public class UserServiceFallbackFactory implements FallbackFactory{ public UserService create(Throwable throwable) { return new UserService() { public List getList() { return Collections.singletonList(new User(0, "服务提供者异常,使用消费者的降级信息")); } }; } }
api工程目录结构
回到Consumer工程
新建Controller
package com.example.cloudconsumer.controller; import com.example.cloudapi.api.UserService; import com.example.cloudapi.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class UserController { private UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @GetMapping("/list") ListgetList(){ return userService.getList(); } }
修改 application.properties文件
spring.application.name=service-consumer server.port=8081 eureka.client.service-url.defaultZone=http://localhost:8888/eureka eureka.client.register-with-eureka=false feign.hystrix.enabled=true
修改启动类,增加相关注解
启动消费者,访问 http://localhost:8081/list
如果把服务提供者停掉,再次访问
优化项目结构
如果你细心你会发现,打开IDEA的maven管理是存在多个root的,所以我们修改一下pom文件,让工程更合理一些。
也就是:1. 子工程依赖父工程。2. 公共部分放在parent的pom文件里。优化之后:
parent
xml version="1.0" encoding="UTF-8"?> <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>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.3.1.RELEASEversion> parent> <groupId>com.examplegroupId> <artifactId>cloud-parentartifactId> <packaging>pompackaging> <version>1.0-SNAPSHOTversion> <modules> <module>cloud-apimodule> <module>cloud-eurekamodule> <module>cloud-providermodule> <module>cloud-consumermodule> modules> <properties> <java.version>1.8java.version> <spring-cloud.version>Hoxton.SR6spring-cloud.version> properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-dependenciesartifactId> <version>${spring-cloud.version}version> <type>pomtype> <scope>importscope> dependency> dependencies> dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.pluginsgroupId> <artifactId>maven-compiler-pluginartifactId> <configuration> <source>${java.version}source> <target>${java.version}target> <encoding>UTF-8encoding> configuration> plugin> <plugin> <groupId>org.apache.maven.pluginsgroupId> <artifactId>maven-clean-pluginartifactId> plugin> <plugin> <groupId>org.apache.maven.pluginsgroupId> <artifactId>maven-jar-pluginartifactId> plugin> <plugin> <groupId>org.apache.maven.pluginsgroupId> <artifactId>maven-dependency-pluginartifactId> plugin> <plugin> <groupId>org.apache.maven.pluginsgroupId> <artifactId>maven-resources-pluginartifactId> plugin> plugins> build> project>
API
xml version="1.0" encoding="UTF-8"?> <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"> <parent> <artifactId>cloud-parentartifactId> <groupId>com.examplegroupId> <version>1.0-SNAPSHOTversion> parent> <modelVersion>4.0.0modelVersion> <artifactId>cloud-apiartifactId> <dependencies> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-openfeignartifactId> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <optional>trueoptional> dependency> dependencies> project>
Eureka
xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <parent> <groupId>com.examplegroupId> <artifactId>cloud-parentartifactId> <version>1.0-SNAPSHOTversion> parent> <artifactId>cloud-eurekaartifactId> <name>cloud-eurekaname> <description>Demo project for Spring Bootdescription> <dependencies> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> <exclusions> <exclusion> <groupId>org.junit.vintagegroupId> <artifactId>junit-vintage-engineartifactId> exclusion> exclusions> dependency> dependencies> <build> <plugins> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> plugin> plugins> build> project>
Provider
xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <parent> <groupId>com.examplegroupId> <artifactId>cloud-parentartifactId> <version>1.0-SNAPSHOTversion> parent> <artifactId>cloud-providerartifactId> <name>cloud-providername> <description>Demo project for Spring Bootdescription> <dependencies> <dependency> <groupId>com.examplegroupId> <artifactId>cloud-apiartifactId> <version>${project.version}version> dependency> <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-netflix-hystrixartifactId> dependency> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <optional>trueoptional> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> <exclusions> <exclusion> <groupId>org.junit.vintagegroupId> <artifactId>junit-vintage-engineartifactId> exclusion> exclusions> dependency> dependencies> <build> <plugins> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> plugin> plugins> build> project>
Consumer
xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <parent> <groupId>com.examplegroupId> <artifactId>cloud-parentartifactId> <version>1.0-SNAPSHOTversion> parent> <artifactId>cloud-consumerartifactId> <name>cloud-consumername> <description>Demo project for Spring Bootdescription> <dependencies> <dependency> <groupId>com.examplegroupId> <artifactId>cloud-apiartifactId> <version>${project.version}version> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starterartifactId> dependency> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <optional>trueoptional> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> <exclusions> <exclusion> <groupId>org.junit.vintagegroupId> <artifactId>junit-vintage-engineartifactId> exclusion> exclusions> dependency> dependencies> <build> <plugins> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> plugin> plugins> build> project>
这样的话,maven的结构是这样的
Install后
随后按照注册中心->服务提供者->服务消费者的顺序java -jar 启动即可。