文章目录

  • Swagger简介
  • Springfox-Swagger简介
  • 整合版本
  • 整合步骤
    • 1.添加依赖
    • 2.添加静态资源
    • 3.添加配置类
    • 4. 扫描配置类
  • 常见问题
    • 启动报错Cannot find cache named 'models' for CacheableOperation...
    • 启动报错NotSerializableException,但能正常运行
    • 显示swagger-ui页面但不显示接口列表
  • 优化重复扫描controller包问题
  • 注解说明
    • @Api
    • @ApiOperation、@ApiResponses、@ApiParam
    • @ApiModel、@ApiModelProperty
  • 其它

Swagger简介

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。SwaggerCore是OpenAPI规范的Java实现。

总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法、参数和模型紧密集成到服务器端的代码,允许 API 来始终保持同步。Swagger 让部署管理和使用功能强大的 API 从未如此简单。

开源的部分包括:

  • OpenAPI Specification:API规范,规定了如何描述一个系统的API
  • Swagger Codegen:用于通过API规范生成服务端和客户端代码
  • Swagger Editor:用来编写API规范
  • Swagger UI:用于展示API规范

Springfox-Swagger简介

一、由来

由于Spring的流行,Marty Pitt编写了一个基于Spring的组件swagger-springmvc,用于将swagger集成到springmvc中来。而springfox则是从这个组件发展而来,同时springfox也是一个新的项目,本文仍然是使用其中的一个组件springfox-swagger2。

pringfox-swagger2依然是依赖OSA规范文档,也就是一个描述API的json文件,而这个组件的功能就是帮助我们自动生成这个json文件,我们会用到的另外一个组件springfox-swagger-ui就是将这个json文件解析出来,用一种更友好的方式呈现出来。

Springfox是一个通过扫描代码提取代码中的信息,生成API文档的工具。API文档的格式不止Swagger的OpenAPI Specification,还有RAML,jsonapi,Springfox的目标同样包括支持这些格式。这就能解释那个swagger2的后缀了,这只是Springfox对Swagger的支持。

在Swagger的教程中,都会提到@Api、@ApiModel、@ApiOperation这些注解,这些注解其实不是Springfox的,而是Swagger的。springfox-swagger2这个包依赖了swagger-core这个包,而这些注解正是在这里面。但是,swagger-core这个包是只支持JAX-RS2的,并不支持常用的Spring MVC。这就是springfox-swagger的作用了,它将上面那些用于JAX-RS2的注解适配到了Spring MVC上。

二、配置流程

我们知道,我们的第一个任务就是生成一个满足OSA规范的json文件(当然,创建一个spring的项目就不说了)。对于这个任务,springfox为我们提供了一个Docket(摘要的意思)类,我们需要把它做成一个Bean注入到spring中,显然,我们需要一个配置文件,并通过一种方式(显然它会是一个注解)告诉程序,这是一个Swagger配置文件。

一个OSA规范文档需要许多信息来描述这个API,springfox允许我们将信息组合成一个ApiInfo的类,作为构造参数传给Docket(当然也可以不构造这个类,而直接使用null,但是你的这个API就太low了)。

接下来,我们要写控制器了,当然这不重要,不用springfox你依然要写控制器,重要的是要告诉springfox,这个控制器是一个需要他来收集API信息的控制器,不用说,这依然会采用注解的方式,同时,我们为了将配置文件与控制器结合起来,需要在配置文件中指明在什么位置收集可能是API的控制器的信息。

到这里,生成OSA规范的json文件的配置就结束了。虽然生成过程比我叙述的更复杂,但这些程序都会帮我们完成,我们可以通过类似http://localhost:8080/demo/v2/api-docs的路径来查看这个json文件。这个v2/api-docs就是springfox默认的生成文档的路径。

接下来,我们需要将它可视化显示出来,如果使用swagger-springmvc,我们需要单独去下载一个swagger ui的显示页面包,并将其中的路径改为上面的http://localhost:8080/demo/v2/api-docs,这里你就可以感受到,swagger ui就是在解析一个json文件了。你依然可以这么做,不过springfox专门提供了一个springfox-swagger-ui组件,不需要配置,我们只需要引入这个依赖的组件就可以看到最终的效果了,而这个路由会是http://localhost:8080/demo/swagger-ui.html。

整合版本

