一、GatewayProperties

1.1、在GatewayAutoConfiguration中加载

  在Spring-Cloud-Gateway初始化时,同时GatewayAutoConfiguration核心配置类会被初始化加载如下 :

NettyConfiguration 底层通信netty配置
GlobalFilter (AdaptCachedBodyGlobalFilter,RouteToRequestUrlFilter,ForwardRoutingFilter,ForwardPathFilter,WebsocketRoutingFilter,WeightCalculatorWebFilter等)
FilteringWebHandler
GatewayProperties
PrefixPathGatewayFilterFactory
RoutePredicateFactory
RouteDefinitionLocator
RouteLocator
RoutePredicateHandlerMapping 查找匹配到 Route并进行处理
GatewayWebfluxEndpoint 管理网关的 HTTP API

  其中在GatewayAutoConfiguration配置加载中含初始化加载GatewayProperties实例的配置:

查看GatewayAutoConfiguration源码:

    @Beanpublic GatewayProperties gatewayProperties() {return new GatewayProperties();}

1.2、再次查看GatewayProperties源码:

@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties {@NotNull@Validprivate List<RouteDefinition> routes = new ArrayList();private List<FilterDefinition> defaultFilters = new ArrayList();private List<MediaType> streamingMediaTypes;public GatewayProperties() {this.streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);}public List<RouteDefinition> getRoutes() {return this.routes;}public void setRoutes(List<RouteDefinition> routes) {this.routes = routes;}public List<FilterDefinition> getDefaultFilters() {return this.defaultFilters;}public void setDefaultFilters(List<FilterDefinition> defaultFilters) {this.defaultFilters = defaultFilters;}public List<MediaType> getStreamingMediaTypes() {return this.streamingMediaTypes;}public void setStreamingMediaTypes(List<MediaType> streamingMediaTypes) {this.streamingMediaTypes = streamingMediaTypes;}public String toString() {return "GatewayProperties{routes=" + this.routes + ", defaultFilters=" + this.defaultFilters + ", streamingMediaTypes=" + this.streamingMediaTypes + '}';}
}

以上会被默认加载并且读取配置信息,如下配置信息:

  • spring.cloud.gateway.routes:网关路由定义配置,列表形式
  • spring.cloud.gateway.default-filters: 网关默认过滤器定义配置,列表形式
  • spring.cloud.gateway.streamingMediaTypes:网关网络媒体类型,列表形式

其中routes是RouteDefinition集合,defaultFilters是FilterDefinition集合,参看具体的配置字段。实际配置文件可如下:

spring:cloud:gateway:default-filters:- PrefixPath=/httpbin- AddResponseHeader=X-Response-Default-Foo, Default-Barroutes:- id: websocket_testuri: ws://localhost:9000order: 9000predicates:- Path=/echo- id: default_path_to_httpbinuri: ${test.uri}order: 10000predicates:- Path=/**

注意:default-filters的配置PrefixPath=/httpbin字符串,可以查看FilterDefinition的构造函数,它其中构造函数包含接收一个text字符串解析字符传并创建实例信息。predicates的配置也是如此。

字符传格式:name=param1,param2,param3

    public FilterDefinition(String text) {int eqIdx = text.indexOf("=");if (eqIdx <= 0) {this.setName(text);} else {this.setName(text.substring(0, eqIdx));String[] args = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ",");for(int i = 0; i < args.length; ++i) {this.args.put(NameUtils.generateName(i), args[i]);}}}

二、Route初始化加载

2.1、GatewayAutoConfiguration加载RouteLocator

  Spring-Cloud-Gateway路由信息是通过路由定位器RouteLocator加载以及初始化。

在Spring-Cloud-Gateway初始化时,同时GatewayAutoConfiguration核心配置类会被初始化加载如下 :
    /*** 创建一个根据RouteDefinition转换的路由定位器*/@Beanpublic RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,List<GatewayFilterFactory> GatewayFilters,List<RoutePredicateFactory> predicates,RouteDefinitionLocator routeDefinitionLocator) {return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, properties);}/*** 创建一个缓存路由的路由定位器* @param routeLocators* @return*/@Bean@Primary//在相同的bean中,优先使用用@Primary注解的bean.public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {//1.创建组合路由定位器,根据(容器)已有的路由定位器集合//2.创建缓存功能的路由定位器return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));}

路由定位器的创建流程:

