springboot利用AOP完成日志統(tǒng)計(jì)的詳細(xì)步驟
步驟寫的很詳細(xì),可以直接復(fù)制拿來用的,其中用到了過濾器、自定義注解以及AOP切面,來完成日志記錄統(tǒng)計(jì),感興趣的收藏起來,以后遇到了可以直接用。
可能步驟會(huì)比較多,但是整體跟著思路下來,應(yīng)該沒什么大問題的。
項(xiàng)目用到了過濾器,可能有的人會(huì)不理解,之所以用過濾器是因?yàn)橄胍谌罩居涗沺ost請(qǐng)求的json數(shù)據(jù)。
請(qǐng)求的時(shí)候,是通過request的body來傳輸?shù)?。在AOP后置方法中獲取request里面的body,是取不到,直接為空。
原因很簡(jiǎn)單:因?yàn)槭橇鳌O胂肟?,java中的流也是只能讀一次,因?yàn)槲沂窃贏OP后置方法獲取的,控制器實(shí)際上已經(jīng)讀過了一次,后置方法再讀自然為空了。所以用過濾器來進(jìn)行解決了這個(gè)問題。
1、創(chuàng)建日志表
這里我用的是mysql,假如您用的別的數(shù)據(jù)庫,可以自行根據(jù)數(shù)據(jù)庫類型進(jìn)行修改。
CREATE TABLE `log` ( `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主鍵', `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '創(chuàng)建人', `create_time` datetime NULL DEFAULT NULL COMMENT '創(chuàng)建時(shí)間', `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最近更新時(shí)間', `update_time` datetime NULL DEFAULT NULL COMMENT '最近更新人', `update_count` int(11) NULL DEFAULT NULL COMMENT '更新次數(shù)', `delete_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '刪除標(biāo)志', `delete_time` datetime NULL DEFAULT NULL COMMENT '刪除日期', `delete_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '刪除人', `cost_time` int(11) NULL DEFAULT NULL COMMENT '花費(fèi)時(shí)間', `ip` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'ip', `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '日志描述', `request_param` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '請(qǐng)求參數(shù)', `request_json` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '請(qǐng)求json數(shù)據(jù)', `request_type` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '請(qǐng)求類型', `request_url` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '請(qǐng)求路徑', `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '請(qǐng)求用戶', `operation_type` int(3) NULL DEFAULT NULL COMMENT '操作類型', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
2、創(chuàng)建實(shí)體類
我的項(xiàng)目運(yùn)用到了mybatisplus、swagger、lombok,你們可以根據(jù)自己項(xiàng)目框架寫對(duì)應(yīng)的實(shí)體類。BaseModel 是我們封裝了一個(gè)基礎(chǔ)實(shí)體類,專門存放關(guān)于操作人的信息,然后實(shí)體類直接繼承。
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import cn.org.xaas.mybatis.model.BaseModel;
import lombok.Data;
import lombok.ToString;
@TableName(value = "log")
@Data
@ToString(callSuper = true)
public class Log extends BaseModel {
@ApiModelProperty(value = "花費(fèi)時(shí)間")
@TableField(value = "cost_time")
private Integer costTime;
@ApiModelProperty(value = "ip")
@TableField(value = "ip")
private String ip;
@ApiModelProperty(value = "日志描述")
@TableField(value = "description")
private String description;
@ApiModelProperty(value = "請(qǐng)求參數(shù)")
@TableField(value = "request_param")
private String requestParam;
@ApiModelProperty(value = "請(qǐng)求json數(shù)據(jù)")
@TableField(value = "request_json")
private String requestJson;
@ApiModelProperty(value = "請(qǐng)求類型")
@TableField(value = "request_type")
private String requestType;
@ApiModelProperty(value = "請(qǐng)求路徑")
@TableField(value = "request_url")
private String requestUrl;
@ApiModelProperty(value = "請(qǐng)求用戶")
@TableField(value = "username")
private String username;
@ApiModelProperty(value = "操作類型")
@TableField(value = "operation_type")
private Integer operationType;
}
3、創(chuàng)建枚舉類
用來記錄日志操作類型
public enum OperationType {
/**
* 操作類型
*/
UNKNOWN("unknown"),
DELETE("delete"),
SELECT("select"),
UPDATE("update"),
INSERT("insert");
OperationType(String s) {
this.value = s;
}
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
4、創(chuàng)建自定義注解
import java.lang.annotation.*;
@Target({ElementType.PARAMETER, ElementType.METHOD})//作用于參數(shù)或方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {
/**
* 日志名稱
*
* @return
*/
String description() default "";
/**
* 操作類型
*
* @return
*/
OperationType type() default OperationType.UNKNOWN;
}
5、獲取ip的util
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Slf4j
@Component
public class IpInfoUtil {
/**
* 獲取客戶端IP地址
*
* @param request 請(qǐng)求
* @return
*/
public String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
if ("127.0.0.1".equals(ip)) {
//根據(jù)網(wǎng)卡取本機(jī)配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ip = inet.getHostAddress();
}
}
// 對(duì)于通過多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)IP按照','分割
if (ip != null && ip.length() > 15) {
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
if ("0:0:0:0:0:0:0:1".equals(ip)) {
ip = "127.0.0.1";
}
return ip;
}
}
6、線程池util
利用線程異步記錄日志。所以直接用了一個(gè)util維護(hù)線程池。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolUtil {
/**
* 線程緩沖隊(duì)列
*/
private static BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(100);
/**
* 核心線程數(shù),會(huì)一直存活,即使沒有任務(wù),線程池也會(huì)維護(hù)線程的最少數(shù)量
*/
private static final int SIZE_CORE_POOL = 5;
/**
* 線程池維護(hù)線程的最大數(shù)量
*/
private static final int SIZE_MAX_POOL = 10;
/**
* 線程池維護(hù)線程所允許的空閑時(shí)間
*/
private static final long ALIVE_TIME = 2000;
private static ThreadPoolExecutor pool = new ThreadPoolExecutor(SIZE_CORE_POOL, SIZE_MAX_POOL, ALIVE_TIME, TimeUnit.MILLISECONDS, bqueue, new ThreadPoolExecutor.CallerRunsPolicy());
static {
pool.prestartAllCoreThreads();
}
public static ThreadPoolExecutor getPool() {
return pool;
}
public static void main(String[] args) {
System.out.println(pool.getPoolSize());
}
}
7、HttpServletRequest實(shí)現(xiàn)類
這個(gè)就是重寫的一個(gè)HttpServletRequest類。
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class BodyReaderRequestWrapper extends HttpServletRequestWrapper {
private final String body;
/**
* @param request
*/
public BodyReaderRequestWrapper(HttpServletRequest request) {
super(request);
StringBuilder sb = new StringBuilder();
InputStream ins = null;
BufferedReader isr = null;
try {
ins = request.getInputStream();
if (ins != null) {
isr = new BufferedReader(new InputStreamReader(ins));
char[] charBuffer = new char[128];
int readCount = 0;
while ((readCount = isr.read(charBuffer)) != -1) {
sb.append(charBuffer, 0, readCount);
}
} else {
sb.append("");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (isr != null) {
isr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (ins != null) {
ins.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
body = sb.toString();
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletIns = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayIns.read();
}
};
return servletIns;
}
}
8、添加過濾器
這個(gè)過濾器我添加了一個(gè)路徑,就是代表需要json日志的接口,可以在list當(dāng)中添加路徑,不需要取request當(dāng)中json數(shù)據(jù)的可以不配置。
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class BodyReaderRequestFilter implements Filter {
private static final Pattern SHOULD_NOT_FILTER_URL_PATTERN;
static {
List<String> urlList = new ArrayList<>();
// 想要通過aop記錄request當(dāng)中body數(shù)據(jù)的,就需要進(jìn)行配置路徑
urlList.add("(socket/.*)");
urlList.add("(test/test1)");
urlList.add("(test/test2)");
StringBuilder sb = new StringBuilder();
for (String url : urlList) {
sb.append(url);
sb.append("|");
}
sb.setLength(sb.length() - 1);
SHOULD_NOT_FILTER_URL_PATTERN = Pattern.compile(sb.toString());
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 獲取訪問的url
String servletPath = request.getServletPath();
if (SHOULD_NOT_FILTER_URL_PATTERN.matcher(servletPath).find()) {
BodyReaderRequestWrapper requestWrapper = new BodyReaderRequestWrapper(request);
if (requestWrapper == null) {
filterChain.doFilter(request, response);
} else {
filterChain.doFilter(requestWrapper, response);
}
}else {
filterChain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
想要讓過濾器生效需要注入到容器當(dāng)中。
import cn.org.bjca.szyx.xaas.equipment.filter.BodyReaderRequestFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyServerConfig {
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new BodyReaderRequestFilter());
return registrationBean;
}
}
9、添加AOP核心類
對(duì)于切面,我們可以通過指定包名,進(jìn)行日志統(tǒng)計(jì),也可以選擇根據(jù)自定義的注解在方法上添加,然后進(jìn)行統(tǒng)計(jì),根據(jù)自己的實(shí)際情況,在切點(diǎn)進(jìn)行配置即可。
LogDao我是沒有提供的,每個(gè)項(xiàng)目框架不一樣,自行根據(jù)情況進(jìn)行編寫,就是保存數(shù)據(jù)庫就可以了。
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import cn.org.xaas.core.util.HeaderSecurityUtils;
import cn.org.xaas.equipment.annotation.SystemLog;
import cn.org.xaas.equipment.dao.LogDao;
import cn.org.xaas.equipment.model.base.Log;
import cn.org.xaas.equipment.utils.IpInfoUtil;
import cn.org.xaas.equipment.utils.ThreadPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
@Slf4j
public class SystemLogAspect {
private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");
@Autowired
private LogDao logDao;
@Autowired
private IpInfoUtil ipInfoUtil;
@Autowired(required = false)
private HttpServletRequest request;
/**
* Controller層切點(diǎn),注解方式
*/
//@Pointcut("execution(* *..controller..*Controller*.*(..))")
@Pointcut("@annotation(cn.org.xaas.equipment.annotation.SystemLog)")
public void controllerAspect() {
}
/**
* 前置通知 (在方法執(zhí)行之前返回)用于攔截Controller層記錄用戶的操作的開始時(shí)間
*
* @param joinPoint 切點(diǎn)
* @throws InterruptedException
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) throws InterruptedException {
//線程綁定變量(該數(shù)據(jù)只有當(dāng)前請(qǐng)求的線程可見)
Date beginTime = new Date();
beginTimeThreadLocal.set(beginTime);
}
/**
* 后置通知(在方法執(zhí)行之后并返回?cái)?shù)據(jù)) 用于攔截Controller層無異常的操作
*
* @param joinPoint 切點(diǎn)
*/
@AfterReturning("controllerAspect()")
public void after(JoinPoint joinPoint) {
try {
// 獲取操作人,每個(gè)系統(tǒng)不一樣,一般存儲(chǔ)與session,此處就不展示了
String username = HeaderSecurityUtils.getUserName();
// 讀取json數(shù)據(jù)
String openApiRequestData = getJSON(request);
Map<String, String[]> requestParams = request.getParameterMap();
Log log = new Log();
if (openApiRequestData != null) {
log.setRequestJson(JSONUtil.toJsonStr(openApiRequestData));
}
log.setId(IdUtil.simpleUUID());
log.setUsername(username);
//日志標(biāo)題
String description = getControllerMethodInfo(joinPoint).get("description").toString();
log.setDescription(description);
//日志類型
log.setOperationType((int) getControllerMethodInfo(joinPoint).get("type"));
//日志請(qǐng)求url
log.setRequestUrl(request.getRequestURI());
//請(qǐng)求方式
log.setRequestType(request.getMethod());
//請(qǐng)求參數(shù)
log.setRequestParam(JSONUtil.toJsonStr(requestParams));
//其他屬性
log.setIp(ipInfoUtil.getIpAddr(request));
log.setCreateBy(username);
log.setUpdateBy(username);
log.setCreateTime(new Date());
log.setUpdateTime(new Date());
log.setDeleteFlag("0");
//請(qǐng)求開始時(shí)間
long beginTime = beginTimeThreadLocal.get().getTime();
long endTime = System.currentTimeMillis();
//請(qǐng)求耗時(shí)
Long logElapsedTime = endTime - beginTime;
log.setCostTime(logElapsedTime.intValue());
//持久化(存儲(chǔ)到數(shù)據(jù)或者ES,可以考慮用線程池)
ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(log, logDao));
} catch (Exception e) {
log.error("AOP后置通知異常", e);
}
}
/**
* 獲取request的body
*
* @param request
* @return
*/
public String getJSON(HttpServletRequest request) {
ServletInputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader streamReader = null;
StringBuilder responseStrBuilder = new StringBuilder();
try {
inputStream = request.getInputStream();
inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
streamReader = new BufferedReader(inputStreamReader);
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
responseStrBuilder.append(inputStr);
}
} catch (IOException ioException) {
ioException.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (inputStreamReader != null) {
inputStreamReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (streamReader != null) {
streamReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return responseStrBuilder.toString();
}
/**
* 保存日志至數(shù)據(jù)庫
*/
private static class SaveSystemLogThread implements Runnable {
private Log log;
private LogDao logDao;
public SaveSystemLogThread(Log esLog, LogDao logDao) {
this.log = esLog;
this.logDao = logDao;
}
@Override
public void run() {
logDao.insert(log);
}
}
/**
* 獲取注解中對(duì)方法的描述信息 用于Controller層注解
*
* @param joinPoint 切點(diǎn)
* @return 方法描述
* @throws Exception
*/
public static Map<String, Object> getControllerMethodInfo(JoinPoint joinPoint) throws Exception {
Map<String, Object> map = new HashMap<String, Object>(16);
//獲取目標(biāo)類名
String targetName = joinPoint.getTarget().getClass().getName();
//獲取方法名
String methodName = joinPoint.getSignature().getName();
//獲取相關(guān)參數(shù)
Object[] arguments = joinPoint.getArgs();
//生成類對(duì)象
Class targetClass = Class.forName(targetName);
//獲取該類中的方法
Method[] methods = targetClass.getMethods();
String description = "";
Integer type = null;
for (Method method : methods) {
if (!method.getName().equals(methodName)) {
continue;
}
Class[] clazzs = method.getParameterTypes();
if (clazzs.length != arguments.length) {
//比較方法中參數(shù)個(gè)數(shù)與從切點(diǎn)中獲取的參數(shù)個(gè)數(shù)是否相同,原因是方法可以重載哦
continue;
}
description = method.getAnnotation(SystemLog.class).description();
type = method.getAnnotation(SystemLog.class).type().ordinal();
map.put("description", description);
map.put("type", type);
}
return map;
}
}
10、接口測(cè)試
import cn.org.xaas.equipment.annotation.SystemLog;
import cn.org.xaas.equipment.constant.OperationType;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping("/test1")
@SystemLog(description = "根據(jù)id查詢某某數(shù)據(jù)",type = OperationType.SELECT)
public void test1(@RequestParam("id")String id){
System.out.println(id);
}
@PostMapping("/test2")
@SystemLog(description = "根據(jù)id查詢某某數(shù)據(jù),傳json",type = OperationType.SELECT)
public void test2(@RequestBody String id){
System.out.println(id);
}
}
調(diào)用第一個(gè)測(cè)試接口:


調(diào)用第二個(gè)測(cè)試接口:


到此這篇關(guān)于springboot利用AOP完成日志統(tǒng)計(jì)的文章就介紹到這了,更多相關(guān)springboot日志統(tǒng)計(jì)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot中利用AOP和攔截器實(shí)現(xiàn)自定義注解
- SpringBoot使用AOP實(shí)現(xiàn)統(tǒng)計(jì)全局接口訪問次數(shù)詳解
- SpringBoot通過AOP與注解實(shí)現(xiàn)入?yún)⑿r?yàn)詳情
- SpringBoot使用AOP統(tǒng)一日志管理的方法詳解
- Springboot+AOP實(shí)現(xiàn)時(shí)間參數(shù)格式轉(zhuǎn)換
- springboot使用AOP+反射實(shí)現(xiàn)Excel數(shù)據(jù)的讀取
- springboot利用aop實(shí)現(xiàn)接口異步(進(jìn)度條)的全過程
- SpringBoot中通過AOP整合日志文件的實(shí)現(xiàn)
- Spring?BOOT?AOP基礎(chǔ)應(yīng)用教程
相關(guān)文章
Spring中Websocket身份驗(yàn)證和授權(quán)的實(shí)現(xiàn)
在Web應(yīng)用開發(fā)中,安全一直是非常重要的一個(gè)方面,本文主要介紹了Spring中Websocket身份驗(yàn)證和授權(quán)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08
JavaWeb頁面中防止點(diǎn)擊Backspace網(wǎng)頁后退情況
當(dāng)鍵盤敲下后退鍵(Backspace)后怎么防止網(wǎng)頁后退情況呢?今天小編通過本文給大家詳細(xì)介紹下,感興趣的朋友一起看看吧2016-11-11
簡(jiǎn)單理解java泛型的本質(zhì)(非類型擦除)
泛型在java中有很重要的地位,在面向?qū)ο缶幊碳案鞣N設(shè)計(jì)模式中有非常廣泛的應(yīng)用。泛型是參數(shù)化類型的應(yīng)用,操作的數(shù)據(jù)類型不限定于特定類型,可以根據(jù)實(shí)際需要設(shè)置不同的數(shù)據(jù)類型,以實(shí)現(xiàn)代碼復(fù)用。下面小編來簡(jiǎn)單講一講泛型2019-05-05
Netty網(wǎng)絡(luò)編程實(shí)戰(zhàn)之搭建Netty服務(wù)器
Netty是JBOSS開源的一款NIO網(wǎng)絡(luò)編程框架,可用于快速開發(fā)網(wǎng)絡(luò)的應(yīng)用。Netty是一個(gè)異步的、基于事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用框架,用于快速開發(fā)高性能的服務(wù)端和客戶端。本文將詳細(xì)說說如何搭建Netty服務(wù)器,需要的可以參考一下2022-10-10
解讀httpclient的validateAfterInactivity連接池狀態(tài)檢測(cè)
這篇文章主要為大家介紹了httpclient的validateAfterInactivity連接池狀態(tài)檢測(cè)解讀*,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
java實(shí)現(xiàn)服務(wù)器文件打包zip并下載的示例(邊打包邊下載)
這篇文章主要介紹了java實(shí)現(xiàn)服務(wù)器文件打包zip并下載的示例,使用該方法,可以即時(shí)打包文件,一邊打包一邊傳輸,不使用任何的緩存,讓用戶零等待,需要的朋友可以參考下2014-04-04