注意Springfox和Spring存在版本对应关系,否则可能会整合失败,由于我是在已稳定运行的Spring4.1.3项目上整合Springfox,所以我在springfox源码库中(https://gitee.com/mirrors/springfox/blob/2.1.2/gradle/dependencies.gradle)或者去maven仓库中,找了个依赖的Spring版本跟Spring4.1.3接近的,是Springfox2.1.2。

若使用不当,springfox-swagger2在集成的时候可能会引入spring的相关jar,且与项目中的spring版本不一致,就会导致冲突,此时需要排除掉swagger中的spring依赖。

整合步骤

1.添加依赖

pom.xml

<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.1.2</version>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.1.2</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.6.6</version>
</dependency>
<!-- FastJson的版本必须在1.2.10以上,不然访问/v2/api-docs返回为空 -->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.30</version>
</dependency>

2.添加静态资源

mvc.xml

<mvc:annotation-driven /><!-- 静态资源配置 ,location必须是webapp根目录下的路径。注:WEB-INF文件夹里的内容是受保护的,即使在这里配置了还是不能直接访问 -->
<mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/"/>
<mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/"/>

3.添加配置类

SwaggerConfig.java

// 必须存在,要被Spring容器扫描到(@Component元注解)
@Configuration
// 必须存在,指示启用Swagger支持,Spring容器才会创建SpringMvcDocumentationConfiguration中定义的那些bean
@EnableSwagger2
// 必须存在,因为Swagger是基于MVC的,只从MVC容器中找需要的bean而不从Spring容器中找,所以需要开启mvc。
@EnableWebMvc
// 若Spring.xml中已配置了该扫描范围,则这里可不写
@ComponentScan(basePackages = {"com.xx"}, useDefaultFilters = false, includeFilters = {@Filter(type = FilterType.ANNOTATION, value = Controller.class)})
@Slf4j
public class SwaggerConfig {@PostConstructpublic void init() {log.info("[swagger]开始初始化--->ok");}/*** 可以创建多个Docket-bean* @return*/@Beanpublic Docket createRestApi() {Docket docket = new Docket(DocumentationType.SWAGGER_2)// Docket.enabled:是否暴露接口文档,默认为true,建议生产环境设置为false.enable(true).apiInfo(apiInfo())// 对DocumentationContext的RequestHandlers(在Spring.xml + @ComponentScan中配置的扫描范围内)的默认ApiSelector为ApiSelector.DEFAULT。// 其中requestHandlerSelector属性=类上和方法上都没有@ApiIgnore;pathSelectors属性=任何路径都满足条件(always true),注:默认不管是否有@Api、@ApiOperation// 可以调用该方法创建一个ApiSelectorBuilder,从而新增自定义的ApiSelector,最终ApiSelector = ApiSelector.DEFAULT and (自定义的ApiSelector).select()// 对DocumentationContext的RequestHandlers(在Spring.xml + @ComponentScan中配置的扫描范围内)新增自定义的ApiSelector//.apis(RequestHandlerSelectors.basePackage("com.hd.controller"))//.apis(RequestHandlerSelectors.withClassAnnotation(Api.class)).paths(PathSelectors.any()).build();return docket;}/*** api文档的描述信息,用于文档页面展示,可不配置* @return*/private ApiInfo apiInfo() {ApiInfo apiInfo = new ApiInfoBuilder().title("xx系统V1.0").description("xx系统接口文档说明").version("1.0").build();return apiInfo;}
}

4. 扫描配置类

spring.xml

<context:component-scan base-package="com.xx" ><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/><context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>

mvc.xml

<context:component-scan base-package="com.xx.**.controller" use-default-filters="false"><context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/><context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>

常见问题

启动报错Cannot find cache named ‘models’ for CacheableOperation…

java.lang.IllegalArgumentException: Cannot find cache named 'models' for CacheableOperation[public com.google.common.base.Optional springfox.documentation.schema.DefaultModelProvider.modelFor(springfox.documentation.spi.schema.contexts.ModelContext)] caches=[models] | key='T(springfox.documentation.schema.ModelCacheKeys).modelContextKey(#modelContext)' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless=''at org.springframework.cache.interceptor.CacheAspectSupport.getCaches(CacheAspectSupport.java:161)at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.<init>(CacheAspectSupport.java:384)at org.springframework.cache.interceptor.CacheAspectSupport.getOperationContext(CacheAspectSupport.java:171)at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContexts.<init>(CacheAspectSupport.java:350)at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:181)at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:60)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)at springfox.documentation.schema.DefaultModelProvider$$EnhancerBySpringCGLIB$$1.modelFor(<generated>)at springfox.documentation.spring.web.scanners.ApiModelReader.read(ApiModelReader.java:66)at springfox.documentation.spring.web.scanners.ApiListingScanner.scan(ApiListingScanner.java:91)at springfox.documentation.spring.web.scanners.ApiDocumentationScanner.scan(ApiDocumentationScanner.java:67)at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.scanDocumentation(DocumentationPluginsBootstrapper.java:102)at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.onApplicationEvent(DocumentationPluginsBootstrapper.java:88)at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.onApplicationEvent(DocumentationPluginsBootstrapper.java:51)at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:98)at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:333)