1、RouteDefinitionRouteLocator
2、CompositeRouteLocator
3、CachingRouteLocator
其中 RouteDefinitionRouteLocator 是获取路由的主要地方,CompositeRouteLocator,CachingRouteLocator对路由定位器做了附加功能的包装,最终使用的是CachingRouteLocator对外提供服务

2.2、查看RouteLocator源码:

/*** 路由定位器,服务获取路由信息* 可以通过 RouteDefinitionRouteLocator 获取 RouteDefinition ,并转换成 Route*/
public interface RouteLocator {/*** 获取路由*/Flux<Route> getRoutes();
}

查看RouteLocator实现类

缓存功能实现→CachingRouteLocator
组合功能实现→CompositeRouteLocator
通过路由定义转换路由实现→RouteDefinitionRouteLocator

2.3、缓存功能实现→CachingRouteLocator

// 路由定位器的包装类,实现了路由的本地缓存功能
public class CachingRouteLocator implements RouteLocator {//目标路由定位器private final RouteLocator delegate;/*** 路由信息* Flux 相当于一个 RxJava Observable,* 能够发出 0~N 个数据项,然后(可选地)completing 或 erroring。处理多个数据项作为stream*/private final Flux<Route> routes;// 本地缓存,用于缓存路由定位器获取的路由集合private final Map<String, List> cache = new HashMap<>();public CachingRouteLocator(RouteLocator delegate) {this.delegate = delegate;routes = CacheFlux.lookup(cache, "routes", Route.class).onCacheMissResume(() -> this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE));}@Overridepublic Flux<Route> getRoutes() {return this.routes;}// 刷新缓存public Flux<Route> refresh() {this.cache.clear();return this.routes;}@EventListener(RefreshRoutesEvent.class)void handleRefresh() {refresh();}
}

1、路由信息的本地缓存,通过Map<String, List> cache 缓存路由到内存中;
2、此类通过@EventListener(RefreshRoutesEvent.class)监听RefreshRoutesEvent事件实现了对缓存的动态刷新;

注:路由动态刷新,使用GatewayControllerEndpoint发布刷新事件

@RestControllerEndpoint(id = "gateway")
public class GatewayControllerEndpoint implements ApplicationEventPublisherAware{// 调用url= /gateway/refresh 刷新缓存中的路由信息@PostMapping("/refresh")public Mono<Void> refresh() {this.publisher.publishEvent(new RefreshRoutesEvent(this));return Mono.empty();}
}

2.4、组合功能实现→CompositeRouteLocator

