Java开发高频实用技巧汇总(List操作/多线程/反射/监控等)
在Java日常开发中,我们经常会遇到List去重分组、多线程数据传递、动态脚本执行、日志统一处理等场景,本文汇总了一系列可直接复用的实用技巧和工具类,覆盖集合操作、Lambda进阶、JVM监控、多线程、AOP等核心场景,助力提升开发效率。
一、List集合高频操作
1. List根据指定字段去重(Collectors.toMap)
使用Collectors.toMap三参数版本实现按字段去重,第三个参数用于指定key重复时的处理策略(保留旧值/新值),相比传统遍历去重更简洁高效。
java
import java.util.ArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.List;
// 示例实体类(按需替换为自己的实体)
class TalentPlanStudentEntity {
private Long userId;
// 省略getter/setter/构造方法
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
public class ListDeduplicationDemo {
public static void main(String[] args) {
List<TalentPlanStudentEntity> relatePlanStudentList = new ArrayList<>();
// 模拟添加重复数据
relatePlanStudentList.add(new TalentPlanStudentEntity());
relatePlanStudentList.get(0).setUserId(1L);
relatePlanStudentList.add(new TalentPlanStudentEntity());
relatePlanStudentList.get(1).setUserId(1L);
// 按userId去重,保留旧值(oldValue)
List<TalentPlanStudentEntity> studentList = new ArrayList<>(relatePlanStudentList.stream()
.collect(Collectors.toMap(
TalentPlanStudentEntity::getUserId, // key:去重字段
Function.identity(), // value:当前对象
(oldValue, newValue) -> oldValue // key重复时保留旧值(可改为newValue保留新值)
)).values());
System.out.println("去重后数量:" + studentList.size());
}
}
2. List按指定字段分组(Collectors.groupingBy)
快速实现List按指定字段分组,返回Map<字段类型, List<原对象>>结构,适用于批量数据分类场景。
java
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// 示例实体类
class Coupon {
private Integer couponId;
private String couponName;
// 省略getter/setter
public Integer getCouponId() {
return couponId;
}
public void setCouponId(Integer couponId) {
this.couponId = couponId;
}
}
public class ListGroupDemo {
public static void main(String[] args) {
List<Coupon> couponList = new ArrayList<>();
// 模拟添加数据
Coupon coupon1 = new Coupon();
coupon1.setCouponId(1);
Coupon coupon2 = new Coupon();
coupon2.setCouponId(1);
Coupon coupon3 = new Coupon();
coupon3.setCouponId(2);
couponList.add(coupon1);
couponList.add(coupon2);
couponList.add(coupon3);
// 按couponId分组
Map<Integer, List<Coupon>> resultList = couponList.stream()
.collect(Collectors.groupingBy(Coupon::getCouponId));
// 遍历分组结果
resultList.forEach((couponId, coupons) -> {
System.out.println("优惠券ID:" + couponId + ",数量:" + coupons.size());
});
}
}
二、Lambda表达式进阶:获取字段名与方法名
通过序列化函数式接口和反射解析SerializedLambda,可以优雅地获取Lambda表达式对应的字段名和方法名,避免硬编码字段名带来的维护问题。
1. 序列化函数式接口定义
自定义函数式接口继承原生接口并实现Serializable,支持序列化解析。
java
import java.io.Serializable;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 序列化Consumer接口
*/
@FunctionalInterface
public interface MConsumer<T> extends Consumer<T>, Serializable {
}
/**
* 序列化Function接口
*/
@FunctionalInterface
public interface MFunction<T, R> extends Function<T, R>, Serializable {
}
2. LambdaUtil工具类(完整可复用)
java
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.property.PropertyNamer;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
@Slf4j
public class LambdaUtil {
/**
* 从SerializedLambda获取字段名
*/
public static String getColumn(SerializedLambda serializedLambda) {
return PropertyNamer.methodToProperty(serializedLambda.getImplMethodName());
}
/**
* 获取MFunction对应的SerializedLambda
*/
@SneakyThrows
public static <T, R> SerializedLambda getSerializedLambda(MFunction<T, R> func) {
Method writeReplace = func.getClass().getDeclaredMethod("writeReplace");
writeReplace.setAccessible(true);
Object sl = writeReplace.invoke(func);
return (SerializedLambda) sl;
}
/**
* 获取MConsumer对应的SerializedLambda
*/
@SneakyThrows
public static <T> SerializedLambda getSerializedLambda(MConsumer<T> func) {
Method writeReplace = func.getClass().getDeclaredMethod("writeReplace");
writeReplace.setAccessible(true);
Object sl = writeReplace.invoke(func);
return (SerializedLambda) sl;
}
/**
* 根据MFunction获取字段名称(例:User::getId -> id)
*/
@SneakyThrows
public static <T, R> String getColumn(MFunction<T, R> func) {
return getColumn(getSerializedLambda(func));
}
/**
* 根据MFunction获取方法名称
*/
public static <T, R> String getMethodName(MFunction<T, R> func) {
return getSerializedLambda(func).getImplMethodName();
}
/**
* 根据MConsumer获取方法名称
*/
public static <T> String getMethodName(MConsumer<T> func) {
return getSerializedLambda(func).getImplMethodName();
}
}
三、JVM监控:获取实时堆内存信息
通过MemoryMXBean可以获取JVM堆内存的初始值、已用值、提交值和最大值,用于监控应用内存使用状态。
java
import lombok.extern.slf4j.Slf4j;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
@Slf4j
public class JvmHeapMemoryMonitor {
public static void main(String[] args) {
// 获取内存MXBean
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
// 获取堆内存使用情况
MemoryUsage memoryUsage = memoryMXBean.getHeapMemoryUsage();
// 单位转换:字节 -> 兆字节(MB)
long initMB = memoryUsage.getInit() >> 20;
long usedMB = memoryUsage.getUsed() >> 20;
long committedMB = memoryUsage.getCommitted() >> 20;
long maxMB = memoryUsage.getMax() >> 20;
log.info("JVM堆内存信息:init:{}M, used:{}M, committed:{}M, max:{}M",
initMB, usedMB, committedMB, maxMB);
}
}
四、多线程开发核心技巧
1. 线程池:主线程数据传递到子线程(无侵入增强)
通过同步MDC(日志上下文)和RequestContextHolder(Spring请求上下文),实现主线程数据向子线程的传递,无需修改原有线程池逻辑。
java
import cn.hutool.core.thread.ThreadUtil;
import org.slf4j.MDC;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
public class ThreadUtilDecorator {
/**
* 增强Runnable执行,传递MDC和RequestContext
*/
public static void execute(Runnable runnable) {
Map<String, String> mdcMap = MDC.getCopyOfContextMap();
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ThreadUtil.execute(() -> {
try {
// 设置子线程上下文
if (mdcMap != null) {
MDC.setContextMap(mdcMap);
}
RequestContextHolder.setRequestAttributes(requestAttributes);
// 执行原任务
runnable.run();
} finally {
// 清理上下文,避免内存泄漏
MDC.clear();
RequestContextHolder.resetRequestAttributes();
}
});
}
/**
* 增强Callable执行,传递MDC和RequestContext,返回Future
*/
public static <T> Future<T> execAsync(Callable<T> task) {
Map<String, String> mdcMap = MDC.getCopyOfContextMap();
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
return ThreadUtil.execAsync(() -> {
try {
if (mdcMap != null) {
MDC.setContextMap(mdcMap);
}
RequestContextHolder.setRequestAttributes(requestAttributes);
return task.call();
} finally {
MDC.clear();
RequestContextHolder.resetRequestAttributes();
}
});
}
}
2. 多线程异步处理List并获取返回结果
通过Future收集异步任务结果,实现List数据的批量异步处理,提高处理效率。
java
import cn.hutool.core.thread.ThreadUtil;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@Data
class TestUser {
private String username;
private Integer age;
}
public class ListAsyncHandleDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 初始化测试数据
List<TestUser> userList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
TestUser testUser = new TestUser();
testUser.setUsername("a" + i);
testUser.setAge(1);
userList.add(testUser);
}
// 存储异步任务结果
List<Future<TestUser>> futureList = new ArrayList<>();
// 提交异步任务
for (TestUser testUser : userList) {
Future<TestUser> future = ThreadUtil.execAsync(new Callable<TestUser>() {
@Override
public TestUser call() throws Exception {
// 模拟耗时操作
Thread.sleep(100);
testUser.setAge(testUser.getAge() + 1);
return testUser;
}
});
futureList.add(future);
}
// 收集异步任务结果
List<TestUser> resultList = new ArrayList<>();
for (Future<TestUser> future : futureList) {
resultList.add(future.get());
}
// 打印结果
for (TestUser testUser : resultList) {
System.out.println("用户名:" + testUser.getUsername() + ",年龄:" + testUser.getAge());
}
}
}
五、动态脚本:Java执行Groovy脚本
1. Maven依赖引入
xml
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.7</version>
<type>pom</type>
</dependency>
2. GroovyUtil工具类(缓存优化)
通过MD5缓存脚本实例,避免重复解析脚本,提高执行效率,也可直接使用Hutool开源工具替代。
java
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class GroovyUtil {
private static final GroovyShell shell = new GroovyShell();
private static final Map<String, Script> SCRIPT_CACHE = new ConcurrentHashMap<>();
private GroovyUtil() {}
/**
* 执行Groovy脚本指定方法
* @param scriptText 脚本内容
* @param methodName 方法名
* @param args 方法参数
* @param <T> 返回值类型
* @return 方法执行结果
*/
@SuppressWarnings("unchecked")
public static <T> T shell(String scriptText, String methodName, Object... args) {
if (StrUtil.isEmpty(scriptText)) {
return null;
}
// 生成脚本缓存key
String key = SecureUtil.md5(scriptText);
Script script = SCRIPT_CACHE.get(key);
// 缓存不存在则解析脚本并放入缓存
if (script == null) {
script = shell.parse(scriptText);
SCRIPT_CACHE.put(key, script);
}
// 执行脚本方法
return (T) script.invokeMethod(methodName, args);
}
}
六、实用工具类汇总
1. 随机对象生成工具(RandomUtils)
支持生成普通随机对象和数据库实体随机对象(适配varchar/char字段长度限制)。
java
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import lombok.SneakyThrows;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RandomUtils {
private static Map<Class<?>, Map<String, Map<String, Object>>> dataBaseMap = new HashMap<>();
// 简易Spring上下文获取(可根据项目实际情况调整)
private static final ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
/**
* 生成普通随机对象
*/
@SneakyThrows
public static <T> T generateObject(Class<T> clazz) {
T t = clazz.getDeclaredConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
setRandomFieldValue(t, field);
}
return t;
}
/**
* 生成数据库实体随机对象(适配字符串字段长度)
*/
@SneakyThrows
public static <T> T generateEntity(Class<T> clazz, String tableName) {
T t = clazz.getDeclaredConstructor().newInstance();
Map<String, Map<String, Object>> dataMap = getDataBaseMap(clazz, tableName);
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
if (field.getType() == String.class) {
String snowflakeId = IdUtil.getSnowflakeNextIdStr();
Map<String, Object> columnMap = dataMap.get(camelToUnderline(field.getName()));
// 适配varchar/char字段长度
if (columnMap != null && ("varchar".equals(columnMap.get("dataType")) || "char".equals(columnMap.get("dataType")))) {
int length = ((Long) columnMap.get("length")).intValue();
if (snowflakeId.length() > length) {
snowflakeId = snowflakeId.substring(0, length);
}
}
field.set(t, snowflakeId);
} else {
setRandomFieldValue(t, field);
}
}
return t;
}
/**
* 设置随机字段值
*/
private static <T> void setRandomFieldValue(T t, Field field) throws IllegalAccessException {
Class<?> fieldType = field.getType();
if (fieldType == Long.class) {
field.set(t, IdUtil.getSnowflakeNextId());
} else if (fieldType == LocalDate.class) {
field.set(t, LocalDate.now());
} else if (fieldType == LocalDateTime.class) {
field.set(t, LocalDateTime.now());
} else if (fieldType == Integer.class) {
field.set(t, RandomUtil.randomInt(1, 10000));
} else if (fieldType == BigDecimal.class) {
field.set(t, RandomUtil.randomBigDecimal(BigDecimal.ZERO, new BigDecimal(1000)));
}
}
/**
* 获取数据库表字段映射
*/
private static Map<String, Map<String, Object>> getDataBaseMap(Class<?> clazz, String tableName) {
if (dataBaseMap.containsKey(clazz)) {
return dataBaseMap.get(clazz);
}
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
String sql = "SELECT column_name columnName, data_type dataType, CHARACTER_MAXIMUM_LENGTH length, " +
"column_comment columnComment, column_key columnKey, extra FROM information_schema.columns " +
"WHERE table_name = ? AND table_schema = (SELECT DATABASE()) ORDER BY ordinal_position";
List<Map<String, Object>> columnList = jdbcTemplate.queryForList(sql, tableName);
Map<String, Map<String, Object>> columnMap = new HashMap<>();
for (Map<String, Object> map : columnList) {
columnMap.put((String) map.get("columnName"), map);
}
dataBaseMap.put(clazz, columnMap);
return columnMap;
}
/**
* 驼峰命名转下划线命名
*/
private static String camelToUnderline(String line) {
if (line == null || line.isEmpty()) {
return "";
}
line = String.valueOf(line.charAt(0)).toUpperCase() + line.substring(1);
Pattern pattern = Pattern.compile("[A-Z]([a-z\\d]+)?");
Matcher matcher = pattern.matcher(line);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
sb.append(matcher.group().toUpperCase());
if (matcher.end() != line.length()) {
sb.append("_");
}
}
return sb.toString().toLowerCase();
}
}
2. 参数替换工具类(ParamReplaceUtil)
支持<>占位符替换,适配注解参数解析场景,支持单表达式和多表达式替换。
java
import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 参数替换工具类
*/
public class ParamReplaceUtil {
public static final Pattern PATTERN = Pattern.compile("<(.*?)>");
/**
* 替换Map参数占位符
*/
public static String replace(String original, Map<String, String> param) {
if (StrUtil.isEmpty(original)) {
return original;
}
Matcher matcher = PATTERN.matcher(original);
while (matcher.find()) {
String key = matcher.group(1);
if (param.containsKey(key)) {
original = original.replaceAll("<" + key + ">", param.get(key));
}
}
return original;
}
/**
* 替换单表达式占位符(例:<user.id>)
*/
@SneakyThrows
public static Object replaceSingle(String original, Object[] params) {
if (StrUtil.isEmpty(original)) {
return original;
}
Matcher matcher = PATTERN.matcher(original);
if (matcher.find()) {
String el = matcher.group(1);
String className = el.split("\\.")[0];
String fieldName = el.split("\\.")[1];
for (Object o : params) {
Class<?> clazz = o.getClass();
if (clazz.getSimpleName().equals(captureName(className))) {
Field field = getField(clazz, fieldName);
field.setAccessible(true);
return field.get(o);
}
}
}
return original;
}
/**
* 替换多表达式占位符(例:<user.name>,<user.id>)
*/
@SneakyThrows
public static String replaceMultiple(String original, Object[] params) {
if (StrUtil.isEmpty(original)) {
return original;
}
Matcher matcher = PATTERN.matcher(original);
while (matcher.find()) {
String el = matcher.group(1);
String className = el.split("\\.")[0];
String fieldName = el.split("\\.")[1];
for (Object o : params) {
Class<?> clazz = o.getClass();
if (clazz.getSimpleName().equals(captureName(className))) {
Field field = getField(clazz, fieldName);
field.setAccessible(true);
Object fieldValue = field.get(o);
original = original.replaceAll("<" + el + ">", StrUtil.toString(fieldValue));
}
}
}
return original;
}
/**
* 获取字段(包含父类字段)
*/
private static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
return clazz.getSuperclass().getDeclaredField(fieldName);
}
}
/**
* 字符串首字母大写
*/
public static String captureName(String str) {
if (StrUtil.isEmpty(str)) {
return str;
}
char[] cs = str.toCharArray();
cs[0] -= 32;
return String.valueOf(cs);
}
}
使用示例
- 注解定义
java
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLogAnno {
String operatePage() default "";
String operateType() default "";
String bizType() default "";
}
- 接口使用
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@PostMapping("/test1")
@OperationLogAnno(operatePage="<user.id>", operateType="984894", bizType="98979894")
public String list1(@RequestBody User user) {
return "success";
}
}
@Data
class User {
private Long id;
private String name;
}
- 切面解析
java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class OperationLogAspect {
private static final Logger log = LoggerFactory.getLogger(OperationLogAspect.class);
@Before("@annotation(OperationLogAnno)")
public void before(JoinPoint joinPoint) {
Object[] params = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLogAnno anno = method.getDeclaredAnnotation(OperationLogAnno.class);
// 解析单表达式
Object operatePage = ParamReplaceUtil.replaceSingle(anno.operatePage(), params);
// 解析多表达式
String operateType = ParamReplaceUtil.replaceMultiple(anno.operateType(), params);
log.info("operatePage:{}", operatePage);
log.info("operateType:{}", operateType);
}
}
3. 反射执行方法工具
动态加载类并执行指定方法,异常时返回原始值,适用于动态调用场景。
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
public class ReflectMethodUtil {
private static final Logger log = LoggerFactory.getLogger(ReflectMethodUtil.class);
/**
* 反射执行指定方法
* @param path 类全路径
* @param methodName 方法名
* @param value 方法参数
* @return 方法执行结果
*/
private static String getDesensitizationData(String path, String methodName, String value) {
String result;
try {
Class<?> clazz = Class.forName(path);
Method method = clazz.getDeclaredMethod(methodName, String.class);
result = (String) method.invoke(clazz.getDeclaredConstructor().newInstance(), value);
} catch (Exception e) {
log.error("执行反射方法失败", e);
return value;
}
return result;
}
}
七、正则表达式:匹配指定标签中间的字符
提供三种常用匹配模式,适配不同场景下的标签内容提取。
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTagMatchDemo {
private static final Logger log = LoggerFactory.getLogger(RegexTagMatchDemo.class);
public static void main(String[] args) {
String result = "<ms>测试内容</ms><HIPMessageServerResult>返回结果</HIPMessageServerResult>";
// 模式1:匹配<ms>标签中非中英文字符(按需调整)
Pattern pattern1 = Pattern.compile("<ms>([^x00-xff]*)</ms>");
Matcher matcher1 = pattern1.matcher(result);
if (matcher1.find()) {
log.info("模式1匹配结果:{}", matcher1.group(1));
}
// 模式2:匹配<HIPMessageServerResult>标签中所有非换行字符
Pattern pattern2 = Pattern.compile("<HIPMessageServerResult>(.*)</HIPMessageServerResult>");
Matcher matcher2 = pattern2.matcher(result);
if (matcher2.find()) {
log.info("模式2匹配结果:{}", matcher2.group(1));
}
// 模式3:匹配<ms>标签中所有字符(包含换行符)
Pattern pattern3 = Pattern.compile("<ms>([\\s\\S]*)</ms>");
Matcher matcher3 = pattern3.matcher(result);
if (matcher3.find()) {
log.info("模式3匹配结果:{}", matcher3.group(1));
}
}
}
八、AOP统一操作日志处理
实现接口操作日志的统一记录,包含模块、业务类型、请求参数、IP、返回结果等信息,支持异常捕获。
1. Maven依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 步骤1:定义Log注解
java
import org.monitor.enums.BusinessType;
import org.monitor.enums.OperatorType;
import java.lang.annotation.*;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/** 模块标题 */
String title() default "";
/** 业务类型 */
BusinessType businessType() default BusinessType.OTHER;
/** 操作人类别 */
OperatorType operatorType() default OperatorType.MANAGE;
/** 是否保存请求参数 */
boolean isSaveRequestData() default true;
}
3. 步骤2:实现LogAspect切面类
java
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.monitor.annotation.Log;
import org.monitor.bean.SysOperLog;
import org.monitor.enums.BusinessStatus;
import org.monitor.service.SysOperLogService;
import org.monitor.util.IpUtils;
import org.monitor.util.ServletUtils;
import org.monitor.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
@Autowired
private SysOperLogService sysOperLogService;
// 切点:匹配所有添加@Log注解的方法
@Pointcut("@annotation(org.monitor.annotation.Log)")
public void logPointCut() {}
/** 正常返回后记录日志 */
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleLog(joinPoint, null, jsonResult);
}
/** 异常抛出后记录日志 */
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
try {
// 获取注解信息
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null) {
return;
}
// 构建日志实体
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
operLog.setOperIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
operLog.setOperTime(new Date());
operLog.setJsonResult(JSON.toJSONString(jsonResult));
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
operLog.setOperName("admin"); // 实际项目中替换为当前登录用户
// 异常处理
if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法信息
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 设置注解参数
getControllerMethodDescription(joinPoint, controllerLog, operLog);
// 保存日志
sysOperLogService.insert(operLog);
} catch (Exception exp) {
log.error("==AOP日志记录异常==", exp);
}
}
/** 获取注解描述信息 */
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception {
operLog.setBusinessType(log.businessType().ordinal());
operLog.setTitle(log.title());
operLog.setOperatorType(log.operatorType().ordinal());
// 保存请求参数
if (log.isSaveRequestData()) {
setRequestValue(joinPoint, operLog);
}
}
/** 设置请求参数 */
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception {
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
}
}
/** 获取方法上的Log注解 */
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
return method != null ? method.getAnnotation(Log.class) : null;
}
/** 拼装请求参数 */
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (!isFilterObject(o)) {
try {
params += JSON.toJSONString(o) + " ";
} catch (Exception e) {
// 忽略序列化异常
}
}
}
}
return params.trim();
}
/** 过滤无需序列化的对象 */
public boolean isFilterObject(final Object o) {
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
}
}
4. 步骤3:SysOperLog日志实体
java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
@TableName("sys_oper_log")
public class SysOperLog {
private static final long serialVersionUID = 1L;
/** 日志主键 */
@TableId(type = IdType.AUTO)
private Long operId;
/** 操作模块 */
private String title;
/** 业务类型(0其它 1新增 2修改 3删除) */
private Integer businessType;
/** 业务类型数组 */
private Integer[] businessTypes;
/** 请求方法 */
private String method;
/** 请求方式 */
private String requestMethod;
/** 操作类别(0其它 1后台用户 2手机端用户) */
private Integer operatorType;
/** 操作人员 */
private String operName;
/** 部门名称 */
private String deptName;
/** 请求url */
private String operUrl;
/** 操作地址 */
private String operIp;
/** 请求参数 */
private String operParam;
/** 返回参数 */
private String jsonResult;
/** 操作状态(0正常 1异常) */
private Integer status;
/** 错误消息 */
private String errorMsg;
/** 操作时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date operTime;
}
5. 步骤4:数据库表结构
sql
CREATE TABLE `sys_oper_log` (
`oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`title` varchar(50) DEFAULT '' COMMENT '模块标题',
`business_type` int(2) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
`method` varchar(100) DEFAULT '' COMMENT '方法名称',
`request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
`operator_type` int(1) DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
`oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
`dept_name` varchar(50) DEFAULT '' COMMENT '部门名称',
`oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
`oper_ip` varchar(50) DEFAULT '' COMMENT '主机地址',
`oper_location` varchar(255) DEFAULT '' COMMENT '操作地点',
`oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
`json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
`status` int(1) DEFAULT '0' COMMENT '操作状态(0正常 1异常)',
`error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
`oper_time` datetime DEFAULT NULL COMMENT '操作时间',
PRIMARY KEY (`oper_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='操作日志记录';
九、项目部署:nohup后台运行Java应用
提供Shell脚本,先停止旧服务,再后台启动jar包,指定UTF-8编码并输出日志。
bash
#!/bin/bash
# 查找旧服务进程ID
ids=`ps -ef | grep "monitor.jar" | grep -v "grep" | awk '{print $2}'`
echo "当前服务进程ID:" $ids
# 停止旧服务
for id in $ids
do
kill -9 $id
echo "已杀死进程:$id"
done
# 后台启动jar包,指定编码,日志输出到nohup.out
nohup java -jar -Dfile.encoding=utf-8 monitor.jar > nohup.out 2>&1 &
echo "服务已后台启动"
总结
本文汇总的Java实用技巧覆盖了集合操作、Lambda进阶、JVM监控、多线程、动态脚本、工具类、正则、AOP、项目部署等核心场景,所有代码均可直接复制复用,能够有效解决日常开发中的常见问题,提升开发效率和项目可维护性。