很久没写SpringCloud相关的博客啦QaQ【总是借口拖延博客TUT我要反思】,不过期间我也进一步学习了SpringCloud。
言归正传,此次系列博客准备介绍如下方面(如果之后我还有动力继续写下去的话TUT):
此次博客将基于较新的SpringCloud和SpringBoot版本(因此会重新开个GitHub链接完成后续更新):【版本的一致性对于SpringCloud很重要!不一致可能会带来很多未知问题!】
本篇博客将介绍如下两个方面:
准备工作:新建EurekaServer项目,此处省略,可以直接参考GitHub仓库代码和快速搭建EurekaServer。
在做这件事前,先要阐明动机,为什么要构建后端的common服务(也可以说是公共jar)呢?
因为在微服务体系中,后端微服务经常会存在多个项目,但这些项目中往往会有很多公共的配置和工具等,这时候我们就需要一个common项目同时为多个后端项目服务,这样可以减少许多重复代码和重复配置后端项目的时间。
<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.1.3.RELEASEversion>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>commonartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>commonname>
<description>Common util and config for backend projectsdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.4version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.8.0version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.2.2version>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Finchley.SR2version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
可以看到项目中引用了大量的springcloud等jar包。因为我们需要将common项目打成jar包,供其它后端微服务使用,因此,所有后端微服务所公共使用的jar包均可以放到本common项目中。
需注意,要删除掉springboot项目中原来用于build jar的插件配置,该插件是用来构建正常的springboot项目的可运行jar包,而我们不需要构建可运行jar,因此需要删掉下面的配置:
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
因为我们不需要其运行,我们只需要其静态代码,因此这些文件都可以且需要被删除。
以下仅拿几个关键类举例介绍,完整代码和功能参加GitHub仓库:
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class Swagger2 {
@Value("${spring.application.name}")
private String name;
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.ant("/api/**"))
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(name + "微服务")
.description("提供" + name + "相关功能")
.version("1.0")
.build();
}
}
package com.example.demo.aspect;
import lombok.extern.apachecommons.CommonsLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* 打印web请求和返回值
*
* @author deng
* @date 2018/10/18
*/
@Aspect
@Component
@CommonsLog
public class WebLogAspect {
@Pointcut("execution(public * com.example.demo.controller.*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
log.info("URL : " + request.getRequestURL().toString());
log.info("HTTP_METHOD : " + request.getMethod());
log.info("IP : " + request.getRemoteAddr());
log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
log.info("RESPONSE : " + ret);
}
}
pom.xml中需要引用aop的jar包,是为了配置这个,配置完成后,控制台/日志系统便能够打印出每次Request和Response内容,这在很多时候能够方便我们定位问题,效果如下图:
配置时,有个关键点在于切面位置的定义:@Pointcut(“execution(public * com.example.demo.controller..(…))”):此处com.example.demo.controller为之后后端微服务中controller的位置(在common项目中并不存在controller包,是后端微服务项目中才有controller包),即所有的后端微服务的XXController所在的包名都要是com.example.demo.controller,这样才能被该切面扫描到:
其它配置(如:GlobalExceptionHandler全局异常处理)、工具类(如:AssertUtil、DoubleFormatUtil浮点数的转换工具)和公共类(如:Response、ServiceInfo、PageVO)的配置在此不一一赘述。
完成上述步骤后,使用mvn install安装到本地仓库即可供本电脑上的项目使用。
【如果想在不同电脑上共享该jar包的使用又不想自己下源码,需要配置nexus私服,使用mvn deploy发布到私服中,此处省略具体步骤】
<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.1.3.RELEASEversion>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>backend-oneartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>backend-onename>
<description>A simple backend projectdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>com.examplegroupId>
<artifactId>commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
可以看到,只需要依赖我们之前构建的common(其中的groupId、artifactId和version都取决于之前common项目中pom.xml的配置)包和spring-boot-starter-test包。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class BackendOneApplication {
public static void main(String[] args) {
SpringApplication.run(BackendOneApplication.class, args);
}
}
增加@EnableDiscoveryClient允许服务注册上EurekaServer
增加@EnableFeignClients允许使用Feign
server:
port: 8880
spring:
application:
name: backend-one
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
feign:
hystrix:
enabled: true
logging:
level:
web: TRACE
org:
springframework:
web: TRACE
配置端口号和spring.application.name
配置eureka注册中心地址
开启feign的hystrix功能(断路器,很重要的功能)
修改日志等级,从而可以在日志中看到所有注册的RequestMapping路径(1.X版本中不需要配置,INFO级别就可以看到)
注意3中的配置文件略有不同(name和端口)
package com.example.demo.service;
import com.example.demo.bean.Response;
import com.example.demo.constant.ServiceInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author deng
* @date 2019/03/13
*/
@FeignClient(value = ServiceInfo.BACKEND_TWO, fallback = TwoApiFallback.class)
public interface TwoApi {
@GetMapping("/api/v1/two/getName")
Response<String> getName();
}
package com.example.demo.service;
import com.example.demo.bean.Response;
import com.example.demo.util.ResponseFactory;
import org.springframework.stereotype.Component;
/**
* @author deng
* @date 2019/03/13
*/
@Component
public class TwoApiFallback implements TwoApi {
@Override
public Response<String> getName() {
return ResponseFactory.okResponse( "二号的替代品");
}
}
package com.example.demo.controller;
import com.example.demo.bean.Response;
import com.example.demo.service.TwoApi;
import com.example.demo.util.ResponseFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author deng
* @date 2019/03/13
*/
@RestController
@RequestMapping("/api/v1/one")
public class OneController {
@Autowired
private TwoApi twoApi;
@GetMapping("/test")
public Response<String> get() {
return ResponseFactory.okResponse("你好");
}
@GetMapping("/sayHi")
public String sayHi() {
return "Hello," + twoApi.getName().getRes();
}
}
可以看到,对比RestTemplate,Feign的使用非常简单,就像调用自己服务内的Service一样,这就是Feign的伪RPC的特性。
将eureka、backend-one、backend-two均启动后:
刚启动完后可能需要等一会时间才能看到是"二号后端",因为注册至eureka需要时间,一开始断路器会fallback到备用服务上,因此会显示"二号的替代品"。过一会后再刷新url即可:
GitHub地址:https://github.com/LeiDengDengDeng/spring-cloud-common-demo
有问题或者有什么好的想法欢迎大家和我交流噢!