Java开发高频实用技巧汇总(List操作/多线程/反射/监控等)

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);
    }
}

使用示例

  1. 注解定义
java 复制代码
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLogAnno {
    String operatePage() default "";
    String operateType() default "";
    String bizType() default "";
}
  1. 接口使用
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;
}
  1. 切面解析
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、项目部署等核心场景,所有代码均可直接复制复用,能够有效解决日常开发中的常见问题,提升开发效率和项目可维护性。

相关推荐
alonewolf_993 小时前
深入Spring核心原理:从Bean生命周期到AOP动态代理全解析
java·后端·spring
天远Date Lab3 小时前
Python实现用户消费潜力评估:天远个人消费能力等级API对接全攻略
java·大数据·网络·python
没有bug.的程序员10 小时前
服务安全:内部服务如何防止“裸奔”?
java·网络安全·云原生安全·服务安全·零信任架构·微服务安全·内部鉴权
一线大码11 小时前
SpringBoot 3 和 4 的版本新特性和升级要点
java·spring boot·后端
YJlio11 小时前
VolumeID 学习笔记(13.10):卷序列号修改与资产标识管理实战
windows·笔记·学习
weixin_4407305011 小时前
java数组整理笔记
java·开发语言·笔记
weixin_4250230011 小时前
Spring Boot 实用核心技巧汇总:日期格式化、线程管控、MCP服务、AOP进阶等
java·spring boot·后端
一线大码11 小时前
Java 8-25 各个版本新特性总结
java·后端
2501_9061505611 小时前
私有部署问卷系统操作实战记录-DWSurvey
java·运维·服务器·spring·开源