由一次NoHttpResponseException异常,追究到Http长连接和短连接_resttemplate 报错长连接-程序员宅基地

技术标签: java原理  java  http  spring相关  

 一片春愁待酒浇,江上舟摇,楼上帘招。秋娘渡与泰娘桥,风又漂漂,雨又潇潇。何日归家洗客袍?银字笙调,心字香烧。流光容易把人抛,红了樱桃,绿了芭蕉。

——蒋捷《一剪梅.舟过吴江》

一、异常描述

调用smk短信出现NoHttpResponseException异常:

用的是公司配置好的RestTemplate对象。 

原因分析:公司配置好的RestTemplate自带链接池,下次请求时时,连接池里有的连接已经断开了;所以会出现上面的错误。

用李哥的原话:这个问题应该是对方超时把连接关了,池子里的还认为是有效的。(要确认smk那边每个请求的keepalived设置多久的,他们响应的请求头上有带,然后在设置我们这边的会比较准确)

二、解决方法

解决方法有两种,一种是将RestTemplate设置成长连接(RestTemplate配置了连接池管理器,同时也要配置keepAlive);另一种是将RestTemplate设置成短连接。

2.1 RestTemplate设置成长连接

长连接以保证高性能,RestTemplate 本身也是一个 wrapper 其底层默认是 SimpleClientHttpRequestFactory ,如果要保证长连接HttpComponentsClientHttpRequestFactory 是个更好的选择,它不仅可以控制能够建立的连接数还能细粒度的控制到某个 server 的连接数,非常方便。在默认情况下,RestTemplate 到某个 server 的最大连接数只有 2, 一般需要调的更高些,最好等于 server 的 CPU 个数,所以通常下建议使用连接池的方式处理RestTeamplate;

 

 

先来看下原来的配置:

package com.hfi.health.starters.web;

import com.hfi.health.starters.common.util.HfiLogger;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.net.ssl.SSLContext;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
@EnableConfigurationProperties({HttpClientProperties.class})
public class RestTemplateAutoConfiguration
{
  private static final Logger logger = HfiLogger.create(RestTemplateAutoConfiguration.class);
  @Autowired
  private HttpClientProperties httpClientProperties;
  
  @Bean
  public RestTemplate restTemplate()
  {
    RestTemplate template = new RestTemplate(httpRequestFactory());
    template.getInterceptors().add(new LoggingReqRespInterceptor());
    
    return template;
  }
  
  @Bean
  public ClientHttpRequestFactory httpRequestFactory()
  {
    return new HttpComponentsClientHttpRequestFactory(httpClient());
  }
  
  @Bean
  public HttpClient httpClient()
  {
    Registry<ConnectionSocketFactory> registry = RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build();
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
    connectionManager.setMaxTotal(this.httpClientProperties.getMaxTotal());
    connectionManager.setDefaultMaxPerRoute(this.httpClientProperties.getDefaultMaxPerRoute());
    connectionManager.setValidateAfterInactivity(this.httpClientProperties.getValidateAfterInactivity());    

    RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(this.httpClientProperties.getSocketTimeout()).setConnectTimeout(this.httpClientProperties.getConnectTimeout()).setConnectionRequestTimeout(this.httpClientProperties.getConnectionRequestTimeout()).build();
    
    HttpClientBuilder clientBuilder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager);
    try
    {
      SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy()
      {
        public boolean isTrusted(X509Certificate[] arg0, String arg1)
          throws CertificateException
        {
          return true;
        }
      }).build();
      SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1" }, null, NoopHostnameVerifier.INSTANCE);
      
      clientBuilder.setConnectionManager(connectionManager).setSSLSocketFactory(csf);
    }
    catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e)
    {
      logger.error("SSL context configuring failed, HTTPS cannot be used in RestTemplate.", e);
    }
    return clientBuilder.build();
  }
}

 改后的配置:

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

package com.hfi.health.starters.web;

import com.alibaba.fastjson.JSON;
import com.hfi.health.starters.common.util.HfiLogger;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.net.ssl.SSLContext;
import org.apache.http.HeaderElement;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
@EnableConfigurationProperties({HttpClientProperties.class})
public class RestTemplateAutoConfiguration {
    private static final Logger logger = HfiLogger.create(RestTemplateAutoConfiguration.class);
    @Autowired
    private HttpClientProperties httpClientProperties;