根据错误日志打断点发现,Springfox确实会使用到缓存,并定义有springfox.documentation.annotations.Cacheable,全局查找发现会用到4个缓存:

所以需要我们先定义好这4个缓存,可以通过JavaConf方式也可以通过xml方式。
方式一、JavaConf方式:

@Bean
public CacheManager cacheManager() {SimpleCacheManager cacheManager = new SimpleCacheManager();List<Cache> caches = new ArrayList<Cache>();caches.add(new ConcurrentMapCache("models"));cacheManager.setCaches(caches);return cacheManager;
}

方式二、xml方式:我的项目是基于ehcache实现缓存的,spring.xml中有如下配置:

<!-- 启用缓存注解功能,这个是必须的,否则注解不会生效 -->
<cache:annotation-driven cache-manager="cacheManager" />
<!-- cacheManager工厂类,指定ehcache.xml的位置 -->
<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">  <property name="configLocation" value="classpath:ehcache-setting.xml"></property>
</bean>
<!-- 声明cacheManager -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">  <property name="cacheManager" ref="cacheManagerFactory"></property>
</bean>

所以我需要在ehcache.xml中新增这4个缓存:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache><diskStore path="/app/application/ehcacheDir" /><!-- 设定缓存的默认数据过期策略 --><!-- timeToLiveSeconds=x:缓存自创建日期起能够存活的最长时间,单位为秒(s);timeToIdleSeconds=y:缓存创建以后,最后一次访问缓存的日期至失效之时的时间间隔y,单位为秒(s);只有在eternal为false时,这2个属性才有效。timeToLiveSeconds必须大于timeToIdleSeconds才有意义。一个缓存的最长存活时间为timeToLiveSeconds!--><defaultCache maxElementsInMemory="10000" eternal="false"overflowToDisk="true" timeToIdleSeconds="10" timeToLiveSeconds="20"diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /><!-- springfox-swagger启动报错说找不到这些cache --><cache name="models" maxElementsInMemory="1000" eternal="false"overflowToDisk="true" timeToIdleSeconds="1800" timeToLiveSeconds="86400"diskPersistent="true" diskExpiryThreadIntervalSeconds="120"/><cache name="modelDependencies" maxElementsInMemory="1000" eternal="false"overflowToDisk="true" timeToIdleSeconds="1800" timeToLiveSeconds="86400"diskPersistent="true" diskExpiryThreadIntervalSeconds="120"/><cache name="modelProperties" maxElementsInMemory="1000" eternal="false"overflowToDisk="true" timeToIdleSeconds="1800" timeToLiveSeconds="86400"diskPersistent="true" diskExpiryThreadIntervalSeconds="120"/><cache name="operations" maxElementsInMemory="1000" eternal="false"overflowToDisk="true" timeToIdleSeconds="1800" timeToLiveSeconds="86400"diskPersistent="true" diskExpiryThreadIntervalSeconds="120"/>
</ehcache>

注意,这里将overflowToDiskdiskPersistent属性设置为true,可能会有问题,具体情况请看下面这个报错。

启动报错NotSerializableException,但能正常运行

[operations.data 2021-02-25 11:40:04.083] [ERROR] DiskStorageFactory$DiskWriteTask.call:488 Disk Write of /exec/updateDrmStatus/{trategyName}.execUpdateDrmStatus.DefaultGenericTypeNamingStrategy failed:
java.io.NotSerializableException: springfox.documentation.service.Operationat java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)at java.util.ArrayList.writeObject(ArrayList.java:762)at sun.reflect.GeneratedMethodAccessor82.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1028)at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)at net.sf.ehcache.Element.writeObject(Element.java:875)at sun.reflect.GeneratedMethodAccessor86.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1028)at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)at net.sf.ehcache.util.MemoryEfficientByteArrayOutputStream.serialize(MemoryEfficientByteArrayOutputStream.java:97)at net.sf.ehcache.store.disk.DiskStorageFactory.serializeElement(DiskStorageFactory.java:403)at net.sf.ehcache.store.disk.DiskStorageFactory.write(DiskStorageFactory.java:385)at net.sf.ehcache.store.disk.DiskStorageFactory$DiskWriteTask.call(DiskStorageFactory.java:477)at net.sf.ehcache.store.disk.DiskStorageFactory$PersistentDiskWriteTask.call(DiskStorageFactory.java:1071)at net.sf.ehcache.store.disk.DiskStorageFactory$PersistentDiskWriteTask.call(DiskStorageFactory.java:1055)

