一、 Spring Cloud分布式开发五大组件
- 服务发现——Netflix Eureka、SpringCloud Alibaba Nacos Discovery
- 客户端负载均衡——Netflix Ribbon
- 断路器——Netflix Hystrix、SpringCloud Alibaba Sentinel
- 服务网关——Netflix Zuul、Spring Cloud Gateway
- 分布式配置——Spring Cloud Config、SpringCloud Alibaba Nacos Config
spring & Netflix | 其他 | 其他 | |
---|---|---|---|
服务发现与注册 | Netflix Eureka(client、server) | nacos | |
客户端负载均衡 | Netflix Ribbon | ||
断路器 | Netflix Hystrix | ||
服务网关 | Netflix Zuul | Gateway | |
分布式配置 | Spring Cloud Config | alibaba-nacos-config | apollo |
分布式注册中心 | Spring Cloud Server | alibaba-nacos-discovery |
二、服务调用
1.Feign调用
(1)传递非业务数据
当微服务之间互相调用的时候,需要传递非业务数据的时候,可以使用拦截器实现,具体实现方式是实现feign.RequestInterceptor接口,实现具体的apply()方法逻辑。
package com.irootech.useraccess.config;
import com.irootech.common.util.SessionBean;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
// 设置本地变量
requestTemplate.header("tenantId", SessionBean.getTenantId());
requestTemplate.header("userId", SessionBean.getUserId());
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
if (requestTemplate.headers().containsKey(name)) {
continue;
}
if (name.equals("content-length") || (name.equalsIgnoreCase("content-type"))) {//此处是个坑,会把现有的content-length去调用feigin接口,报错
continue;
}
String values = request.getHeader(name);
requestTemplate.header(name, values);
}
}
}
}
}
(2)错误和异常数据解析
实现 feign.codec.ErrorDecoder 接口
package com.irootech.useraccess.config;
import com.irootech.common.exception.SysException;
import com.irootech.common.util.JsonParsingUtil;
import com.irootech.useraccess.feign.vo.ACLBaseResponse;
import com.irootech.useraccess.feign.vo.ACLErrorMessage;
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.util.List;
/**
* @Author: fei.yu
* @Date: 2021/2/1 10:20
*/
@Configuration
@Slf4j
public class FeignClientErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
log.info("feign client response:", response);
UserAccessResultCode userAccessResultCode = UserAccessResultCode.getByCode(response.status());
if (userAccessResultCode != UserAccessResultCode.ERROR) {
return new SysException(userAccessResultCode);
}
RemoteExceptionEnumCode remoteExceptionCode = null;
if (response.body() == null) {
log.error("feign调用异常,body为空");
// remoteExceptionCode = new RemoteExceptionCode(response.status(),"", "");
remoteExceptionCode = RemoteExceptionEnumCode.getByMessageEN("");
}else if(response.headers().containsKey("x-rootcloud-setpw-token")){
List<String> setPwTokenParam = (List<String>)response.headers().get("x-rootcloud-setpw-token");
SysException sysException = new SysException(UserAccessResultCode.getByCode(8610),setPwTokenParam.get(0));
throw sysException;
} else {
try {
String body = Util.toString(response.body().asReader());
log.error("feign调用异常,body为 ==>{}",body);
ACLBaseResponse baseResponse = JsonParsingUtil.fromJson(body, ACLBaseResponse.class);
try {
ACLErrorMessage errorMessage = JsonParsingUtil.fromJson(baseResponse.getMessage(), ACLErrorMessage.class);
// remoteExceptionCode = new RemoteExceptionCode(baseResponse.getStatus(),
// errorMessage.getMessage(), errorMessage.getMessage());
remoteExceptionCode = RemoteExceptionEnumCode.getByMessageEN(errorMessage.getMessage());
} catch (Exception e) {
// remoteExceptionCode = new RemoteExceptionCode(baseResponse.getStatus(),
// baseResponse.getMessage(), baseResponse.getMessage());
remoteExceptionCode = RemoteExceptionEnumCode.getByMessageEN(baseResponse.getMessage());
}
} catch (IOException e) {
log.error("feign.IOException", e);
throw new SysException(userAccessResultCode);
}
}
return new SysException(remoteExceptionCode);
}
}
三、服务启动与加载
1. Springboot读取配置文件原理和加载顺序优先级 :
(1)读取配置文件原理
Springboot读取配置文件是通过事件监听的方式读取的,在Springboot启动的时候,会发布一个ApplicationEnvironmentPreparedEvent事件,ConfigFileApplicationListener监听器监听了这个事件,在该监听器中读取配置文件。
通过事件监听的方式读取的配置文件,这个监听器是ConfigFileApplicationListener。
(2)配置文件加载顺序:
(开发环境下)
使用SpringCloudConfig这种统一配置时Spring Boot 配置文件的加载顺序,依次为bootstrap.properties-> bootstrap.yml ->application.properties-> application.yml,其中bootstrap.properties配置为最高优先级。
(部署环境下)
下面目录下的配置文件的优先级从高到低,高优先级的配置覆盖低优先级的配置,所有配置会形成互补配置。
# jar文件同级目录下的config文件夹下的配置文件file:./config/
# jar文件同级目录下的配置文件file:./
# classpath目录下的config文件夹下的配置文件classpath:config/
# classpath目录下的配置文件classpath
2.项目启动初始化操作实现方案
项目启动的时候,需要加载字典数据到redis里面,除了单例代码块初始化实现之外,还可以使用@PostConstruct注解实现。在service实现类对应的方法上面加这个注解,Spring容器就会在依赖注入之后调用此方法。
// 可以看到,它是通过注解@PostConstruct实现的自动加载数据,带有该注解的方法会在依赖注入之后调用
/**
* 项目启动时,初始化字典到缓存
*/
@PostConstruct
public void init()
{
loadingDictCache();
}
3.不需要连接数据库的微服务启动
例如Gateway 服务,需要在启动类加排除数据库自动注册的类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
/**
* 网关启动程序
*
* @author ruoyi
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class RuoYiGatewayApplication
{
public static void main(String[] args)
{
SpringApplication.run(RuoYiGatewayApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 若依网关启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}
四、windows启动java服务
1. 方式一:java -jar xxx.jar
2. 方式二:直接进程运行,可关闭窗口
(1)定义startup.bat文件一
这种方式需要手动关闭窗口
@echo off
@javaw -jar ruoyi-admin.jar > ruoyi-admin.log
exit
(2)定义startup.bat文件二
@echo off
start javaw -jar ruoyi-admin.jar > ruoyi-admin.log
exit
这中这种方式自动关闭窗口,
(3)定义stop.bat文件关闭进程
@echo off
taskkill -f -t -im javaw.exe
exit
3.更多方式参考:Windows 下后台启动 jar 包,UTF-8 启动 jar 包_windows启动jar包-CSDN博客