一、Nacos配置管理
1.统一配置管理
配置更改热更新
①在Nacos中添加配置信息:
②在弹出表单中填写配置信息:
2.配置获取的步骤如下
配置文件bootstrap.yml的优先级比application.yml优先级高。把nacos地址放入bootstrap.yml。
①在userservice中引入Nacos的配置管理客户端依赖:
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
②在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:
spring:
application:
name: userservice # 服务名称
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件。
③在user-service中将pattern.dateformat这个属性注入到UserController中做测试
④访问http://localhost:8081/user/now获取时间对应格式,配置生效
3.配置自动更新
Nacos配置更新,无需重启微服务就可以配置更新。
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
方式二:
使用@ConfigurationProperties注解
①创建一个配置类PatternProperties
@Component
@Data
@ConfigurationProperties(prefix = "pattern") // 跟配置文件的顶级名一致
public class PatternProperties {
private String dateformat;
}
②在需要用的controller进行注入
总结:
- 通过@Value注解注入,结合@RefreshScope来刷新
- 通过@ConfigurationProperties注入,自动刷新
注意事项:
- 不是所有的配置都适合放到配置中心,维护起来比较麻烦
- 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
4.多服务共享配置
启动的时候从nacos读取多个配置文件优先级
userservice-dev.yaml>userservice.yaml>application.yml
无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件。
总结:
二、http客户端Feign
1.RestTemplate方式存在的问题
RestTemplate远程调用的代码
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
①代码可读性差,编程体验不统一
②参数复杂时URL难以维护
2.定义和使用Feign客户端
①在orderservice导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
②在启动类添加注解开启Feign功能@EnableFeignClients
③编写Feign客户端接口
@FeignClient("userservice") //提供者名
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
④使用Feign客户端代替RestTemplate
总结:
- 引入依赖
- 添加@EnableFeignClients注解
- 编写FeignClient接口
- 使用FeignClient中定义的方法代替RestTemplate
3.配置Feign日志的两种方式
feign.Logger.Level 修改日志级别 包含四种不同的级别:NONE、BASIC、HEADERS、FULL
方式一
配置文件方式
①全局生效
②局部生效
方式二
①声明一个Bean
public class FeignClientConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC;
}
}
②全局配置,则把它放到@EnableFeignClients这个注解中
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
③局部配置,则把它放到@FeignClient这个注解
@FeignClient(value="userservice",configuration = FeignClientConfiguration.class)
总结
方式一是配置文件,feign.client.config.xxx.loggerLevel
- 如果xxx是default则代表全局
- 如果xxx是服务名称,例如userservice则代表某服务
方式二是java代码配置Logger.Level这个Bean
- 如果在@EnableFeignClients注解声明则代表全局
- 如果在@FeignClient注解中声明则代表某服务
4.Feign性能优化
①Feign连接池的设置
引入httpClient依赖
配置连接池
优化总结
- 日志级别尽量用basic
- 使用HttpClient或OKHttp代替URLConnection
引入feign-httpClient依赖
配置文件开启httpClient功能,设置连接池参数
5.Feign的最佳实践
方式一(继承):给消费者的FeignClient和提供者controller定义统一的父接口。然后实现接口
缺点
- 服务紧耦合
- 父接口参数列表中的映射不会被继承
方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
6.抽取FeignClient
- 首先创建一个module,命名为feign-api,然后引入feign的starter依赖
- 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
- 在order-service中引入feign-api的依赖
- 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
- 重启测试
注意:
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
方式一:指定FeignClient所在包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:指定FeignClient字节码
@EnableFeignClients(clients = {UserClient.class})
三、统一网关Gateway
1.为什么需要网关
①对用户请求做身份验证、权限校验。
②将用户请求路由到微服务,实现负载均衡
③用户请求做限流
2.网关技术的实现
SpringCloud实现网关的方式
①gateway
②zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
3.搭建网关服务
①创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖:
<!--nacos客户端服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
②编写路由配置及nacos地址
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848 #nacos地址
gateway:
routes:
- id: user-service #路由标识,唯一
uri: lb://userservice # 路由的目标地址
predicates: # 判断请求的规则
- Path=/user/** # 路径的规则
- id: order-service #路由标识,唯一
uri: lb://orderservice # 路由的目标地址
predicates: # 判断请求的规则
- Path=/order/** # 路径的规则
③测试 http://localhost:10010/order/101
端口请求访问的是网关,基于路由规则判断,拉取服务列表,进行负载均衡发送请求。
总结:
4.路由断言工厂 Route Predicate Factory
- 在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
- Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
问题
- PredicateFactory的作用是什么?
读取用户定义的断言条件,对请求做出判断
- Path=/user/**是什么含义?
路径是以/user开头的就认为是符合的
5.过滤工厂GatewayFilterFactory
GatewayFilter是网关提供的一种过滤器,对进入网关的请求和微服务的返回的响应做处理
过滤工厂
案例:所有进入userservice的请求添加一个请求头
请求头:Truth=itcast is freaking awesome!
①在gateway的配置文件application.yml添加过滤配置
②在user-service的UserController,获取请求头参数
@GetMapping("/prop")
public String prop(@RequestHeader(value = "Truth", required = false) String truth) {
return truth;
}
③访问http://localhost:10010/user/prop
注意:
所有的路由都生效的过滤器
总结
- 过滤器的作用是什么?
对路由的请求或响应做加工处理,比如添加请求头
配置在路由下的过滤器只对当前路由的请求生效
- defaultFilters的作用是什么?
对所有路由都生效的过滤器
6.全部过滤器
实现GlobalFilter接口
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
ServerWebExchange:请求上下文,获取request,response信息
GatewayFilterChain:把请求交给下一个过滤器
案例:定义全局过滤器,拦截并判断用户身份
判断请求的参数是否满足下面条件:
- 参数中是否有authorization,
- authorization参数值是否为admin
如果同时满足则放行,否则拦截
步骤:
①在gateway编写自定义全局过滤器
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2.获取参数的authorization
String auth = params.getFirst("authorization");
// 3.判断是否是admin
if ("admin".equals(auth)){
// 放行
return chain.filter(exchange);
}
//4.不放行就禁止
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 5.结束处理
return exchange.getResponse().setComplete();
}
}
②发起请求
总结
7.过滤器执行顺序
请求进入网关会碰到三类过滤器:DefaultFilter、当前路由的过滤器、GlobalFilter
①每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
②当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器
8.跨域问题处理
在实际项目中,前后端分成两个不同的项目,各自部署在不同的域名下,这也就会遇到跨域问题了。浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS