每天一个知识点——Java注解

1.注解介绍

Annotation (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。

注解本质是一个继承了Annotation 的特殊接口:

less 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}

public interface Override extends Annotation{

}

注解只有被解析之后才会生效,常见的解析方法有两种:

  • 编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
  • 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的 @Value@Component)都是通过反射来进行处理的。

2. 自定义注解实现数据加密

2.1 创建自定义注解,自定义注解的创建需要使用 @interface 关键字

less 复制代码
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptMethod  {
    String[] fields() default ""; // 用于指定需要加密的字段名称
}
  • @Target:定义注解可以应用的目标元素类型。
  • @Retention:定义注解的保留策略(源码、编译时、运行时)。
  • @Documented:标记注解类型,使其在 JavaDoc 文档中可见。

2.2 解析自定义注解

typescript 复制代码
@Aspect
@Component
public class EncryptAspect {

    @PostConstruct
    public void init() {
        System.out.println("EncryptAspect initialized.");
    }

    /* ---------- 切点 ---------- */
    @Pointcut("@annotation(encryptMethod)")
    public void encryptPointcut(EncryptMethod encryptMethod) {}

    /* ---------- 环绕通知 ---------- */
    @Around("encryptPointcut(encryptMethod)")
    public Object encryptFields(ProceedingJoinPoint jp, EncryptMethod encryptMethod) throws Throwable {
        System.out.println("EncryptAspect invoked for: " + jp.getSignature());

        // 1. 处理入参
        Object[] args = jp.getArgs();
        if (args != null) {
            for (Object arg : args) {
                if (arg == null) continue;
                for (String fieldName : encryptMethod.fields()) {
                    encryptField(arg, fieldName);
                }
            }
        }

        // 2. 执行原方法
        Object result = jp.proceed();

        // 3. 处理返回值
        if (result != null) {
            // 情况1:直接返回 List
            if (result instanceof Collection<?>) {
                for (Object item : (Collection<?>) result) {
                    for (String field : encryptMethod.fields()) {
                        encryptField(item, field);
                    }
                }
            }
            // 情况2:返回 TableDataInfo
            else if (result instanceof TableDataInfo) {
                TableDataInfo table = (TableDataInfo) result;
                List<?> rows = table.getRows();
                if (rows != null) {
                    for (Object row : rows) {
                        for (String field : encryptMethod.fields()) {
                            encryptField(row, field);
                        }
                    }
                }
            }
            // 情况3:单个对象
            else {
                for (String field : encryptMethod.fields()) {
                    encryptField(result, field);
                }
            }
        }

        return result;
    }

    /* ---------- 反射加密指定字段 ---------- */
    private void encryptField(Object target, String fieldName) {
        try {
            Field field = findField(target.getClass(), fieldName);
            if (field == null) return;

            field.setAccessible(true);
            Object value = field.get(target);
            if (value instanceof String) {
                String cipher = encrypt((String) value);
                field.set(target, cipher);
            }
        } catch (Exception e) {
            // 记录日志即可,不要阻断业务
            e.printStackTrace();
        }
    }

    /* ---------- 递归找字段(支持父类) ---------- */
    private Field findField(Class<?> clazz, String fieldName) {
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                return clazz.getDeclaredField(fieldName);
            } catch (NoSuchFieldException ignored) {}
        }
        return null;
    }

    /* ---------- AES 加密 ---------- */
    private String encrypt(String plain) {
        try {
            String key = "XnUFyFy+9G0JWc9LwaYAWw==";
            byte[] cipherBytes = AES.encrypt(plain.getBytes(StandardCharsets.UTF_8),
                    key.getBytes(), AES.ALGORITHM_AEPP);
            return Base64.getUrlEncoder().withoutPadding().encodeToString(cipherBytes);
        } catch (Exception e) {
            e.printStackTrace();
            return plain;
        }
    }
}

切面:EncryptAspect

  • @Aspect:标记该类为一个切面类。

切点:

java 复制代码
@Pointcut("@annotation(encryptMethod)")
  • @Pointcut:定义一个切点,指定哪些方法会被拦截。
  • @annotation(encryptMethod) :匹配所有被 @EncryptMethod 注解标记的方法。

连接点:

java 复制代码
public Object encryptFields(ProceedingJoinPoint jp, EncryptMethod encryptMethod) throws Throwable {

ProceedingJoinPoint :提供对目标方法的访问,可以通过 proceed() 方法执行目标方法

通知:

java 复制代码
@Around("encryptPointcut(encryptMethod)")
  • @Around:定义一个环绕通知,用于在方法执行前后执行自定义逻辑。

总结执行逻辑:

  1. 定义切点 :匹配被 @EncryptMethod 注解标记的方法。
  2. 环绕通知:在方法执行前后执行自定义逻辑。
  3. 处理入参:对方法的参数中的指定字段进行加密。
  4. 执行原方法:调用目标方法。
  5. 处理返回值:对返回值中的指定字段进行加密。
  6. 反射加密:通过反射机制访问和修改字段值。
  7. AES 加密:使用 AES 算法对字段值进行加密。

这种实现方式可以灵活地对方法的参数和返回值进行加密处理,适用于需要对敏感数据进行加密的场景。

相关推荐
IT_陈寒23 分钟前
SpringBoot 3.x实战:5种高并发场景下的性能优化秘籍,让你的应用快如闪电!
前端·人工智能·后端
Victor35640 分钟前
Redis(47)如何配置Redis哨兵?
后端
Victor35641 分钟前
Redis(46) 如何搭建Redis哨兵?
后端
尘鹄6 小时前
go 初始化组件最佳实践
后端·设计模式·golang
墩墩分墩6 小时前
【Go语言入门教程】 Go语言的起源与技术特点:从诞生到现代编程利器(一)
开发语言·后端·golang·go
程序员爱钓鱼8 小时前
Go语言实战案例- 开发一个ToDo命令行工具
后端·google·go
学渣676569 小时前
文件传输工具rsync|rust开发环境安装|Ascend实验相关命令
开发语言·后端·rust
我是渣哥9 小时前
Java String vs StringBuilder vs StringBuffer:一个性能优化的探险故事
java·开发语言·jvm·后端·算法·职场和发展·性能优化
晚安里10 小时前
JVM相关 4|JVM调优与常见参数(如 -Xms、-Xmx、-XX:+PrintGCDetails) 的必会知识点汇总
java·开发语言·jvm·后端·算法
齐 飞11 小时前
SpringBoot实现国际化(多语言)配置
java·spring boot·后端