前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Java自定义注解:定义、解析,AOP切面与日志打印

Java自定义注解:定义、解析,AOP切面与日志打印

原创
作者头像
Yeats_Liao
发布2025-01-10 23:55:54
发布2025-01-10 23:55:54
1830
举报

本文主要介绍了 Java 中的自定义注解以及结合 AOP(面向切面编程)技术进行日志记录的方法。

1. 注解定义

注解是附在代码上的元数据(Metadata),给编译器或运行时提供额外指导信息。Java自带多种注解,也可自定义以适应特殊需求。

代码示例

创建一个名为LogExecution的注解,它可以应用到方法上,并有两个可选参数:value(用于存储自定义消息)和level(用于指定日志级别)。

代码语言:java
复制
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// @Target 表明该注解可以应用于哪些程序元素
@Target(ElementType.METHOD)
// @Retention 表明注解在何时可用:源码级别、类文件级别还是运行时级别
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
    // 定义注解的一个属性,并给出默认值
    String value() default "";
    
    // 可以定义更多的属性,例如记录的日志级别
    LogLevel level() default LogLevel.INFO;
}

// 自定义枚举用于指定日志级别
public enum LogLevel {
    DEBUG, INFO, WARN, ERROR
}

2. 注解解析

通过Java反射API(动态地获取和操作类、接口、字段、方法等信息)去利用自定义注解,下面的代码用于检查方法是否标有LogExecution注解,如果有则在运行时执行日志记录。

代码示例

代码语言:java
复制
import java.lang.reflect.Method;

public class AnnotationProcessor {
    public void process(Method method) {
        if (method.isAnnotationPresent(LogExecution.class)) {
            LogExecution logExecution = method.getAnnotation(LogExecution.class);
            String message = logExecution.value();
            LogLevel level = logExecution.level();

            // 根据日志级别调用对应的日志接口
            switch (level) {
                case DEBUG:
                    System.out.println("DEBUG: " + message);
                    break;
                case INFO:
                    System.out.println("INFO: " + message);
                    break;
                // ... 其他级别处理 ...
            }
        }
    }
}

3. 自定义注解结合AOP切面

Spring AOP能帮我们在程序中不改动原有代码的情况下,加上一些通用功能,比如记录日志。

假设有一个LogExecution标记,我们可以用AOP技术,设置在这个方法执行前后自动做某些事情。在方法开始前和结束后记时,然后按照标记里设定的日志级别,比如是警告还是信息,打印一条包含执行时长的消息。这样就不用每个方法都手动去写这些记录时间、打日志的代码了。

代码示例

代码语言:java
复制
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Around("@annotation(LogExecution)")
    public Object logMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        LogExecution annotation = signature.getMethod().getAnnotation(LogExecution.class);
        // 开始计时
        long start = System.currentTimeMillis();
        // 执行原方法        
        Object result = joinPoint.proceed();
        // 结束计时并打印日志
        long executionTime = System.currentTimeMillis() - start;
        String message = annotation.value();
        LogLevel level = annotation.level();

        // 打印执行时间和注解信息
        printLog(level, message, executionTime);

        return result;
    }

    private void printLog(LogLevel level, String message, long executionTime) {
        // 根据日志级别调用对应的日志服务,这里仅做简单演示
        switch (level) {
            case DEBUG:
                System.out.printf("[DEBUG] %s took %d ms%n", message, executionTime);
                break;
            // ... 其他级别处理 ...
        }
    }
}

4. 自定义注解用于日志打印

结合前面的例子,我们可以进一步完善LogExecution注解的功能,使其不仅能记录执行时间,还能按照注解中定义的消息格式打印日志。

1.创建自定义注解

代码语言:java
复制
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecution {
    String message() default "Method execution: {}";
    LogLevel level() default LogLevel.INFO;
    
    enum LogLevel {
        TRACE, DEBUG, INFO, WARN, ERROR
    }
}

2.定义服务接口和其实现类,并在方法上应用注解

代码语言:java
复制
public interface UserService {
    @LogExecution(message = "User is being retrieved by ID: {}", level = LogLevel.DEBUG)
    User getUserById(Long id);
}

@Service
public class UserServiceImpl implements UserService {
    // ...
    @Override
    public User getUserById(Long id) {
        // 实现获取用户的方法逻辑
        // ...
        return user;
    }
}

3. 编写AOP切面类处理日志打印

代码语言:java
复制
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAspect.class);

    @Around("@annotation(logExecution)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecution logExecution) throws Throwable {
        String methodSignature = joinPoint.getSignature().toShortString();
        String formattedMessage = String.format(logExecution.message(), methodSignature);

        switch (logExecution.level()) {
            case TRACE:
                LOGGER.trace(formattedMessage);
                break;
            case DEBUG:
                LOGGER.debug(formattedMessage);
                break;
            case INFO:
                LOGGER.info(formattedMessage);
                break;
            case WARN:
                LOGGER.warn(formattedMessage);
                break;
            case ERROR:
                LOGGER.error(formattedMessage);
                break;
        }

        long start = System.currentTimeMillis();
        try {
            // 执行原方法
            Object result = joinPoint.proceed();
            // 记录执行结束时间(此处仅演示了注解的功能性,实际可能需要记录更多信息)
            LOGGER.info("Finished executing {}, took {}ms", methodSignature, System.currentTimeMillis() - start);
            return result;
        } catch (Throwable ex) {
            LOGGER.error("An error occurred while executing {}", methodSignature, ex);
            throw ex;
        }
    }
}

当调用getUserById方法时,由于该方法被@LogExecution注解标记,所以会触发LoggingAspect中的logExecutionTime方法,在方法执行前后进行相应的日志记录。

4.在Controller类上应用自定义注解

代码语言:java
复制
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/users/{id}")
    @LogExecution(message = "User is being retrieved from API with ID: {}", level = LogLevel.INFO)
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }
}

当访问/users/{id}端点以获取用户信息时,由于getUser方法上添加了@LogExecution注解,所以每当此方法被调用时,LoggingAspect切面会捕获到这个事件,并根据注解的配置打印相应的日志信息。如果客户端请求 /users/1 ,那么控制台将会看到类似于以下格式的日志:

代码语言:java
复制
INFO [com.example.LoggingAspect] - User is being retrieved from API with ID: UserController.getUser(Long)
INFO [com.example.LoggingAspect] - Finished executing UserController.getUser(Long), took x ms

5. 总结

自定义注解为程序元素加上标签,让代码携带更多信息。程序运行时,Java能读取这些标签(注解解析),按标签指示执行特别操作。结合AOP,自定义注解变身智能开关,自动执行如日志记录等任务。

  • 日志管理:用LogExecution等注解轻松标注方法,自动收集日志,涵盖执行时间、参数、结果和异常,提升日志管理的一致性和效率。
  • 微服务管理:在微服务中,特定注解帮助识别关键服务部分,集中监控、管理事务和缓存,增强服务性能和运维便利性。
  • 规范与安全强化:自定义注解不仅提升代码质量,还能确保遵循规范。在安全上,结合AOP,注解成为强效工具,强制执行权限检查和数据保护措施,防堵安全漏洞。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 注解定义
  • 2. 注解解析
  • 3. 自定义注解结合AOP切面
  • 4. 自定义注解用于日志打印
  • 5. 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档