//组合多个 RRouteLocator 的实现,为Route提供统一获取入口
public class CompositeRouteLocator implements RouteLocator {/*** 能够发出 0~N 个数据项(RouteLocator),然后(可选地)completing 或 erroring。处理多个数据项作为stream*/private final Flux<RouteLocator> delegates;public CompositeRouteLocator(Flux<RouteLocator> delegates) {this.delegates = delegates;}@Overridepublic Flux<Route> getRoutes() {//this.delegates.flatMap((routeLocator)-> routeLocator.getRoutes());return this.delegates.flatMap(RouteLocator::getRoutes);}
}

此类将遍历传入的目录路由定位器集合,组合每个路由定位器获取到的路由信息

2.5、通过路由定义转换路由实现→RouteDefinitionRouteLocator

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cloud.gateway.route;import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.event.FilterArgsEvent;
import org.springframework.cloud.gateway.event.PredicateArgsEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.cloud.gateway.route.Route.AsyncBuilder;
import org.springframework.cloud.gateway.support.ConfigurationUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.Validator;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware {protected final Log logger = LogFactory.getLog(this.getClass());private final RouteDefinitionLocator routeDefinitionLocator;private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap();private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap();private final GatewayProperties gatewayProperties;private final SpelExpressionParser parser = new SpelExpressionParser();private BeanFactory beanFactory;private ApplicationEventPublisher publisher;@Autowiredprivate Validator validator;public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator, List<RoutePredicateFactory> predicates, List<GatewayFilterFactory> gatewayFilterFactories, GatewayProperties gatewayProperties) {this.routeDefinitionLocator = routeDefinitionLocator;this.initFactories(predicates);gatewayFilterFactories.forEach((factory) -> {GatewayFilterFactory var10000 = (GatewayFilterFactory)this.gatewayFilterFactories.put(factory.name(), factory);});this.gatewayProperties = gatewayProperties;}public void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {this.publisher = publisher;}private void initFactories(List<RoutePredicateFactory> predicates) {predicates.forEach((factory) -> {String key = factory.name();if (this.predicates.containsKey(key)) {this.logger.warn("A RoutePredicateFactory named " + key + " already exists, class: " + this.predicates.get(key) + ". It will be overwritten.");}this.predicates.put(key, factory);if (this.logger.isInfoEnabled()) {this.logger.info("Loaded RoutePredicateFactory [" + key + "]");}});}public Flux<Route> getRoutes() {return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute).map((route) -> {if (this.logger.isDebugEnabled()) {this.logger.debug("RouteDefinition matched: " + route.getId());}return route;});}private Route convertToRoute(RouteDefinition routeDefinition) {AsyncPredicate<ServerWebExchange> predicate = this.combinePredicates(routeDefinition);List<GatewayFilter> gatewayFilters = this.getFilters(routeDefinition);return ((AsyncBuilder)Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters)).build();}private List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {List<GatewayFilter> filters = (List)filterDefinitions.stream().map((definition) -> {GatewayFilterFactory factory = (GatewayFilterFactory)this.gatewayFilterFactories.get(definition.getName());if (factory == null) {throw new IllegalArgumentException("Unable to find GatewayFilterFactory with name " + definition.getName());} else {Map<String, String> args = definition.getArgs();if (this.logger.isDebugEnabled()) {this.logger.debug("RouteDefinition " + id + " applying filter " + args + " to " + definition.getName());}Map<String, Object> properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory);Object configuration = factory.newConfig();ConfigurationUtils.bind(configuration, properties, factory.shortcutFieldPrefix(), definition.getName(), this.validator);GatewayFilter gatewayFilter = factory.apply(configuration);if (this.publisher != null) {this.publisher.publishEvent(new FilterArgsEvent(this, id, properties));}return gatewayFilter;}}).collect(Collectors.toList());ArrayList<GatewayFilter> ordered = new ArrayList(filters.size());for(int i = 0; i < filters.size(); ++i) {GatewayFilter gatewayFilter = (GatewayFilter)filters.get(i);if (gatewayFilter instanceof Ordered) {ordered.add(gatewayFilter);} else {ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));}}return ordered;}private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {List<GatewayFilter> filters = new ArrayList();if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {filters.addAll(this.loadGatewayFilters("defaultFilters", this.gatewayProperties.getDefaultFilters()));}if (!routeDefinition.getFilters().isEmpty()) {filters.addAll(this.loadGatewayFilters(routeDefinition.getId(), routeDefinition.getFilters()));}AnnotationAwareOrderComparator.sort(filters);return filters;}private AsyncPredicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) {List<PredicateDefinition> predicates = routeDefinition.getPredicates();AsyncPredicate<ServerWebExchange> predicate = this.lookup(routeDefinition, (PredicateDefinition)predicates.get(0));AsyncPredicate found;for(Iterator var4 = predicates.subList(1, predicates.size()).iterator(); var4.hasNext(); predicate = predicate.and(found)) {PredicateDefinition andPredicate = (PredicateDefinition)var4.next();found = this.lookup(routeDefinition, andPredicate);}return predicate;}private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) {RoutePredicateFactory<Object> factory = (RoutePredicateFactory)this.predicates.get(predicate.getName());if (factory == null) {throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName());} else {Map<String, String> args = predicate.getArgs();if (this.logger.isDebugEnabled()) {this.logger.debug("RouteDefinition " + route.getId() + " applying " + args + " to " + predicate.getName());}Map<String, Object> properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory);Object config = factory.newConfig();ConfigurationUtils.bind(config, properties, factory.shortcutFieldPrefix(), predicate.getName(), this.validator);if (this.publisher != null) {this.publisher.publishEvent(new PredicateArgsEvent(this, route.getId(), properties));}return factory.applyAsync(config);}}
}

View Code

此类的核心方法getRoutes通过传入的routeDefinitionLocator获取路由定位,并循环遍历路由定位依次转换成路由返回,
代码中可以看到getRoutes通过convertToRoute方法将路由定位转换成路由的

2.5.1、RouteDefinition转换:convertToRoute

    // RouteDefinition 转换为对应的Routeprivate Route convertToRoute(RouteDefinition routeDefinition) {//获取routeDefinition中的Predicate信息Predicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);//获取routeDefinition中的GatewayFilter信息List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);//构建路由信息return Route.builder(routeDefinition).predicate(predicate).replaceFilters(gatewayFilters).build();}

