本文主要介绍了 Java 中的自定义注解以及结合 AOP(面向切面编程)技术进行日志记录的方法。
注解是附在代码上的元数据(Metadata),给编译器或运行时提供额外指导信息。Java自带多种注解,也可自定义以适应特殊需求。
代码示例
创建一个名为LogExecution
的注解,它可以应用到方法上,并有两个可选参数:value
(用于存储自定义消息)和level
(用于指定日志级别)。
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
}
通过Java反射API(动态地获取和操作类、接口、字段、方法等信息)去利用自定义注解,下面的代码用于检查方法是否标有LogExecution
注解,如果有则在运行时执行日志记录。
代码示例
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;
// ... 其他级别处理 ...
}
}
}
}
Spring AOP能帮我们在程序中不改动原有代码的情况下,加上一些通用功能,比如记录日志。
假设有一个LogExecution
标记,我们可以用AOP技术,设置在这个方法执行前后自动做某些事情。在方法开始前和结束后记时,然后按照标记里设定的日志级别,比如是警告还是信息,打印一条包含执行时长的消息。这样就不用每个方法都手动去写这些记录时间、打日志的代码了。
代码示例
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;
// ... 其他级别处理 ...
}
}
}
结合前面的例子,我们可以进一步完善LogExecution
注解的功能,使其不仅能记录执行时间,还能按照注解中定义的消息格式打印日志。
1.创建自定义注解
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.定义服务接口和其实现类,并在方法上应用注解
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切面类处理日志打印
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类上应用自定义注解
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
,那么控制台将会看到类似于以下格式的日志:
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
自定义注解为程序元素加上标签,让代码携带更多信息。程序运行时,Java能读取这些标签(注解解析),按标签指示执行特别操作。结合AOP,自定义注解变身智能开关,自动执行如日志记录等任务。
LogExecution
等注解轻松标注方法,自动收集日志,涵盖执行时间、参数、结果和异常,提升日志管理的一致性和效率。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。