SpringCloud如何创建一个服务提供者provider
创建子moudle provider-demo
创建一个子module,项目名叫provider-demo. 填充springboot和springcloud依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
<
dependencies
>
<!--springboot 依赖start-->
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-web</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-actuator</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-devtools</
artifactId
>
<
optional
>true</
optional
>
</
dependency
>
<
dependency
>
<
groupId
>com.fasterxml.jackson.datatype</
groupId
>
<
artifactId
>jackson-datatype-jsr310</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework.cloud</
groupId
>
<
artifactId
>spring-cloud-starter-eureka</
artifactId
>
</
dependency
>
<!--springboot 依赖结束-->
<
dependency
>
<
groupId
>io.springfox</
groupId
>
<
artifactId
>springfox-swagger2</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>io.springfox</
groupId
>
<
artifactId
>springfox-swagger-ui</
artifactId
>
</
dependency
>
<!--工具类 start-->
<
dependency
>
<
groupId
>com.google.guava</
groupId
>
<
artifactId
>guava</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.projectlombok</
groupId
>
<
artifactId
>lombok</
artifactId
>
<
optional
>true</
optional
>
</
dependency
>
<
dependency
>
<
groupId
>net.logstash.logback</
groupId
>
<
artifactId
>logstash-logback-encoder</
artifactId
>
</
dependency
>
<!--工具类end-->
</
dependencies
>
|
spring-boot-starter-web
提供web能力,必须spring-boot-starter-actuator
提供项目统计和基础的监控endpoint, 想要使用spring-boot-admin监控就必须添加了 spring-boot-devtools
开发模式 jackson-datatype-jsr310
可以解决Java8新的时间APILocalDate解体 spring-cloud-starter-eureka
eureka客户端,负责维护心跳和注册 swagger
提供Restful契约 lombok
看起来很清爽的编译级别getter setter工具 guava
大而全的Java必备类库 logstash-logback-encoder
想要收集日志到ELK,使用这个appender
启动类
1
2
3
4
5
6
7
8
9
|
@EnableDiscoveryClient
@SpringBootApplication
public
class
ProviderDemoApplication {
public
static
void
main(String[] args) {
SpringApplication.run(ProviderDemoApplication.
class
, args);
}
}
|
@EnableDiscoveryClient
来启用服务注册
这个ProviderDemoApplication应该放置于项目包的最外层,因为@SpringbootAppliatin包含了@ComponentScan的注解,默认扫描本类包下,否则必须手动指定scan。
Swagger
swagger就是一个配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@EnableSwagger2
@Configuration
public
class
SwaggerConfiguration {
private
ApiInfo apiInfo() {
return
new
ApiInfoBuilder()
.title(
"服务提供者 API"
)
.description(
"提供用户信息查询"
)
.termsOfServiceUrl(
""
)
.version(
"1.0.0"
)
.build();
}
/**
* 定义api配置.
*/
@Bean
public
Docket api() {
return
new
Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(Api.
class
))
.build()
.apiInfo(apiInfo());
}
}
|
对于swagger页面的路由,需要我们来引导下:
创建一个controller来导航
1
2
3
4
5
6
7
8
|
@Controller
public
class
HomeController {
@GetMapping
(value = {
"/api"
,
"/"
})
public
String api() {
return
"redirect:/swagger-ui.html"
;
}
}
|
来一个Controller 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Api
@RestController
@RequestMapping
(
"/api/v1/users"
)
public
class
UserController{
private
List<User> users = Lists.newArrayList(
new
User(
1
,
"谭浩强"
,
100
, LocalDate.now()),
new
User(
2
,
"严蔚敏"
,
120
, LocalDate.now()),
new
User(
3
,
"谭浩强"
,
100
, LocalDate.now()),
new
User(
4
,
"James Gosling"
,
150
, LocalDate.now()),
new
User(
6
,
"Doug Lea"
,
150
, LocalDate.now())
);
@GetMapping
(
"/"
)
public
List<UserVo> list() {
return
users.stream()
.map(u ->
new
UserVo(u.getId(), u.getName(), u.getAge(), u.getBirth()))
.collect(Collectors.toList());
}
}
|
一些简单的环境配置
application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
spring:
application:
name: provider-demo
jackson:
serialization:
WRITE_DATES_AS_TIMESTAMPS: false
default-property-inclusion: non_null
#服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除
#注意,EurekaServer一定要设置eureka.server.eviction-interval-timer-in-ms否则这个配置无效,这个配置一般为服务刷新时间配置的三倍
#默认90s
eureka.instance.lease-expiration-duration-in-seconds: 15
#服务刷新时间配置,每隔这个时间会主动心跳一次
#默认30s
eureka.instance.lease-renewal-interval-in-seconds: 5
server:
port: 8082
springfox:
documentation:
swagger:
v2:
path: /swagger-resources/api-docs
log:
path: logs
|
application-dev.yml
1
2
3
4
5
6
7
8
9
10
11
|
management:
security:
enabled: false
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
logstash:
url: localhost:4560
|
这里需要提一点,由于我集成了logstash, 所以必须安装好logstash, 见ELK入门使用。 当然可以跳过,只要不提供logback.xml的配置就行,把依赖中logstash移除即可。
Log配置
默认采用logback作为日志框架,简单配置如下,对于不想使用logstash的,移除logstash的appender即可。
在resource下新建logback-spring.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
configuration
scan
=
"true"
scanPeriod
=
"60 seconds"
debug
=
"false"
>
<
springProperty
scope
=
"context"
name
=
"appName"
source
=
"spring.application.name"
defaultValue
=
"unknown"
/>
<
springProperty
scope
=
"context"
name
=
"log.path"
source
=
"log.path"
defaultValue
=
"logs"
/>
<
springProperty
scope
=
"context"
name
=
"logstashurl"
source
=
"logstash.url"
defaultValue
=
"localhost:4560"
/>
<
include
resource
=
"org/springframework/boot/logging/logback/base.xml"
/>
<!--输出到控制台-->
<
appender
name
=
"console"
class
=
"ch.qos.logback.core.ConsoleAppender"
>LoggingInterceptor
<
encoder
>
<
pattern
>%d{HH:mm:ss.SSS} %X{req.remoteHost} %X{req.requestURI}
${appName} [%thread] %-5level %logger{36} - %msg%n
</
pattern
>
</
encoder
>
</
appender
>
<!--输出到文件-->
<
appender
name
=
"file"
class
=
"ch.qos.logback.core.rolling.RollingFileAppender"
>
<
rollingPolicy
class
=
"ch.qos.logback.core.rolling.TimeBasedRollingPolicy"
>
<
fileNamePattern
>${log.path}/${appName}.%d{yyyy-MM-dd}.log</
fileNamePattern
>
</
rollingPolicy
>
<
encoder
>
<
pattern
>%d{HH:mm:ss.SSS} ${appName} %X{req.remoteHost} %X{req.requestURI}
%X{req.userAgent}
%X{req.method} - [%thread] %-5level %logger{36} - %msg%n
</
pattern
>
</
encoder
>
</
appender
>
<!-- 输出到logstash-->
<
appender
name
=
"LOGSTASH"
class
=
"net.logstash.logback.appender.LogstashTcpSocketAppender"
>
<
destination
>${logstashurl}</
destination
>
<
encoder
charset
=
"UTF-8"
class
=
"net.logstash.logback.encoder.LogstashEncoder"
/>
</
appender
>
<
springProfile
name
=
"dev"
>
<
root
level
=
"info"
>
<
appender-ref
ref
=
"console"
/>
<
appender-ref
ref
=
"file"
/>
<
appender-ref
ref
=
"LOGSTASH"
/>
</
root
>
</
springProfile
>
<
springProfile
name
=
"test, prod"
>
<
root
level
=
"info"
>
<
appender-ref
ref
=
"file"
/>
<
appender-ref
ref
=
"LOGSTASH"
/>
</
root
>
</
springProfile
>
</
configuration
>
|
启动
确保eureka已启动,admin最好也启动,方便查看app状态,ELK的日志系统也最好可以使用。当然,只有eureka是刚需。
编译打包
1
|
mvn clean install package spring-boot:repackage
|
运行main方法,指定profile为dev, 可以在idea中编辑运行配置,添加参数
1
|
--spring.profiles.active=dev
|
或者命令行jar启动
启动后,访问eureka
访问admin
访问provider-demo
暴露我们的API给consumer
既然有服务提供者,必然是为了consumer消费。consumer应该如何消费?手动调用这个http请求即可。前面提到swagger Restful契约,就是服务提供者提供请求访问的参数和要求。consumer如果手动去开发这个client必然耗时,而且容易出错。所以,作为服务提供者,理应提供sdk或者client给consumer来用。
在spring cloud技术体系中,远程调用自然是重中之重。目前我找到的具体用法为Feign+Ribbon+Hystrix.
通过Feign的声明式接口对接,实现了consumer对provider的调用。ribbon客户端负载均衡,hystrix作健康熔断。
在这里,我们就首先要提供Feign的接口了。
把controller的api提炼成一个接口。首先,我们创建一个新的项目
https://github.com/Ryan-Miao/spring-cloud-Edgware-demo/tree/master/provider-api
将这个项目放到provider-demo的依赖列表里
1
2
3
4
5
6
7
|
<!--内部依赖-->
<
dependency
>
<
groupId
>com.test</
groupId
>
<
artifactId
>provider-api</
artifactId
>
<
version
>0.0.1-SNAPSHOT</
version
>
</
dependency
>
<!--内部依赖end-->
|
抽离UserApi接口道provider-api项目中
1
2
3
4
5
6
|
@RequestMapping
(
"/api/v1/users"
)
public
interface
UserApi {
@GetMapping
(
"/"
)
List<UserVo> list();
}
|
在provider-demo的controller里改造如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Api
@RestController
public
class
UserController
implements
UserApi {
private
List<User> users = Lists.newArrayList(
new
User(
1
,
"谭浩强"
,
100
, LocalDate.now()),
new
User(
2
,
"严蔚敏"
,
120
, LocalDate.now()),
new
User(
3
,
"谭浩强"
,
100
, LocalDate.now()),
new
User(
4
,
"James Gosling"
,
150
, LocalDate.now()),
new
User(
6
,
"Doug Lea"
,
150
, LocalDate.now())
);
@Override
public
List<UserVo> list() {
return
users.stream()
.map(u ->
new
UserVo(u.getId(), u.getName(), u.getAge(), u.getBirth()))
.collect(Collectors.toList());
}
}
|
这样,controller没有变化,只是被抽离了api路径。而独立出来的module provider-api就是我们给consumer提供的client。下一节使用consumer消费。