convertToRoute方法功能作用
  获取routeDefinition中的Predicate信息 (通过combinePredicates方法)
  获取routeDefinition中的GatewayFilter信息(通过gatewayFilters方法)
  构建路由信息

1、convertToRoute中combinePredicates获取routeDefinition中的Predicate信息如下:

    // 返回组合的谓词private Predicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) {//获取RouteDefinition中的PredicateDefinition集合List<PredicateDefinition> predicates = routeDefinition.getPredicates();Predicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0));for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) {Predicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate);//流程4//返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑ANDpredicate = predicate.and(found);}return predicate;}/*** 获取一个谓语定义(PredicateDefinition)转换的谓语* @param route* @param predicate* @return*/@SuppressWarnings("unchecked")private Predicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) {//流程1//流程1==获取谓语创建工厂RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());if (factory == null) {throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName());}//流程2//获取参数Map<String, String> args = predicate.getArgs();if (logger.isDebugEnabled()) {logger.debug("RouteDefinition " + route.getId() + " applying "+ args + " to " + predicate.getName());}//组装参数Map<String, Object> properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory);//构建创建谓语的配置信息Object config = factory.newConfig();ConfigurationUtils.bind(config, properties,factory.shortcutFieldPrefix(), predicate.getName(), validator);if (this.publisher != null) {this.publisher.publishEvent(new PredicateArgsEvent(this, route.getId(), properties));}//流程3//通过谓语工厂构建谓语return factory.apply(config);}

获取Predicate流程:

  • 根据PredicateDefinition name 获取 RoutePredicateFactory
  • 根据PredicateDefinition args 组装 config信息
  • 通过RoutePredicateFactory 根据config信息创建Predicate信息
  • 多个Predicate 以短路逻辑AND组合

2、convertToRoute中 getFilters获取routeDefinition中的GatewayFilter信息

