一、前言
看完这篇文教你怎样biu的佛的打印日志
二、线程池配置
执行异步人任务时,需要将执行的任务放入到线程池中,所以需配置好我们的线程池,比如
核心线程大小
、
最大可创建的线程数
和
队列长度
等。
2.1 基本参数配置
@Configuration
public class ThreadPoolConfig {
/**
* 核心线程池大小
*/
private int corePoolSize = 50;
/**
* 最大可创建的线程数
*/
private int maxPoolSize = 200;
/**
* 队列最大长度
*/
private int queueCapacity = 1000;
/**
* 线程池维护线程所允许的空闲时间
*/
private int keepAliveSeconds = 300;
......
......
}
配置好基本的参数后,我们需要用这些参数初始化 ThreadPoolTaskExecutor 线程池任务执行器:
2.2 初始化任务执行器
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
// 创建线程池任务执行器对象
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程池大小
executor.setCorePoolSize(corePoolSize);
// 设置最大可创建的线程数
executor.setMaxPoolSize(maxPoolSize);
// 设置队列最大长度
executor.setQueueCapacity(queueCapacity);
// 设置线程池维护线程所允许的空闲时间
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用时)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
注意:@Bean 注解不能少,直接将其交 Spring 容器管理,后面可以通过 getBean() 方法拿到方法返回的实例。
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
return new ScheduledThreadPoolExecutor
(
corePoolSize,
new BasicThreadFactory.Builder()
.namingPattern("schedule-pool-%d").daemon(true)
.build()
) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
ThreadUtil.printException(r, t);
}
};
}
/**
* 打印线程异常信息
*/
public static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
future.get();
}
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
logger.error(t.getMessage(), t);
}
}
这个 Bean 实例是用来创建一个调度线程池执行器,并需重写 afterExecute() 方法,去处理执行任务过程中产生的异常或执行完成后的下一步操作流程,主要是用于
执行周期性或定时任务
,后面会用到它去异步执行记录登录日志任务。
三、异步任务管理器
顾名思义,就是用来对异步任务进行统一的管理,并提供了一种访问其唯一对象的方式,这样做得好处就是,在内存中有且仅有一个实例,减少了内存的开销,尤其对于频繁的创建和销毁实例,用这种方式来频繁执行多个异步任务性能是相对比较好的。
3.1 设计模式
想必,聪明的各位已经想到了 —— 设计模式之单例模式(
Singleton Pattern
),这种模式也被称之为 Java 中最简单的设计模式。
public class AsyncTaskManger {
private AsyncTaskManger() {
}
private static AsyncTaskManger me = new AsyncTaskManger();
public static AsyncTaskManger me() {
return me;
}
......
......
}
这里采用最常用的线程安全的饿汉式,没有加锁执行效率会比较高,就是在类加载就会初始化,比较浪费内存了  ̄□ ̄||
3.2 暴露实例方法
/**
* 操作延迟 10 毫秒
*/
private static final int OPERATE_DELAY_TIME = 10;
/**
* 异步操作任务调度线程池
*/
private ScheduledExecutorService executor = SpringUtil.getBean("scheduledExecutorService");
/**
* 执行任务
*
* @param task 任务
*/
public void execute(TimerTask task) {
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
通过前面种下的豆,都到豆,这也就是人们常说的种瓜得瓜、种豆得豆,这里的豆指的是调度线程池执行器通过 scheduledExecutorService 获取,拿来执行周期性或定时任务。
四、异步任务工厂
设计这个类主要是用来产生 TimerTask 的,比如用户访问登录页面,登录判断的同时,还需记录下用户登录的日志情况,是登录成功了呢?还是失败了!退出登录也可以记录到日志表中,并且完全不用担心后端处理不过来的问题,因为它是放在异步操作任务调度线程池当中。
4.1 具体代码实现
public class AsyncTaskFactory {
private static final Logger loginUserLogger = LoggerFactory.getLogger("user-login");
......
......
}
获取日志打印对象,用于控制台的日志的打印输出,便于下一步的开发调试。
/**
* 记录用户登录日志
*
* @param username 用户名
* @param status 状态
* @param message 提示消息
* @param args 参数列表
* @return 定时器任务
*/
public static TimerTask recordUserLoginLog(final String username, final String status, final String message,
final Object... args) {
// 获取请求对象
HttpServletRequest request = ServletUtil.getRequest();
// 请求表头
final String header = ServletUtil.getHeader(request, "User-Agent", CharsetUtil.UTF_8);
final UserAgent userAgent = UserAgentParser.parse(header);
// 获取客户端 IP 地址
String ip = ServletUtil.getClientIP(request);
return new TimerTask() {
@Override
public void run() {
// 通过 ip 地址获取现实地址
String address = AddressUtil.getRealAddressByIP(ip);
StringBuilder builder = new StringBuilder();
final boolean ignoreNull = true;
builder.append(StrUtil.format("[ {} ]", ip, ignoreNull));
builder.append(StrUtil.format("[ {} ]", address, ignoreNull));
builder.append(StrUtil.format("[ {} ]", username, ignoreNull));
builder.append(StrUtil.format("[ {} ]", status, ignoreNull));
builder.append(StrUtil.format("[ {} ]", message, ignoreNull));
// 打印信息到日志
loginUserLogger.info(builder.toString(), args);
// 获取客户端操作系统
String os = UserAgentUtil.getOperatingSystem();
// 获取客户端浏览器
String browser = UserAgentUtil.getBrowser();
// 封装对象
UserLoginLog userLoginLog = new UserLoginLog();
userLoginLog.setUserName(username);
userLoginLog.setIpaddr(ip);
userLoginLog.setLoginLocation(address);
userLoginLog.setBrowser(browser);
userLoginLog.setOs(os);
userLoginLog.setMessage(message);
// 设置日志状态
if (StrUtil.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
userLoginLog.setStatus(Constants.SUCCESS);
} else if (Constants.LOGIN_FAIL.equals(status)) {
userLoginLog.setStatus(Constants.FAIL);
}
// 调用 Service 层方法,记录日志
SpringUtil.getBean(UserLoginLogService.class).insertUserLoginLog(userLoginLog);
}
};
}
除了一些记录常规信息之外,通过解析
User Agent
中文名为用户代理这个特殊字符串头获取并记录下客户使用的
操作系统及版本
、
浏览器及版本
、
浏览器渲染引擎
等。
具体解析方法,我使用了 Hutool 中 UserAgentParser.parse(请求头) 二次封装了一下去获取的(推荐),如果不使用该方法,那可能要引入相关包依赖去解析获取了。
五、调用
// 执行异步任务
AsyncTaskManger.me().execute(AsyncTaskFactory.recordUserLoginLog(username, Constants.LOGIN_SUCCESS, "User login successful."));
大功告成,链式调用美观优雅,skr ~
六、结尾
撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。