    public RestTemplateAutoConfiguration() {
    }

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate template = new RestTemplate(this.httpRequestFactory());
        template.getInterceptors().add(new LoggingReqRespInterceptor());
        return template;
    }

    @Bean
    public ClientHttpRequestFactory httpRequestFactory() {
        return new HttpComponentsClientHttpRequestFactory(this.httpClient());
    }

    @Bean
    public HttpClient httpClient() {
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build();
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        connectionManager.setMaxTotal(this.httpClientProperties.getMaxTotal());
        connectionManager.setDefaultMaxPerRoute(this.httpClientProperties.getDefaultMaxPerRoute());
        connectionManager.setValidateAfterInactivity(this.httpClientProperties.getValidateAfterInactivity());
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(this.httpClientProperties.getSocketTimeout()).setConnectTimeout(this.httpClientProperties.getConnectTimeout()).setConnectionRequestTimeout(this.httpClientProperties.getConnectionRequestTimeout()).build();
        HttpClientBuilder clientBuilder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager);

        try {
            SSLContext sslContext = (new SSLContextBuilder()).loadTrustMaterial((KeyStore)null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                    return true;
                }
            }).build();
            SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, (String[])null, NoopHostnameVerifier.INSTANCE);
            clientBuilder.setConnectionManager(connectionManager).setSSLSocketFactory(csf);
            clientBuilder.setKeepAliveStrategy(this.connectionKeepAliveStrategy());
        } catch (KeyStoreException | KeyManagementException | NoSuchAlgorithmException var7) {
            logger.error("SSL context configuring failed, HTTPS cannot be used in RestTemplate.", var7);
        }

        return clientBuilder.build();
    }

    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return (response, context) -> {
            BasicHeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator("Keep-Alive"));

            while(true) {
                String param;
                String value;
                do {
                    do {
                        if (!it.hasNext()) {
                            HttpHost target = (HttpHost)context.getAttribute("http.target_host");
                            Optional<Entry<String, Integer>> any = ((Map)Optional.ofNullable(this.httpClientProperties.getKeepAliveTargetHost()).orElseGet(HashMap::new)).entrySet().stream().filter((e) -> {
                                return ((String)e.getKey()).equalsIgnoreCase(target.getHostName());
                            }).findAny();
                            return (Long)any.map((en) -> {
                                return (long)(Integer)en.getValue() * 1000L;
                            }).orElse(this.httpClientProperties.getKeepAliveTime() * 1000L);
                        }

                        HeaderElement he = it.nextElement();
                        logger.info("HeaderElement:{}", JSON.toJSONString(he));
                        param = he.getName();
                        value = he.getValue();
                    } while(value == null);
                } while(!"timeout".equalsIgnoreCase(param));

                try {
                    return Long.parseLong(value) * 1000L;
                } catch (NumberFormatException var8) {
                    logger.error("Error occurs while parsing timeout settings of keep-alived connection.", var8);
                }
            }
        };
    }
}

 关键代码在这里:

红框的代码是设置http的keep-alive的策略的方法;
keep-alive的作用:使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。Web服务器,基本上都支持HTTP Keep-Alive。

keep-alive使用场景:我们相当于有少数固定客户端,长时间极高频次的访问服务器,启用keep-alive非常合适。

2.2 RestTemplate设置成短连接

 

二、HTTP协议中的长连接和短连接(keep-alive状态)

2.1 HTTP Keep-Alive

在http早期,每个http请求都要求打开一个tpc socket连接,并且使用一次之后就断开这个tcp连接。

使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。

但是,keep-alive并不是免费的午餐,长时间的tcp连接容易导致系统资源无效占用。配置不当的keep-alive,有时比重复利用连接带来的损失还更大。所以,正确地设置keep-alive timeout时间非常重要。

2.2 keepalvie timeout

Httpd守护进程,一般都提供了keep-alive timeout时间设置参数。比如nginx的keepalive_timeout,和Apache的KeepAliveTimeout。这个keepalive_timout时间值意味着:一个http产生的tcp连接在传送完最后一个响应后,还需要hold住keepalive_timeout秒后,才开始关闭这个连接。

 此处参考文章:HTTP Keep-Alive是什么?如何工作?

2.3 长连接与短连接

  • 长连接:client方与server方先建立连接,连接建立后不断开,然后再进行报文发送和接收。这种方式下由于通讯连接一直存在。此种方式常用于P2P通信。
  • 短连接:Client方与server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。此方式常用于一点对多点通讯。C/S通信。

2.4 长连接与短连接的操作过程

短连接的操作步骤是:
建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接

长连接的操作步骤是:
建立连接——数据传输...(保持连接)...数据传输——关闭连接

 2.5 长连接与短连接的使用时机

短连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。每个TCP连接的建立都需要三次握手,每个TCP连接的断开要四次握手。