private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {List<GatewayFilter> filters = new ArrayList<>();//校验gatewayProperties是否含义默认的过滤器集合if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {//加载全局配置的默认过滤器集合filters.addAll(loadGatewayFilters("defaultFilters",this.gatewayProperties.getDefaultFilters()));}if (!routeDefinition.getFilters().isEmpty()) {//加载路由定义中的过滤器集合
            filters.addAll(loadGatewayFilters(routeDefinition.getId(), routeDefinition.getFilters()));}//排序
        AnnotationAwareOrderComparator.sort(filters);return filters;}/*** 加载过滤器,根据过滤器的定义加载* @param id* @param filterDefinitions* @return*/@SuppressWarnings("unchecked")private List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {//遍历过滤器定义,将过滤器定义转换成对应的过滤器List<GatewayFilter> filters = filterDefinitions.stream().map(definition -> {//流程1    //通过过滤器定义名称获取过滤器创建工厂GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());if (factory == null) {throw new IllegalArgumentException("Unable to find GatewayFilterFactory with name " + definition.getName());}//流程2//获取参数Map<String, String> args = definition.getArgs();if (logger.isDebugEnabled()) {logger.debug("RouteDefinition " + id + " applying filter " + args + " to " + definition.getName());}//根据args组装配置信息Map<String, Object> properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory);//构建过滤器创建配置信息Object configuration = factory.newConfig();ConfigurationUtils.bind(configuration, properties,factory.shortcutFieldPrefix(), definition.getName(), validator);//流程3//通过过滤器工厂创建GatewayFilterGatewayFilter gatewayFilter = factory.apply(configuration);if (this.publisher != null) {//发布事件this.publisher.publishEvent(new FilterArgsEvent(this, id, properties));}return gatewayFilter;}).collect(Collectors.toList());ArrayList<GatewayFilter> ordered = new ArrayList<>(filters.size());//包装过滤器使其所有过滤器继承Ordered属性,可进行排序for (int i = 0; i < filters.size(); i++) {GatewayFilter gatewayFilter = filters.get(i);if (gatewayFilter instanceof Ordered) {ordered.add(gatewayFilter);}else {ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));}}return ordered;}

  • getFilters 方法 同时加载 全局配置 gatewayProperties与routeDefinition配置下的所有过滤器定义filterDefinitions
  • loadGatewayFilters 负责将filterDefinition转化成对应的GatewayFilter
    转化流程如下
  1. 根据filterDefinition name 获取 GatewayFilterFactory
  2. 根据filterDefinition args 组装 config信息
  3. 通过GatewayFilterFactory 根据config信息创建PGatewayFilter信息

006-spring cloud gateway-GatewayAutoConfiguration核心配置-GatewayProperties初始化加载、Route初始化加载...相关推荐

  1. spring cloud gateway的stripPrefix配置

    序 本文主要研究下spring cloud gateway的stripPrefix配置 使用zuul的配置 zuul:routes:demo:sensitiveHeaders: Access-Cont ...

  2. Nacos + Spring Cloud Gateway动态路由配置

    前言 Nacos最近项目一直在使用,其简单灵活,支持更细粒度的命令空间,分组等为麻烦复杂的环境切换提供了方便:同时也很好支持动态路由的配置,只需要简单的几步即可.在国产的注册中心.配置中心中比较突出, ...

  3. (十八)Alian 的 Spring Cloud Gateway 集群配置

    目录 一.简介 二.配置 三.配置文件 3.1.application.properties 四.主类 五.部署及配置 5.1.部署 5.2.Nginx配置 5.3.Spring Cloud Gate ...

  4. Spring Cloud Gateway 源码解析(1) —— 基础

    目录 Gateway初始化 启用Gateway GatewayClassPathWarningAutoConfiguration GatewayLoadBalancerClientAutoConfig ...

  5. Spring Cloud GateWay系列(三):路由规则动态刷新

    Spring Cloud Gateway旨在提供一种简单而有效的方式来路由API,并为它们提供横切关注点,例如:安全性.监控/指标和弹性.Route(路由)是网关的基本单元,由唯一标识符ID.目标地址 ...

  6. Spring Cloud Gateway 源码解析(2) —— 路由

    目录 基本组件 路由定位器(RouteDefinitionLocator ) 路由定义(RouteDefinition) PredicateDefinition FilterDefinition Co ...

  7. 有什么办法动态更改yml的值吗_基于Redis实现Spring Cloud Gateway的动态管理

    转载本文需注明出处:微信公众号EAWorld,违者必究. 引言: Spring Cloud Gateway是当前使用非常广泛的一种API网关.它本身能力并不能完全满足企业对网关的期望,人们希望它可以提 ...

  8. 网关Spring Cloud Gateway科普

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"加群"获取公众号专属群聊入口 欢迎跳转到本文的原文链接:https://honeypp ...

  9. Spring Cloud Gateway一次请求调用源码解析

    简介: 最近通过深入学习Spring Cloud Gateway发现这个框架的架构设计非常简单.有效,很多组件的设计都非常值得学习,本文就Spring Cloud Gateway做一个简单的介绍,以及 ...

最新文章

  1. Windows phone 8 学习笔记(5) 图块与通知
  2. 标签页使用及bug解决
  3. java new url 带密码_获取密码重置URL
  4. OpenCASCADE:拓扑 API之历史支持
  5. spring 同时配置hibernate and jdbc 事务
  6. DevC++最新汉化版(支持C++11)
  7. list容器java_【Java容器】List容器使用方法及源码分析
  8. 安卓手电筒_将价值10美元的手电筒砍入超高亮高级灯中
  9. Android学习问题:关于AlertDialog中自定义布局带有的EditText无法弹出键盘
  10. 毕业这几年的嵌入式开发之路
  11. leetcode链表题
  12. linux mysql 临时文件_linux下mysql自动备份数据库与自动删除临时文件
  13. 高效开发 Android App 的 10 个建议
  14. Android 系统(200)---Android build.prop参数详解
  15. 【Elasticsearch】es CPU热点线程 HotThreads 源码解析
  16. turtlebot3 模型没有显示_Turtlebot3新手教程:Open-Manipulator机械臂
  17. 严防ARP病毒的六个步骤
  18. 立志做个有激情的coder
  19. 利用imageio将多张.jpg转.gif图片(Python3)
  20. Unity材质:玻璃

热门文章

  1. MVC 视图与控制器传值的几种方法
  2. 在windows7下安装CentOS
  3. 2012是团购移动电商年
  4. C#中处理XML文档的方法
  5. RealSync异构热容灾解决方案
  6. 电脑b站html加速播放,b站投稿如何提高播放速度?如何2倍速?b站播放器选择倍速快捷方式...
  7. python cgitb_python CGI 编程实践
  8. 提高sqlmap爆破效率
  9. DuckHunter Attacks
  10. 调用支付宝接口android最新,Android 外接sdk之支付宝