基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄的方法
一 引言
最近項(xiàng)目在線上運(yùn)行出現(xiàn)了一些難以復(fù)現(xiàn)的bug需要定位相應(yīng)api的日志,通過nginx提供的api請(qǐng)求日志難以實(shí)現(xiàn),于是在gateway通過全局過濾器記錄api請(qǐng)求日志。
二 實(shí)現(xiàn)邏輯
- 接受到用戶請(qǐng)求后,經(jīng)過全局過濾器,檢驗(yàn)是否開啟相應(yīng)的日志配置及相應(yīng)的黑白名單配置
- 在gateway前置處理如記錄當(dāng)前請(qǐng)求開始時(shí)間,請(qǐng)求url,請(qǐng)求方法等
- 在gateway后置處理中獲得相應(yīng)的請(qǐng)求結(jié)果,響應(yīng)狀態(tài)碼
- 記錄當(dāng)前請(qǐng)求日志,根據(jù)實(shí)際需求,通過mq異步處理工具持久化相應(yīng)的日志(本案列作處理)
- 診斷請(qǐng)求結(jié)果,對(duì)請(qǐng)求異常,慢api等推送相應(yīng)的消息給研發(fā)人人員
三 代碼實(shí)現(xiàn)
定義相應(yīng)的配置類
@Data @RefreshScope @Component @ConfigurationProperties(prefix = LogProperties.PREFIX) public class LogProperties { public static final String PREFIX = "config.gateway.log.access"; /** * 是否開啟日志打印 */ private Boolean enabled = true; /** * 忽略的pattern */ private List<String> ignoredPatterns; private ApiAlarmConfiguration fail = new ApiAlarmConfiguration(); private SlowApiAlarmConfiguration slow = new SlowApiAlarmConfiguration(); /** * 慢API報(bào)警配置 */ @Data public static class SlowApiAlarmConfiguration { /** * 是否開啟API慢日志打印 */ private boolean alarm = true; /** * 報(bào)警閾值 (單位:毫秒) */ private long threshold = 500; } /** * API異常報(bào)警(根據(jù)http狀態(tài)碼判定) */ @Data public static class ApiAlarmConfiguration { /** * 是否開啟異常報(bào)警 默認(rèn)關(guān)閉 */ private boolean alarm = false; /** * 排除狀態(tài)碼 */ private List<Integer> exclusion; } }
定義log實(shí)體
@Data public class GatewayLog implements Serializable { private static final long serialVersionUID = -3205904134722576668L; /** * 訪問實(shí)例 */ private String targetServer; /** * 請(qǐng)求路徑 */ private String requestPath; /** * 請(qǐng)求與方法 */ private String method; /** * 請(qǐng)求協(xié)議 */ private String schema; /** * 請(qǐng)求ip */ private String ip; /** * 請(qǐng)求時(shí)間 */ private Date requestTime; /** * 請(qǐng)求參數(shù) */ private Map<String,String> queryParams; /** * 請(qǐng)求體 */ private String requestBody; /** * 請(qǐng)求執(zhí)行時(shí)間 */ private Long executeTime; /** * 請(qǐng)求類型 */ private String requestContentType; /** * 相應(yīng)狀態(tài)碼 */ private int code; }
定義相應(yīng)日志工廠及常量
public interface GatewayLogType { /** * 常規(guī)輸出 */ String APPLICATION_JSON_REQUEST = "applicationJsonRequest"; String FORM_DATA_REQUEST = "formDataRequest"; String BASIC_REQUEST = "basicRequest"; String NORMAL_REQUEST = "normalRequest"; /** * 慢查詢 */ String SLOW = "slow"; /** * 非200響應(yīng) */ String FAIL = "fail"; } @Slf4j public class GatewayLogInfoFactory { public static void log(String type, GatewayLog gatewayLog){ switch (type){ case GatewayLogType.APPLICATION_JSON_REQUEST: log.info("[{}] {} {},route: {},status: {},excute: {} mills,requestBody: {}" ,gatewayLog.getIp() ,gatewayLog.getMethod() ,gatewayLog.getRequestPath() ,gatewayLog.getTargetServer() ,gatewayLog.getCode() ,gatewayLog.getExecuteTime() ,StrUtil.replace(gatewayLog.getRequestBody(), StrPool.LF,"") ); break; case GatewayLogType.FORM_DATA_REQUEST: log.info("[{}] {} {},route: {},status: {},excute: {} mills,requestBody: {}" ,gatewayLog.getIp() ,gatewayLog.getMethod() ,gatewayLog.getRequestPath() ,gatewayLog.getTargetServer() ,gatewayLog.getCode() ,gatewayLog.getExecuteTime() ,StrUtil.replace(gatewayLog.getRequestBody(), StrPool.LF,"") ); break; case GatewayLogType.BASIC_REQUEST: log.info("[{}] {} {},route: {},status: {},excute: {} mills,requestBody: {}" ,gatewayLog.getIp() ,gatewayLog.getMethod() ,gatewayLog.getRequestPath() ,gatewayLog.getTargetServer() ,gatewayLog.getCode() ,gatewayLog.getExecuteTime() ,StrUtil.replace(gatewayLog.getRequestBody(), StrPool.LF,"") ); break; case GatewayLogType.NORMAL_REQUEST: log.info("[{}] {} {},route: {},status: {},excute: {} mills,queryParams: {}" ,gatewayLog.getIp() ,gatewayLog.getMethod() ,gatewayLog.getRequestPath() ,gatewayLog.getTargetServer() ,gatewayLog.getCode() ,gatewayLog.getExecuteTime() ,gatewayLog.getQueryParams() ); break; default: break; } } }
定義日志全局過濾器
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; import com.zkjc.xlcp.common.notifier.core.Notifier; import com.zkjc.xlcp.gateway.log.GatewayLogInfoFactory; import com.zkjc.xlcp.gateway.log.LogProperties; import com.zkjc.xlcp.gateway.log.constant.GatewayLogType; import com.zkjc.xlcp.gateway.log.entity.GatewayLog; import com.zkjc.xlcp.gateway.util.IpUtil; import io.netty.util.internal.StringUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.reactivestreams.Publisher; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.support.BodyInserterContext; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.multipart.FormFieldPart; import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.HandlerStrategies; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.net.URI; import java.util.*; /** * @author likun * @date 2022年10月24日 9:52 */ @Component @RequiredArgsConstructor @Slf4j public class AccessLogFilter implements GlobalFilter, Ordered { private final LogProperties logProperties; private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders(); /** * default HttpMessageReader. */ private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders(); private final AntPathMatcher antPathMatcher = new AntPathMatcher(); private final Notifier notifier; /* * 在CncloudRequestGlobalFilter后面執(zhí)行 先清洗url在進(jìn)行路徑的日志的打印 * */ @Override public int getOrder() { return -100; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 判斷是否打開相應(yīng)是日志配置 ingore配置校驗(yàn) if (!logProperties.getEnabled()||hasIgnoredFlag(exchange,logProperties)){ return chain.filter(exchange); } // 獲得請(qǐng)求上下文 GatewayLog gatewayLog = parseGateway(exchange); ServerHttpRequest request = exchange.getRequest(); MediaType mediaType = request.getHeaders().getContentType(); if (Objects.isNull(mediaType)){ return writeNormalLog(exchange,chain,gatewayLog); } gatewayLog.setRequestContentType(mediaType.getType() + "/" + mediaType.getSubtype()); // 對(duì)不同的請(qǐng)求類型做相應(yīng)的處理 if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){ return writeBodyLog(exchange,chain,gatewayLog); }else if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType) || MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){ return readFormData(exchange,chain,gatewayLog); }else { return writeBasicLog(exchange,chain,gatewayLog); } } /** * 校驗(yàn)白名單 * @param exchange * @param logProperties * @return */ private Boolean hasIgnoredFlag(ServerWebExchange exchange,LogProperties logProperties){ List<String> ignoredPatterns = logProperties.getIgnoredPatterns(); if (CollectionUtil.isEmpty(ignoredPatterns)){ return Boolean.FALSE; } ServerHttpRequest request = exchange.getRequest(); URI uri = request.getURI(); for (String pattern : ignoredPatterns) { if (antPathMatcher.match(pattern,uri.getPath())){ return Boolean.TRUE; } } return Boolean.FALSE; } /** * 生成相應(yīng)的報(bào)告并推送qq郵箱消息 */ private void report(GatewayLog gatewayLog){ if (notifier==null){ return; } boolean reported = exceptionReport(gatewayLog); if (!reported){ slowApiReport(gatewayLog); } } /** * 異常報(bào)警 * @param gatewayLog * @return */ private Boolean exceptionReport(GatewayLog gatewayLog){ int code = gatewayLog.getCode(); if (code==HttpStatus.OK.value()){ return Boolean.FALSE; } LogProperties.ApiAlarmConfiguration apiAlarmConfiguration = logProperties.getFail(); if (!apiAlarmConfiguration.isAlarm()){ log.debug("api exception alarm disabled."); return Boolean.FALSE; } if (!CollectionUtils.isEmpty(apiAlarmConfiguration.getExclusion()) && apiAlarmConfiguration.getExclusion().contains(code)) { log.debug("status [{}] excluded.", code); return Boolean.FALSE; } String alarmContent = String.format("【API異?!?請(qǐng)求ip:[{%s}],請(qǐng)求路由:[{%s}],請(qǐng)求地址:[{%s}],返回狀態(tài)碼:[{%d}],執(zhí)行時(shí)間:%d ms",gatewayLog.getIp(),gatewayLog.getTargetServer(),gatewayLog.getRequestPath(),code,gatewayLog.getExecuteTime()); notifier.notify(alarmContent); return Boolean.TRUE; } private Boolean slowApiReport(GatewayLog gatewayLog){ LogProperties.SlowApiAlarmConfiguration slowApiAlarmConfiguration = logProperties.getSlow(); long threshold = slowApiAlarmConfiguration.getThreshold(); if (gatewayLog.getExecuteTime()<threshold){ return Boolean.FALSE; } if (!slowApiAlarmConfiguration.isAlarm()) { log.debug("slow api alarm disabled."); return Boolean.FALSE; } String slowContent = String.format("【API執(zhí)行時(shí)間過長,超過設(shè)定閾值】 請(qǐng)求ip:[{%s}],請(qǐng)求路由:[{%s}],請(qǐng)求地址:[{%s}],執(zhí)行時(shí)間:%d ms",gatewayLog.getIp(),gatewayLog.getTargetServer(),gatewayLog.getRequestPath(),gatewayLog.getExecuteTime()); notifier.notify(slowContent); return Boolean.TRUE; } /** * 獲得當(dāng)前請(qǐng)求分發(fā)的路由 * @param exchange * @return */ private Route getGatewayRoute(ServerWebExchange exchange) { return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); } private GatewayLog parseGateway(ServerWebExchange exchange){ ServerHttpRequest request = exchange.getRequest(); String requestPath = request.getPath().pathWithinApplication().value(); Route route = getGatewayRoute(exchange); String ip = IpUtil.getIpAddress(request); GatewayLog gatewayLog = new GatewayLog(); gatewayLog.setSchema(request.getURI().getScheme()); gatewayLog.setMethod(request.getMethodValue()); gatewayLog.setRequestPath(requestPath); gatewayLog.setTargetServer(route.getId()); gatewayLog.setIp(ip); gatewayLog.setRequestTime(new Date()); return gatewayLog; } private Mono writeNormalLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog){ return chain.filter(exchange).then(Mono.fromRunnable(()->{ ServerHttpResponse response = exchange.getResponse(); int value = response.getStatusCode().value(); gatewayLog.setCode(value); long executeTime = DateUtil.between(gatewayLog.getRequestTime(), new Date(), DateUnit.MS); gatewayLog.setExecuteTime(executeTime); ServerHttpRequest request = exchange.getRequest(); MultiValueMap<String, String> queryParams = request.getQueryParams(); Map<String, String> paramsMap = new HashMap<>(); if (CollectionUtil.isNotEmpty(queryParams)) { for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) { paramsMap.put(entry.getKey(), StrUtil.join(StrPool.COMMA,entry.getValue())); } } gatewayLog.setQueryParams(paramsMap); GatewayLogInfoFactory.log(GatewayLogType.NORMAL_REQUEST,gatewayLog); // 推送相應(yīng)的報(bào)告 report(gatewayLog); })); } /** * 解決 request body 只能讀取一次問題, * 參考: org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory * @param exchange * @param chain * @param gatewayLog * @return */ @SuppressWarnings("unchecked") private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) { ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders); Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> { gatewayLog.setRequestBody(body); return Mono.just(body); }); // 通過 BodyInserter 插入 body(支持修改body), 避免 request body 只能獲取一次 BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); HttpHeaders headers = new HttpHeaders(); headers.putAll(exchange.getRequest().getHeaders()); // the new content type will be computed by bodyInserter // and then set in the request decorator headers.remove(HttpHeaders.CONTENT_LENGTH); CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers); return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> { // 重新封裝請(qǐng)求 ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage); // 記錄響應(yīng)日志 ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog); // 記錄普通的 return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()).then(Mono.fromRunnable(() -> { // 打印日志 GatewayLogInfoFactory.log(GatewayLogType.APPLICATION_JSON_REQUEST,gatewayLog); // 推送相應(yīng)的報(bào)告 report(gatewayLog); })); })); } /** * 讀取form-data數(shù)據(jù) * @param exchange * @param chain * @param accessLog * @return */ private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) { return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> { DataBufferUtils.retain(dataBuffer); final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()))); final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { return cachedFlux; } @Override public MultiValueMap<String, String> getQueryParams() { return UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).build().getQueryParams(); } }; final HttpHeaders headers = exchange.getRequest().getHeaders(); if (headers.getContentLength() == 0) { return chain.filter(exchange); } ResolvableType resolvableType; if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(headers.getContentType())) { resolvableType = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class); } else { //解析 application/x-www-form-urlencoded resolvableType = ResolvableType.forClass(String.class); } return MESSAGE_READERS.stream().filter(reader -> reader.canRead(resolvableType, mutatedRequest.getHeaders().getContentType())).findFirst().orElseThrow(() -> new IllegalStateException("no suitable HttpMessageReader.")).readMono(resolvableType, mutatedRequest, Collections.emptyMap()).flatMap(resolvedBody -> { if (resolvedBody instanceof MultiValueMap) { LinkedMultiValueMap map = (LinkedMultiValueMap) resolvedBody; if (CollectionUtil.isNotEmpty(map)) { StringBuilder builder = new StringBuilder(); final Part bodyPartInfo = (Part) ((MultiValueMap) resolvedBody).getFirst("body"); if (bodyPartInfo instanceof FormFieldPart) { String body = ((FormFieldPart) bodyPartInfo).value(); builder.append("body=").append(body); } accessLog.setRequestBody(builder.toString()); } } else { accessLog.setRequestBody((String) resolvedBody); } //獲取響應(yīng)體 ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog); return chain.filter(exchange.mutate().request(mutatedRequest).response(decoratedResponse).build()).then(Mono.fromRunnable(() -> { // 打印日志 // 打印響應(yīng)的日志 GatewayLogInfoFactory.log(GatewayLogType.FORM_DATA_REQUEST,accessLog); // 推送相應(yīng)的報(bào)告 report(accessLog); })); }); }); } private Mono<Void> writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) { return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> { DataBufferUtils.retain(dataBuffer); final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()))); final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { return cachedFlux; } @Override public MultiValueMap<String, String> getQueryParams() { return UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).build().getQueryParams(); } }; StringBuilder builder = new StringBuilder(); MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams(); if (CollectionUtil.isNotEmpty(queryParams)) { for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) { builder.append(entry.getKey()).append("=").append(entry.getValue()).append(StrPool.COMMA); } } accessLog.setRequestBody(builder.toString()); //獲取響應(yīng)體 ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog); return chain.filter(exchange.mutate().request(mutatedRequest).response(decoratedResponse).build()).then(Mono.fromRunnable(() -> { // 打印日志 GatewayLogInfoFactory.log(GatewayLogType.BASIC_REQUEST,accessLog); // 推送相應(yīng)的報(bào)告 report(accessLog); })); }); } /** * 請(qǐng)求裝飾器,重新計(jì)算 headers * @param exchange * @param headers * @param outputMessage * @return */ private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) { return new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public HttpHeaders getHeaders() { long contentLength = headers.getContentLength(); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.putAll(super.getHeaders()); if (contentLength > 0) { httpHeaders.setContentLength(contentLength); } else { httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); } return httpHeaders; } @Override public Flux<DataBuffer> getBody() { return outputMessage.getBody(); } }; } /** * 記錄響應(yīng)日志 * 通過 DataBufferFactory 解決響應(yīng)體分段傳輸問題。 */ private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) { ServerHttpResponse response = exchange.getResponse(); DataBufferFactory bufferFactory = response.bufferFactory(); return new ServerHttpResponseDecorator(response) { @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { if (body instanceof Flux) { // 計(jì)算執(zhí)行時(shí)間 long executeTime = DateUtil.between(gatewayLog.getRequestTime(), new Date(), DateUnit.MS); gatewayLog.setExecuteTime(executeTime); // 獲取響應(yīng)類型,如果是 json 就打印 String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);// gatewayLog.setCode(this.getStatusCode().value()); // if (Objects.equals(this.getStatusCode(), HttpStatus.OK) && !StringUtil.isNullOrEmpty(originalResponseContentType) && originalResponseContentType.contains("application/json")) { Flux<? extends DataBuffer> fluxBody = Flux.from(body); return super.writeWith(fluxBody.buffer().map(dataBuffers -> { // 合并多個(gè)流集合,解決返回體分段傳輸 DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); DataBuffer join = dataBufferFactory.join(dataBuffers); byte[] content = new byte[join.readableByteCount()]; // 釋放掉內(nèi)存 join.read(content); DataBufferUtils.release(join); return bufferFactory.wrap(content); })); }else { } } return super.writeWith(body); } }; } }
到此這篇關(guān)于基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄的文章就介紹到這了,更多相關(guān)Spring-cloud-gateway全局日志記錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringCloud-Gateway轉(zhuǎn)發(fā)WebSocket失敗問題及解決
- spring?cloud?gateway中配置uri三種方式
- spring?cloud?gateway中netty線程池小優(yōu)化
- Spring?Cloud?Gateway中netty線程池優(yōu)化示例詳解
- SpringCloudGateway使用Skywalking時(shí)日志打印traceId解析
- SpringCloud?Gateway之請(qǐng)求應(yīng)答日志打印方式
- Spring Cloud gateway 網(wǎng)關(guān)如何攔截Post請(qǐng)求日志
- Spring Cloud Gateway 記錄請(qǐng)求應(yīng)答數(shù)據(jù)日志操作
相關(guān)文章
JAVA實(shí)現(xiàn)往字符串中某位置加入一個(gè)字符串
這篇文章主要介紹了JAVA實(shí)現(xiàn)往字符串中某位置加入一個(gè)字符串,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08IntelliJ IDEA 設(shè)置代碼提示或自動(dòng)補(bǔ)全的快捷鍵功能
這篇文章主要介紹了IntelliJ IDEA 設(shè)置代碼提示或自動(dòng)補(bǔ)全的快捷鍵功能,需要的朋友可以參考下2018-03-03JavaWeb簡(jiǎn)單文件上傳流程的實(shí)戰(zhàn)記錄
在Web應(yīng)用系統(tǒng)開發(fā)中,文件上傳和下載功能是非常常用的功能,下面這篇文章主要給大家介紹了關(guān)于JavaWeb實(shí)現(xiàn)簡(jiǎn)單文件上傳流程的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03查找native方法的本地實(shí)現(xiàn)函數(shù)native_function詳解
JDK開放給用戶的源碼中隨處可見Native方法,被Native關(guān)鍵字聲明的方法說明該方法不是以Java語言實(shí)現(xiàn)的,而是以本地語言實(shí)現(xiàn)的,Java可以直接拿來用。這里介紹下查找native方法的本地實(shí)現(xiàn)函數(shù)native_function,感興趣的朋友跟隨小編一起看看吧2021-12-12java環(huán)境中的JDK、JVM、JRE詳細(xì)介紹
這篇文章主要介紹了java環(huán)境中的JDK、JVM、JRE詳細(xì)介紹的相關(guān)資料,對(duì)于初學(xué)者還是有必要了解下,細(xì)致說明他們是什么,需要的朋友可以參考下2016-11-11基于Java實(shí)現(xiàn)簡(jiǎn)易的七星彩號(hào)碼生成器
七星彩是中國體育彩票的一種玩法,由中國國家體育總局體育彩票管理中心統(tǒng)一發(fā)行。本文為大家準(zhǔn)備了一個(gè)七星彩號(hào)碼生成器Java工具類,感興趣的可以了解一下2022-08-08基于java中的null類型---有關(guān)null的9件事
這篇文章主要介紹了java中的null類型---有關(guān)null的9件事,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08JavaSE實(shí)戰(zhàn)之酒店訂房系統(tǒng)的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了如何利用JavaSE實(shí)現(xiàn)酒店訂房系統(tǒng),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)JavaSE開發(fā)有一定的幫助,需要的可以參考一下2022-07-07