如果每次操作都要建立连接然后再操作的话处理速度会降低,所以每次操作后,下次操作时直接发送数据就可以了,不用再建立TCP连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,频繁的socket创建也是对资源的浪费。

Web网站的http服务一般都用短连接,因为长连接对于服务器来说要耗费一定的资源。像web网站这么频繁的成千上万甚至上亿客户端的连接用短连接更省一些资源。试想如果都用长连接,而且同时用成千上万的用户,每个用户都占有一个连接的话,可想而知服务器的压力有多大。所以并发量大,但是每个用户又不需频繁操作的情况下需要短连接。

总之:长连接和短连接的选择要根据需求而定。
长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。

2.6 HTTP协议长连接、短连接总结

长连接与短连接的不同主要在于client和server采取的关闭策略不同。短连接在建立连接以后只进行一次数据传输就关闭连接,而长连接在建立连接以后会进行多次数据数据传输直至关闭连接(长连接中关闭连接通过Connection:closed头部字段)。

2.7 二者关闭策略的不同,就产生了长连接的优点

  • 通过开启、关闭更少的TCP连接,节约CPU时间和内存
  • 通过减少TCP开启引起的包的数目,降低网络阻塞。

二者所应用的具体场景不同。短连接多用于操作频繁、点对点的通讯,且连接数不能太多的情况。数据库的连接则采用长连接。

此小节参考文章:HTTP协议中的长连接与短连接

三、TCP的keepalive和HTTP的keepalive之间的区别

两者是完全不同的概念,只是凑巧名字相同。
tcp的keepalive指的是:周期性的去检查链接是否有效(working),经过一个时钟周期(keepalive_timeout)之后发送一个空的探测报文来检查;
HTTP的keepalive 表示:是否允许在同一次TCP链接中进行多次的HTTP请求,从而减少tcp链接建立和断开造成的开销

参考文章:HTTP协议中的长连接和短连接(keep-alive状态)

四、如何配置HTTP自定义KeepAlive策略

4.1RestTemlate自定义KeepAlive策略示例

/**
 * SpringBoot启动类
 */
@SpringBootApplication
@Slf4j
public class CustomerServiceApplication implements ApplicationRunner {
   @Autowired
   private RestTemplate restTemplate;
 
   public static void main(String[] args) {
      new SpringApplicationBuilder()
            .sources(CustomerServiceApplication.class)
            .bannerMode(Banner.Mode.OFF)
            .web(WebApplicationType.NONE)
            .run(args);
   }
 
   @Bean
   public HttpComponentsClientHttpRequestFactory requestFactory() {
      PoolingHttpClientConnectionManager connectionManager =
            new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
      connectionManager.setMaxTotal(200);
      connectionManager.setDefaultMaxPerRoute(20);
 
      CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .evictIdleConnections(30, TimeUnit.SECONDS)
            .disableAutomaticRetries()
            // 有 Keep-Alive 认里面的值,没有的话永久有效
            //.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
            // 换成自定义的
            .setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy())
            .build();
 
      HttpComponentsClientHttpRequestFactory requestFactory =
            new HttpComponentsClientHttpRequestFactory(httpClient);
 
      return requestFactory;
   }
 
   @Bean
   public RestTemplate restTemplate(RestTemplateBuilder builder) {
//    return new RestTemplate();
 
      return builder
            .setConnectTimeout(Duration.ofMillis(100))
            .setReadTimeout(Duration.ofMillis(500))
            .requestFactory(this::requestFactory)
            .build();
   }
 
   @Override
   public void run(ApplicationArguments args) throws Exception {
      URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:8080/coffee/?name={name}")
            .build("mocha");
      RequestEntity<Void> req = RequestEntity.get(uri)
//          .accept(MediaType.APPLICATION_XML)
            .build();
      ResponseEntity<String> resp = restTemplate.exchange(req, String.class);
      log.info("Response Status: {}, Response Headers: {}", resp.getStatusCode(), resp.getHeaders().toString());
      log.info("Coffee: {}", resp.getBody());
   }
}
 
 
/**
 * KeepAlive策略
 */
public class CustomConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {
    private final long DEFAULT_SECONDS = 30;
 
    @Override
    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        return Arrays.asList(response.getHeaders(HTTP.CONN_KEEP_ALIVE))
                .stream()
                .filter(h -> StringUtils.equalsIgnoreCase(h.getName(), "timeout")
                        && StringUtils.isNumeric(h.getValue()))
                .findFirst()
                .map(h -> NumberUtils.toLong(h.getValue(), DEFAULT_SECONDS))
                .orElse(DEFAULT_SECONDS) * 1000;
    }
}

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/moneywenxue/article/details/109787242

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法