根据报错信息,分析发现Ehcache是使用ObjectOutputStream.writeObject0()实现序列化对象的,所以若被序列化的对象没有实现Serializable接口,就会报这个错,而日志显示要序列化的对象是springfox.documentation.service.Operation类实例,我们没法修改Springfox的源码,所以只能将刚才在ehcache.xml中新增的几个缓存的overflowToDiskdiskPersistent属性值都改为false

若ehcache中配置diskPersistent="true"的话意味者被缓存的对象会持久化至硬盘中,此时被保存的对象必须serializable,否则会报错;若diskPersistent="false"的话可以不用serializable

显示swagger-ui页面但不显示接口列表

其实swagger-ui页面的接口列表数据是来自于 /v2/api-docs,所以你访问 /v2/api-docs 看一下是不是也没有接口列表数据,若没有,那就是swagger从所有controller(RequestHandlers)中根据匹配规则(ApiSelector)并没有匹配到任何controller,所以要么是controller没扫描到,要么是匹配规则有问题,可以在 ApiListingReferenceScanner#scan 处打断点,再次启动,分析具体情况

第1步-获取DocumentationContextRequestHandlers(在Spring.xml + @ComponentScan中配置的扫描范围内);

第2步-获取对DocumentationContextRequestHandlers(在Spring.xml + @ComponentScan中配置的扫描范围内)的最终ApiSelector(在SwaggerConfig#createRestApi中设置的);

第3步-匹配,从而得出所有需要生成接口文档的RequestHandler

若SwaggerConfig是由Spring容器管理的,而不是MVC容器,且spring容器扫描不到controller,就会导致第1步的结果并不包含controller,从而导致swagger-ui页面不显示接口列表。

优化重复扫描controller包问题

参考我的另一篇文章

注解说明

参考swagger2 注解说明。再加一点补充说明

@Api

controller上不加这个注解也一样会生成接口文档,只是都是swagger根据类名自动生成的描述,意义不是很大。其实后面的其它注解也是一样,即使不加这些注解,文档还是会生成。

@ApiOperation、@ApiResponses、@ApiParam

@ApiOperation的httpMethod(接口请求方式)/consumes(接口接受的内容类型)/produces(接口响应的内容类型)属性可以不指定,会自动获取SpringMVC的@RequestMapping中的method/consumes/produces属性值。若@RequestMapping和@ApiOperation都没有指定httpMethod,则会为每种httpMethod生成一个接口文档,所以请尽量确保指定了httpMethod。

@ApiModel、@ApiModelProperty

@ApiModel("结果信息")
public class ResultInfo {/** * 消息码。* 默认:code = 0* 成功:code > 0* 失败:code < 1*/@ApiModelProperty( "消息码")public int code = 0;/** 提示信息 */@ApiModelProperty( "提示信息")public String msg = "亲,系统繁忙!";/** 结果集中的对象 */@ApiModelProperty( "结果集中的对象")public Object obj;public ResultInfo() {super();}public ResultInfo(int code, String msg) {this.code = code;this.msg = msg;}@Overridepublic String toString() {return "ResultInfo [code=" + code + ", msg=" + msg + ", object=" + obj + "]";}
}

使用@ApiModel、@ApiModelProperty之前,生成的接口文档:

使用@ApiModel、@ApiModelProperty之后,生成的接口文档:

其它

参考springfox-swagger原理解析与使用过程中遇到的坑

【Spring】使用Springfox-Swagger2相关推荐

  1. Swagger 学习笔记 | Swagger 简介 | Springfox 简介 | Springfox 2.9.2 常用注解 | Spring Boot 整合 Swagger2 案例

    文章目录 一.Swagger 简介 二.Springfox 简介 三.Springfox2.9.2 常用注解 四.SpringBoot 整合 Swagger2 4.1 引入Maven依赖 4.2 项目 ...

  2. 从零搭建一个 Spring Boot 开发环境!Spring Boot+Mybatis+Swagger2 环境搭建

    从零搭建一个 Spring Boot 开发环境!Spring Boot+Mybatis+Swagger2 环境搭建 本文简介 为什么使用Spring Boot 搭建怎样一个环境 开发环境 导入快速启动 ...

  3. Spring Boot: SpringFox Swagger原理解析及简单实用

    文章目录 简介 一.Swagger简单使用 二.Swagger原理 三.Swagger架构分析及组成 其他 简介 API的全称是应用编程接口(Application Programming Inter ...

  4. Spring Boot 2.X - Spring Boot整合Swagger2(starter方式)

    文章目录 Spring Boot 2.X - Spring Boot整合Swagger2(starter方式) 引入依赖 添加@EnableSwagger2Doc注解 创建实体类 创建Controll ...

  5. Spring Boot 集成Swagger2生成RESTful API文档

    Swagger2可以在写代码的同时生成对应的RESTful API文档,方便开发人员参考,另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API. 使用Spring Boot可 ...

  6. SSM三大框架整合Springfox(Swagger2)详细解决方案

    由于项目中使用的是前后端分离,所以,频繁的需要进行数据的交互和接口的调用,所以需要api文档的使用,这样就更加的方便,于是就找到了swagger这个东东,还是很好用.下面介绍一下如何整合到spring ...

  7. Spring Boot——集成Swagger2

    问题描述 在团队开发中,一个好的 API 文档不但可以减少大量的沟通成本,还可以帮助一位新人快速上手业务.传统的做法是由开发人员创建一份 RESTful API 文档来记录所有的接口细节,并在程序员之 ...

  8. Spring Boot整合swagger2(生成有左右菜单式的api文档界面)

    一.pom.xml内容: <!--=====依赖swagger2 zhongzk 2019.7.7 --> <dependency><groupId>io.spri ...

  9. SpringFox Swagger2注解基本用法

    一切参数说明,参考官方API文档:http://docs.swagger.io/swagger-core/current/apidocs/index.html?io/swagger/annotatio ...

  10. 【swagger2】Spring Boot 配置swagger2

    说明:本文写作目的单纯是记录一次项目搭建,以便于以后查看. 开发工具:IDEA:操作系统:MacOS 文章目录 前言 一.引入依赖 二.修改配置文件 三.Docket编写 遇到的问题 前言 Swagg ...

最新文章

  1. android表格自动刷新,Android SwipeRefreshLayout 自动刷新
  2. wxWidgets:在带有 DC 的面板上绘图
  3. esp8266 阿里云 arduino_NUCLEO-G071RB通过WiFi与NB连接阿里云
  4. 嵌入式中常见的存储器总结(二)SRAM VS DRAM
  5. python main调试_在main.py中调试显示这个是什么问题
  6. codable swift_使用Codable进行Swift JSON解析
  7. 查看字符串的编码chardet
  8. 修理牧场 (25 分)(优先队列 简单)
  9. 引用原话,不等于原意
  10. (超赞的Chrome翻译插件)沙拉查词-聚合词典划词翻译
  11. select函数的使用
  12. mysql经纬度与度分秒转换
  13. mysql proxy maxscale_通过Maxscale代理,实现MySQL读写分离
  14. 基于TI Sitara系列AM437x ARM Cortex-A9核心板 处理器
  15. java中注解 详解
  16. 【C++】多态之组合与聚合
  17. 利用计算机对多媒体进行综合处理,多媒体技术复习题及答案
  18. 「完美解决」关于最新Ubuntu22.04.1安装launchpad里面PPA报错:“InRelease not available“,“not found file“等
  19. ggplot2 去掉网格
  20. android listview 导航条,Android侧边导航栏+ListView基础实践

热门文章

  1. Python教程:读取文件有三种方法:(read、readline、readlines)详细用法
  2. Hadoop YARN ResourceManager未授权访问漏洞
  3. Windows环境搭建MQTT服务器
  4. 肯德基宅急送,你值得学习的滚动屏
  5. 简单记录一下金蝶动态表单插件
  6. 命令ls、date、cal、mkdir、touch、rm、cp、mv及文件系统详解
  7. STM32F7--->QSPI
  8. 腾讯云 Ubuntu16.04 搭建Git 服务
  9. reflow与repaint
  10. 内存代管理器TenuredGeneration对垃圾对象的回收