上一篇简单入门了SpringBoot+SpringCloud 构建微服务。但只能算是一个
demo
级别的应用。
此次会按照实际生产要求来搭建这套服务。html
上次提到咱们调用本身的http
接口的时候采用的是PostMan
来模拟请求,这个在平时调试时天然没有什么问题,但当咱们须要和前端联调开发的时候效率就比较低了。前端
一般来讲如今先后端分离的项目通常都是后端接口先行。java
后端大大们先把接口定义好(入参和出参),前端大大们来肯定是否知足要求,能够了以后后端才开始着手写实现,这样总体效率要高上许多。node
但也会带来一个问题:在接口定义阶段频繁变动接口定义而没有一个文档或相似的东西来记录,那么双方的沟通加上前端的调试都是比较困难的。git
基于这个需求网上有各类解决方案,好比阿里的rap就是一个不错的例子。github
可是springCould
为咱们在提供了一种在开发springCloud
项目下更方便的工具swagger
。spring
实际效果以下:后端
以sbc-order
为例我将项目分为了三个模块:api
├── order // Order服务实现
│ ├── src/main
├── order-api // 对内API
│ ├── src/main
├── order-client // 对外的clientAPI
│ ├── src/main
├── .gitignore
├── LICENSE
├── README.md复制代码
由于实现都写在order
模块中,因此只须要在该模块中配置便可。springboot
首先须要加入依赖,因为我在order
模块中依赖了:
<dependency>
<groupId>com.crossoverJie</groupId>
<artifactId>order-api</artifactId>
<version>${target.version}</version>
</dependency>复制代码
order-api
又依赖了:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<scope>compile</scope>
</dependency>复制代码
接着须要配置一个SwaggerConfig
@Configuration
@EnableSwagger2
/** 是否打开swagger **/
@ConditionalOnExpression("'${swagger.enable}' == 'true'")
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.crossoverJie.sbcorder.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("sbc order api")
.description("sbc order api")
.termsOfServiceUrl("http://crossoverJie.top")
.contact("crossoverJie")
.version("1.0.0")
.build();
}
}复制代码
其实就是配置swagger
的一些基本信息。
以后启动项目,在地址栏输入http://ip:port/swagger-ui.html#/
便可进入。
能够看到如上图所示的接口列表,点击以下图所示的参数例子便可进行接口调用。
swagger
的便利能给咱们带来不少好处,但稍有不慎也可能出现问题。
好比若是在生产环境还能经过IP访问swagger
的话那后果但是不堪设想的。
因此咱们须要灵活控制swagger
的开关。
这点能够利用spring的条件化配置(条件化配置能够配置存在于应用中,一旦知足一些特定的条件时就取消这些配置)
来实现这一功能:
@ConditionalOnExpression("'${swagger.enable}' == 'true'")复制代码
该注解的意思是给定的SpEL表达式计算结果为true
时才会建立swagger
的bean
。
swagger.enable
这个配置则是配置在application.properties
中:
# 是否打开swagger
swagger.enable = true复制代码
这样当咱们在生产环境时只须要将该配置改成false
便可。
ps:更多spring条件化配置
:
@ConditionalOnBean //配置了某个特定Bean
@ConditionalOnMissingBean //没有配置特定的Bean
@ConditionalOnClass //Classpath里有指定的类
@ConditionalOnMissingClass //Classpath里缺乏指定的类
@ConditionalOnExpression //给定的Spring Expression Language(SpEL)表达式计算结果为true
@ConditionalOnJava //Java的版本匹配特定值或者一个范围值
@ConditionalOnJndi //参数中给定的JNDI位置必须存在一个,若是没有给参数,则要有JNDI InitialContext
@ConditionalOnProperty //指定的配置属性要有一个明确的值
@ConditionalOnResource //Classpath里有指定的资源
@ConditionalOnWebApplication //这是一个Web应用程序
@ConditionalOnNotWebApplication //这不是一个Web应用程序
(参考SpringBoot实战)复制代码
在上一篇中是用Eureka
来作了服务注册中心,全部的生产者都往它注册服务,消费者又经过它来获取服务。
可是以前讲到的都是单节点,这在生产环境风险巨大,咱们必须作到注册中心的高可用,搭建Eureka
集群。
这里简单起见就搭建两个Eureka
,思路则是这两个Eureka都把本身当成应用向对方注册,这样就能够构成一个高可用的服务注册中心。
在实际生产环节中会是每一个注册中心一台服务器,为了演示起见,我就在本地启动两个注册中心,可是端口不同。
首先须要在本地配置一个host
:
127.0.0.1 node1 node2复制代码
这样不管是访问node1
仍是node2
均可以在本机调用的到(固然不配置host也能够,只是须要经过IP来访问,这样看起来不是那么明显
)。
并给sbc-service
新增了两个配置文件:
application-node1.properties:
spring.application.name=sbc-service
server.port=8888
eureka.instance.hostname=node1
## 不向注册中心注册本身
#eureka.client.register-with-eureka=false
#
## 不须要检索服务
#eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://node2:9999/eureka/复制代码
application-node2.properties:
spring.application.name=sbc-service
server.port=9999
eureka.instance.hostname=node2
## 不向注册中心注册本身
#eureka.client.register-with-eureka=false
#
## 不须要检索服务
#eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/复制代码
其中最重要的就是:
eureka.client.serviceUrl.defaultZone=http://node2:9999/eureka/
eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/复制代码
两个应用互相注册。
启动的时候咱们按照:java -jar sbc-service-1.0.0-SNAPSHOT.jar --spring.profiles.active=node1
启动,就会按照传入的node1或者是node2去读取application-node1.properties,application-node2.properties
这两个配置文件(配置文件必须按照application-{name}.properties的方式命名
)。
分别启动两个注册中心能够看到如下:
能够看到两个注册中心以及互相注册了。
在服务注册的时候只须要将两个地址都加上便可:eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/,http://node2:9999/eureka/
在服务调用的时候能够尝试关闭其中一个,正常状况下依然是能够调用到服务的。
接下来谈谈服务调用,上次提到能够用ribbon
来进行服务调用,可是明显很不方便,不如像以前rpc
调用那样简单直接。
为此此次使用Feign
来进行声明式调用,就像调用一个普通方法那样简单。
片头说到我将应用分红了三个模块order、order-api、order-client
,其中的client
模块就是关键。
来看看其中的内容,只有一个接口:
@RequestMapping(value="/orderService")
@FeignClient(name="sbc-order")
@RibbonClient
public interface OrderServiceClient extends OrderService{
@ApiOperation("获取订单号")
@RequestMapping(value = "/getOrderNo", method = RequestMethod.POST)
BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) ;
}复制代码
@FeignClient
这个注解要注意下,其中的name的是本身应用的应用名称,在application.properties中的spring.application.name配置
。
其中继承了一个OrderService
在order-api
模块中,来看看order-api
中的内容。
其中也只有一个接口:
@RestController
@Api("订单服务API")
@RequestMapping(value = "/orderService")
@Validated
public interface OrderService {
@ApiOperation("获取订单号")
@RequestMapping(value = "/getOrderNo", method = RequestMethod.POST)
BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) ;
}复制代码
这个接口有两个目的。
controller
来进行实现。client
接口进行继承。类关系以下:
注解这些都没什么好说的,一看就懂。
order
则是具体接口实现的模块,就和平时写controller
同样。
来看看如何使用client
进行声明式调用:
此次看看sbc-user
这个项目,在里边调用了sbc-order
的服务。
其中的user模块
依赖了order-client
:
<dependency>
<groupId>com.crossoverJie</groupId>
<artifactId>order-client</artifactId>
</dependency>复制代码
具体调用:
@Autowired
private OrderServiceClient orderServiceClient ;
@Override
public BaseResponse<UserResVO> getUserByFeign(@RequestBody UserReqVO userReq) {
//调用远程服务
OrderNoReqVO vo = new OrderNoReqVO() ;
vo.setReqNo(userReq.getReqNo());
BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNo(vo);
logger.info("远程返回:"+JSON.toJSONString(orderNo));
UserRes userRes = new UserRes() ;
userRes.setUserId(123);
userRes.setUserName("张三");
userRes.setReqNo(userReq.getReqNo());
userRes.setCode(StatusEnum.SUCCESS.getCode());
userRes.setMessage("成功");
return userRes ;
}复制代码
能够看到只须要将order-client
包中的Order服务注入进来便可。
在sbc-client
的swagger
中进行调用:
因为我并没传appId
因此order
服务返回的错误。
当一个应用须要对外暴露接口时着须要按照以上方式提供一个
client
包更消费者使用。
其实应用自己也是须要作高可用的,和Eureka
高可用同样,再不一样的服务器上再启一个或多个服务并注册到Eureka
集群中便可。
后续还会继续谈到zuul网关,容错,断路器
等内容,欢迎拍砖讨论。
博客:crossoverjie.top。