入职新公司,用的SpringCloud,重新开始学习一下
简介
之前接触Ribbon,可以知道调用微服务的方法是指定地址,然后通过RestTemplate来实现调用,用起来有点别扭,因为跟使用HttpClient来调用http接口的感觉是一样的,完全不是面向接口编程。
Feign是一个声明性的Web服务客户端。它使编写Web服务客户端更加容易。要使用Feign,只需要创建一个接口 + 注释。比如:
@FeignClient(name = "${feign.name}", url = "${feign.url}") public interface StoreClient { @GetMapping(value = "/stores") List<Store> getStores(); @GetMapping(value = "/stores") Page<Store> 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 <code>@Qualifier</code> 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文件引入依赖
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </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.example</groupId> <artifactId>cloud-api</artifactId> <version>1.0-SNAPSHOT</version> </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 { List<User> getList(); }
服务实现
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 List<User> getList() { 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 List<User> getList(){ return userService.getList(); } }
目录结构如下:
启动测试
Eureka里出现新的服务
实际上,如果不配置 eureka.instance.instance-id ,有个默认值【IP:spring.application.name:端口】
建立服务消费者工程
Next
Next
Next
Finish
将API的依赖添加进来
<dependency> <groupId>com.example</groupId> <artifactId>cloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
在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") List<User> getList(); }
再新建一个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<UserService> { public UserService create(Throwable throwable) { return new UserService() { public List<User> 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") List<User> getList(){ 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.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE</version> </parent> <groupId>com.example</groupId> <artifactId>cloud-parent</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <!--模块--> <modules> <module>cloud-api</module> <module>cloud-eureka</module> <module>cloud-provider</module> <module>cloud-consumer</module> </modules> <!--公共属性--> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR6</spring-cloud.version> </properties> <!--Cloud依赖--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!--构建参数--> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-clean-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> </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-parent</artifactId> <groupId>com.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-api</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </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.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>cloud-parent</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>cloud-eureka</artifactId> <name>cloud-eureka</name> <description>Demo project for Spring Boot</description> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </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.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>cloud-parent</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>cloud-provider</artifactId> <name>cloud-provider</name> <description>Demo project for Spring Boot</description> <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>cloud-api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </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.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>cloud-parent</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>cloud-consumer</artifactId> <name>cloud-consumer</name> <description>Demo project for Spring Boot</description> <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>cloud-api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
这样的话,maven的结构是这样的
Install后
随后按照注册中心->服务提供者->服务消费者的顺序java -jar 启动即可。