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消费。