SpringCloud:学习Gateway网关拦截器的ServerWebExchange
1.Gateway的拦截器
我们要在项目中实现一个拦截器,需要继承两个类:GlobalFilter, Ordered
GlobalFilter:全局过滤拦截器,在gateway中已经有部分实现,具体参照:https://www.cnblogs.com/liukaifeng/p/10055862.html
Ordered:拦截器的顺序,不多说
于是一个简单的拦截器就有了
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return chain.filter(exchange);
} @Override
public int getOrder() {
return -10;
}
}
Gateway的核心接口:GatewayFilter,GlobalFilter,GatewayFilterChain。具体介绍:https://www.cnblogs.com/bjlhx/p/9786478.html
Gateway的路由转发规则介绍:https://segmentfault.com/a/1190000019101829
2.简介
我们在使用Spring Cloud Gateway的时候,注意到过滤器(包括GatewayFilter、GlobalFilter和过滤器链GatewayFilterChain),都依赖到ServerWebExchange。
这里的设计和Servlet中的Filter是相似的,当前过滤器可以决定是否执行下一个过滤器的逻辑,由GatewayFilterChain#filter()是否被调用来决定。而ServerWebExchange就相当于当前请求和响应的上下文。
ServerWebExchange实例不单存储了Request和Response对象,还提供了一些扩展方法,如果想实现改造请求参数或者响应参数,就必须深入了解ServerWebExchange。
3.ServerWebExchange
ServerWebExchange的注释: ServerWebExchange是一个HTTP请求-响应交互的契约。提供对HTTP请求和响应的访问,并公开额外的服务器端处理相关属性和特性,如请求属性。
其实,ServerWebExchange命名为服务网络交换器,存放着重要的请求-响应属性、请求实例和响应实例等等,有点像Context的角色。
3.1.所有接口
public interface ServerWebExchange { // 日志前缀属性的KEY,值为org.springframework.web.server.ServerWebExchange.LOG_ID
// 可以理解为 attributes.set("org.springframework.web.server.ServerWebExchange.LOG_ID","日志前缀的具体值");
// 作用是打印日志的时候会拼接这个KEY对饮的前缀值,默认值为""
String LOG_ID_ATTRIBUTE = ServerWebExchange.class.getName() + ".LOG_ID";
String getLogPrefix(); // 获取ServerHttpRequest对象
ServerHttpRequest getRequest(); // 获取ServerHttpResponse对象
ServerHttpResponse getResponse(); // 返回当前exchange的请求属性,返回结果是一个可变的Map
Map<String, Object> getAttributes(); // 根据KEY获取请求属性
@Nullable
default <T> T getAttribute(String name) {
return (T) getAttributes().get(name);
} // 根据KEY获取请求属性,做了非空判断
@SuppressWarnings("unchecked")
default <T> T getRequiredAttribute(String name) {
T value = getAttribute(name);
Assert.notNull(value, () -> "Required attribute '" + name + "' is missing");
return value;
} // 根据KEY获取请求属性,需要提供默认值
@SuppressWarnings("unchecked")
default <T> T getAttributeOrDefault(String name, T defaultValue) {
return (T) getAttributes().getOrDefault(name, defaultValue);
} // 返回当前请求的网络会话
Mono<WebSession> getSession(); // 返回当前请求的认证用户,如果存在的话
<T extends Principal> Mono<T> getPrincipal(); // 返回请求的表单数据或者一个空的Map,只有Content-Type为application/x-www-form-urlencoded的时候这个方法才会返回一个非空的Map -- 这个一般是表单数据提交用到
Mono<MultiValueMap<String, String>> getFormData(); // 返回multipart请求的part数据或者一个空的Map,只有Content-Type为multipart/form-data的时候这个方法才会返回一个非空的Map -- 这个一般是文件上传用到
Mono<MultiValueMap<String, Part>> getMultipartData(); // 返回Spring的上下文
@Nullable
ApplicationContext getApplicationContext(); // 这几个方法和lastModified属性相关
boolean isNotModified();
boolean checkNotModified(Instant lastModified);
boolean checkNotModified(String etag);
boolean checkNotModified(@Nullable String etag, Instant lastModified); // URL转换
String transformUrl(String url); // URL转换映射
void addUrlTransformer(Function<String, String> transformer); // 注意这个方法,方法名是:改变,这个是修改ServerWebExchange属性的方法,返回的是一个Builder实例,Builder是ServerWebExchange的内部类
default Builder mutate() {
return new DefaultServerWebExchangeBuilder(this);
} interface Builder { // 覆盖ServerHttpRequest
Builder request(Consumer<ServerHttpRequest.Builder> requestBuilderConsumer);
Builder request(ServerHttpRequest request); // 覆盖ServerHttpResponse
Builder response(ServerHttpResponse response); // 覆盖当前请求的认证用户
Builder principal(Mono<Principal> principalMono); // 构建新的ServerWebExchange实例
ServerWebExchange build();
}
}
注意到ServerWebExchange#mutate()方法,ServerWebExchange实例可以理解为不可变实例。
如果我们想要修改它,需要通过mutate()方法生成一个新的实例,后面会修改请求以及响应时会用到,暂时不做介绍
4.ServerHttpRequest
ServerHttpRequest实例是用于承载请求相关的属性和请求体,Spring Cloud Gateway中底层使用Netty处理网络请求。
通过追溯源码,可以从ReactorHttpHandlerAdapter中得知ServerWebExchange实例中持有的ServerHttpRequest实例的具体实现是ReactorServerHttpRequest。
之所以列出这些实例之间的关系,是因为这样比较容易理清一些隐含的问题,例如:ReactorServerHttpRequest的父类AbstractServerHttpRequest中初始化内部属性headers的时候把请求的HTTP头部封装为只读的实例:
// HttpHeaders类中的readOnlyHttpHeaders方法,其中ReadOnlyHttpHeaders屏蔽了所有修改请求头的方法,直接抛出UnsupportedOperationException
public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
Assert.notNull(headers, "HttpHeaders must not be null");
if (headers instanceof ReadOnlyHttpHeaders) {
return headers;
}
else {
return new ReadOnlyHttpHeaders(headers);
}
}
4.1.所有接口
ublic interface HttpMessage { // 获取请求头,目前的实现中返回的是ReadOnlyHttpHeaders实例,只读
HttpHeaders getHeaders();
} public interface ReactiveHttpInputMessage extends HttpMessage { // 返回请求体的Flux封装
Flux<DataBuffer> getBody();
} public interface HttpRequest extends HttpMessage { // 返回HTTP请求方法,解析为HttpMethod实例
@Nullable
default HttpMethod getMethod() {
return HttpMethod.resolve(getMethodValue());
} // 返回HTTP请求方法,字符串
String getMethodValue(); // 请求的URI
URI getURI();
} public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage { // 连接的唯一标识或者用于日志处理标识
String getId(); // 获取请求路径,封装为RequestPath对象
RequestPath getPath(); // 返回查询参数,是只读的MultiValueMap实例
MultiValueMap<String, String> getQueryParams(); // 返回Cookie集合,是只读的MultiValueMap实例
MultiValueMap<String, HttpCookie> getCookies(); // 远程服务器地址信息
@Nullable
default InetSocketAddress getRemoteAddress() {
return null;
} // SSL会话实现的相关信息
@Nullable
default SslInfo getSslInfo() {
return null;
} // 修改请求的方法,返回一个建造器实例Builder,Builder是内部类
default ServerHttpRequest.Builder mutate() {
return new DefaultServerHttpRequestBuilder(this);
} interface Builder { // 覆盖请求方法
Builder method(HttpMethod httpMethod); // 覆盖请求的URI、请求路径或者上下文,这三者相互有制约关系,具体可以参考API注释
Builder uri(URI uri);
Builder path(String path);
Builder contextPath(String contextPath); // 覆盖请求头
Builder header(String key, String value);
Builder headers(Consumer<HttpHeaders> headersConsumer); // 覆盖SslInfo
Builder sslInfo(SslInfo sslInfo); // 构建一个新的ServerHttpRequest实例
ServerHttpRequest build();
}
}
5.ServerHttpResponse
ServerHttpResponse实例是用于承载响应相关的属性和响应体,Spring Cloud Gateway中底层使用Netty处理网络请求。
通过追溯源码,可以从ReactorHttpHandlerAdapter中得知ServerWebExchange实例中持有的ServerHttpResponse实例的具体实现是ReactorServerHttpResponse。
之所以列出这些实例之间的关系,是因为这样比较容易理清一些隐含的问题,例如:ReactorServerHttpResponse构造函数初始化实例的时候,存放响应Header的是HttpHeaders实例,也就是响应Header是可以直接修改的。
public ReactorServerHttpResponse(HttpServerResponse response, DataBufferFactory bufferFactory) {
super(bufferFactory, new HttpHeaders(new NettyHeadersAdapter(response.responseHeaders())));
Assert.notNull(response, "HttpServerResponse must not be null");
this.response = response;
}
5.1.所有接口
public interface HttpMessage { // 获取响应Header,目前的实现中返回的是HttpHeaders实例,可以直接修改
HttpHeaders getHeaders();
} public interface ReactiveHttpOutputMessage extends HttpMessage { // 获取DataBufferFactory实例,用于包装或者生成数据缓冲区DataBuffer实例(创建响应体)
DataBufferFactory bufferFactory(); // 注册一个动作,在HttpOutputMessage提交之前此动作会进行回调
void beforeCommit(Supplier<? extends Mono<Void>> action); // 判断HttpOutputMessage是否已经提交
boolean isCommitted(); // 写入消息体到HTTP协议层
Mono<Void> writeWith(Publisher<? extends DataBuffer> body); // 写入消息体到HTTP协议层并且刷新缓冲区
Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body); // 指明消息处理已经结束,一般在消息处理结束自动调用此方法,多次调用不会产生副作用
Mono<Void> setComplete();
} public interface ServerHttpResponse extends ReactiveHttpOutputMessage { // 设置响应状态码
boolean setStatusCode(@Nullable HttpStatus status); // 获取响应状态码
@Nullable
HttpStatus getStatusCode(); // 获取响应Cookie,封装为MultiValueMap实例,可以修改
MultiValueMap<String, ResponseCookie> getCookies(); // 添加响应Cookie
void addCookie(ResponseCookie cookie);
}
6.ServerWebExchangeUtils和上下文属性
ServerWebExchangeUtils里面存放了很多静态公有的字符串KEY值(这些字符串KEY的实际值是org.springframework.cloud.gateway.support.ServerWebExchangeUtils. + 下面任意的静态公有KEY)
这些字符串KEY值一般是用于ServerWebExchange的属性(Attribute,见上文的ServerWebExchange#getAttributes()方法)的KEY
这些属性值都是有特殊的含义,在使用过滤器的时候如果时机适当可以直接取出来使用,下面逐个分析。
1.PRESERVE_HOST_HEADER_ATTRIBUTE:
是否保存Host属性,值是布尔值类型,写入位置是PreserveHostHeaderGatewayFilterFactory,使用的位置是NettyRoutingFilter,作用是如果设置为true,HTTP请求头中的Host属性会写到底层Reactor-Netty的请求Header属性中。
2.CLIENT_RESPONSE_ATTR:
保存底层Reactor-Netty的响应对象,类型是reactor.netty.http.client.HttpClientResponse
3.CLIENT_RESPONSE_CONN_ATTR:
保存底层Reactor-Netty的连接对象,类型是reactor.netty.Connection。
4.URI_TEMPLATE_VARIABLES_ATTRIBUTE:
PathRoutePredicateFactory解析路径参数完成之后,把解析完成后的占位符KEY-路径Path映射存放在ServerWebExchange的属性中,KEY就是URI_TEMPLATE_VARIABLES_ATTRIBUTE。
5.CLIENT_RESPONSE_HEADER_NAMES:
保存底层Reactor-Netty的响应Header的名称集合。
6.GATEWAY_ROUTE_ATTR:
用于存放RoutePredicateHandlerMapping中匹配出来的具体的路由(org.springframework.cloud.gateway.route.Route)实例,通过这个路由实例可以得知当前请求会路由到下游哪个服务。
7.GATEWAY_REQUEST_URL_ATTR:
java.net.URI类型的实例,这个实例代表直接请求或者负载均衡处理之后需要请求到下游服务的真实URI。
8.GATEWAY_ORIGINAL_REQUEST_URL_ATTR:
java.net.URI类型的实例,需要重写请求URI的时候,保存原始的请求URI。
9.GATEWAY_HANDLER_MAPPER_ATTR:
保存当前使用的HandlerMapping具体实例的类型简称(一般是字符串”RoutePredicateHandlerMapping”)。
10.GATEWAY_SCHEME_PREFIX_ATTR:
确定目标路由URI中如果存在schemeSpecificPart属性,则保存该URI的scheme在此属性中,路由URI会被重新构造,见RouteToRequestUrlFilter。
11.GATEWAY_PREDICATE_ROUTE_ATTR:
用于存放RoutePredicateHandlerMapping中匹配出来的具体的路由(org.springframework.cloud.gateway.route.Route)实例的ID。
12.WEIGHT_ATTR:
实验性功能(此版本还不建议在正式版本使用)存放分组权重相关属性,见WeightCalculatorWebFilter。
13.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR:
存放响应Header中的ContentType的值。
14.HYSTRIX_EXECUTION_EXCEPTION_ATTR:
Throwable的实例,存放的是Hystrix执行异常时候的异常实例,见HystrixGatewayFilterFactory。
15.GATEWAY_ALREADY_ROUTED_ATTR:
布尔值,用于判断是否已经进行了路由,见NettyRoutingFilter。
16.GATEWAY_ALREADY_PREFIXED_ATTR:
布尔值,用于判断请求路径是否被添加了前置部分,见PrefixPathGatewayFilterFactory。
ServerWebExchangeUtils提供的上下文属性用于Spring Cloud Gateway的ServerWebExchange组件处理请求和响应的时候,内部一些重要实例或者标识属性的安全传输和使用
使用它们可能存在一定的风险,因为没有人可以确定在版本升级之后,原有的属性KEY或者VALUE是否会发生改变,如果评估过风险或者规避了风险之后,可以安心使用。
例如我们在做请求和响应日志(类似Nginx的Access Log)的时候,可以依赖到GATEWAY_ROUTE_ATTR,因为我们要打印路由的目标信息。
7.修改数据
7.1.修改请求路径
可以将原来的请求路径进行修改,也可以修改其他属性,具体可以看: interface Builder下的方法
@Slf4j
@Component
public class ModifyRequestFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();//修改请求路径
String newServletPath = "/test"
ServerHttpRequest newRequest = request.mutate().path(newServletPath).build();return chain.filter(exchange.mutate().request(decorator).build());
}
7.2.修改请求数据
ServerHttpRequest的getBody方法获取的是Flux<DataBuffer>,遍历这个DataBuffer就可以取出数据
@Slf4j
@Component
public class ModifyRequestBodyGlobalFilter implements GlobalFilter { private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); @Autowired
private ObjectMapper objectMapper; @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 新建一个ServerHttpRequest装饰器,覆盖需要装饰的方法
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) { @Override
public Flux<DataBuffer> getBody() {
Flux<DataBuffer> body = super.getBody();
InputStreamHolder holder = new InputStreamHolder();
body.subscribe(buffer -> holder.inputStream = buffer.asInputStream());
if (null != holder.inputStream) {
try {
// 解析JSON的节点
JsonNode jsonNode = objectMapper.readTree(holder.inputStream);
Assert.isTrue(jsonNode instanceof ObjectNode, "JSON格式异常");
ObjectNode objectNode = (ObjectNode) jsonNode;
// JSON节点最外层写入新的属性
objectNode.put("userId", accessToken);
DataBuffer dataBuffer = dataBufferFactory.allocateBuffer();
String json = objectNode.toString();
log.info("最终的JSON数据为:{}", json);
dataBuffer.write(json.getBytes(StandardCharsets.UTF_8));
return Flux.just(dataBuffer);
} catch (Exception e) {
throw new IllegalStateException(e);
}
} else {
return super.getBody();
}
}
};
// 使用修改后的ServerHttpRequestDecorator重新生成一个新的ServerWebExchange
return chain.filter(exchange.mutate().request(decorator).build());
} private class InputStreamHolder {
InputStream inputStream;
}
}
7.3.修改路由
路由信息等存储在ServerWebExchange的属性中的,修改后重新存进去接口覆盖
@Slf4j
@Component
public class ModifyRequestFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String serviceName = exchange.getAttribute(GatewayConstant.SERVICE_NAME); //修改路由
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
Route newRoute = Route.async()
.asyncPredicate(route.getPredicate())
.filters(route.getFilters())
.id(route.getId())
.order(route.getOrder())
.uri(GatewayConstant.URI.LOAD_BALANCE+serviceName).build(); exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,newRoute);return chain.filter(exchange);
}
}
7.4.修改响应体
@Slf4j
@Component
public class ModifyResponseFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestId = exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID);
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
//释放掉内存
DataBufferUtils.release(dataBuffer);
//原始响应
String originalbody = new String(content, Charset.forName("UTF-8"));
//修改后的响应体
finalBody = JSON.toJSONString("{test:'test'}"); return bufferFactory.wrap(finalBody.getBytes());
}));
} return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
}
8.ServerWebExchange对比Servlet
这里总结部分我在写代码中遇到的一些不同与相应代替办法
8.1.request.setAttribute
在HttpServletRequest中:request.setAttribute(“key”, "value");
而在ServerHttpRequest中并无Attribute相关操作,可以存数据的HttpHeader也是read-only的
替代:ServerWebExchange --> exchange.getAttributes().put(“key”, "value");
8.2.request.getHeader()
在HttpServletRequest中:request.getHeader("test");
替代:ServerHttpRequest -->request.getHeaders().getFirst(“test”)
getFirst的原因是获取的到Headers里面的List数组,遍历Header如下:
HttpHeaders headers = request.getHeaders();
for (Map.Entry<String,List<String>> header:headers.entrySet()) {
String key = header.getKey();
List<String> values = header.getValue();
}
内容参考:http://throwable.coding.me/2019/05/18/spring-cloud-gateway-modify-request-response
SpringCloud:学习Gateway网关拦截器的ServerWebExchange的更多相关文章
- spring boot 学习(十二)拦截器实现IP黑名单
拦截器实现IP黑名单 前言 最近一直在搞 Hexo+GithubPage 搭建个人博客,所以没怎么进行 SpringBoot 的学习.所以今天就将上次的”?秒防刷新”进行了一番修改.上次是采用注解加拦 ...
- Struts2重新学习之自定义拦截器(判断用户是否是登录状态)
拦截器 一:1:概念:Interceptor拦截器类似于我们学习过的过滤器,是可以再action执行前后执行的代码.是web开发时,常用的技术.比如,权限控制,日志记录. 2:多个拦截器Interce ...
- Struts2学习笔记(拦截器配置添加)
一.拦截器工作原理: 根据Struts2的工作原理图,拦截器在action执行前进行顺序调用,之后执行Action并返回结果字符串,再逆序调用拦截器.(结构类似递归方式...)大部分时候,拦截器方法都 ...
- Struts学习之自定义拦截器
* 所有的拦截器都需要实现Interceptor接口或者继承Interceptor接口的扩展实现类 * 要重写init().intercept().destroy()方法 * in ...
- Struts2 学习笔记18 拦截器原理分析
我们来进行一下拦截器的原理分析,从Struts2的源代码开始,然后我们手动创建一个项目进行模拟.(源代码需要下载然后添加好才能看到)我们可以用Debug来读源码. 从doFilter开始执行,流程如图 ...
- springMVC3学习(七)--Interceptor拦截器
Spring为我们提供了:org.springframework.web.servlet.HandlerInterceptor接口, org.springframework.web.servlet.h ...
- Struts2学习笔记五 拦截器
拦截器,在AOP中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作.拦截是AOP的一种实现策略. Struts2中,拦截器是动态拦截Action调用的对象.它提供了一种机制可以使 ...
- SpringMVC学习笔记:拦截器和过滤器
首先说明一下二者的区别: 1. 拦截器基于java的反射机制,而过滤器是基于函数回调 2. 拦截器不依赖于servlet容器,过滤器依赖servlet容器 3. 拦截器只能对action请求起作用,而 ...
- springMVC学习 十二 拦截器
一 拦截器概述 拦截器技术比较像java web技术中的过滤器技术,都是发送 请求时被拦截器拦截,在控制器的前后添加额外功能.但是和Spring中的Aop技术是由区别的.AOP 在特定方法前后扩充(一 ...
随机推荐
- Spring怎么管理事务?
我们一般通过aop管理事务,就是把代码看成一个纵向有序的,然后通过aop管理事务,就好比增删改的时候需要开启一个事务,我们给他配置一个required,required就是有事务就执行事务,没有就给他 ...
- CSS文本居中问题
文本水平居中 水平居中比较简单,将对应的html元素text-align属性值为center,其子元素就会水平居中. 文本垂直居中 单行文本垂直居中 设置文本元素的line-height属性值为元素高 ...
- ECharts堆叠柱状图label显示总和
Echarts本身没提供现成的解决方案. option = { title: { text: '分类销量' }, legend: { y: "bottom", data: ['百货 ...
- HTML5中重新定义的 b 和 i 元素
HTML5强调元素的语义,而非表现.b和i元素是早期HTML遗留下来的产物,它们分别用于将文本变为粗体和斜体(那时CSS还未出现). 当时的规范建议编码人员用strong替代b,用em替代i.不过,事 ...
- 【JMeter】压力测试工具的概览与使用
软件工程综合实践第五次个人作业 作业要求:在软件测试章节中,我们介绍了不少VSTS的软件测试工具,请使用一些其他平台上的测试工具,并写博客介绍如何在你的项目中具体使用. 前言: 第一次看到这个作业 ...
- 自定义View(四),onMeasure
转自:http://blog.csdn.net/a396901990/article/details/36475213 简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--on ...
- springboot 单元测试 指定启动类
问题 在做单元测试时,写了一个工具类,用于注入spring的上下文. public class AppBeanUtil implements ApplicationContextAware { pri ...
- 《linux就该这么学》课堂笔记04 常用命令cat、mor...tar、find
本节命令汇总 命令 说明 格式 常用参数 实例 备注 cat 查看纯文本文件(内容较少) cat [选项] 文件名称 -n 显示行号 cat -n install-setup-ks.cfg 查看ins ...
- Spring Cloud 之 Feign 知识点:封装了 REST 调用
Feign Client 会在底层根据你的注解,跟你指定的服务建立连接.构造请求.发起请求.获取响应.解析响应,等等. Feign 的一个关键机制就是使用了动态代理. 首先,如果你对某个接口定义了 @ ...
- abp记录2
AbpCoreInstaller只是完成注册系统框架级的所有配置类.Abp支持自动完成符合Conventional(基于约定)的组件的注册. Conventional 的规则要通过继承IConvent ...