如何通过Java注解注入自定义Logger?适配IBM MXLogger无需手动声明
纯Java实现注解式隐式Logger注入
当然可以实现,下面提供两种无需Spring、Lombok的纯Java方案,满足你通过注解直接调用logger.debug(...)的需求:
一、运行时反射注入方案(简单易上手)
这种方案通过运行时反射为类注入Logger实例,配置和实现都很简单,适合快速落地。
1. 定义自定义注解
先创建一个运行时保留的注解,标记需要注入Logger的类:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface InjectLogger { // 可选参数:自定义Logger的key,默认使用当前类的全限定名 String value() default ""; }
2. 实现Logger注入工具类
这个工具类负责通过反射查找类中的logger字段,并注入MXLogger实例:
import java.lang.reflect.Field; public class LoggerInjector { // 为实例对象注入Logger public static void inject(Object target) { Class<?> clazz = target.getClass(); if (!clazz.isAnnotationPresent(InjectLogger.class)) { return; } InjectLogger anno = clazz.getAnnotation(InjectLogger.class); String loggerKey = anno.value().isEmpty() ? clazz.getName() : anno.value(); MXLogger logger = MXLogger.getLogger(loggerKey); try { // 查找类中名为"logger"的字段,忽略访问修饰符 Field loggerField = clazz.getDeclaredField("logger"); loggerField.setAccessible(true); loggerField.set(target, logger); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException("Logger注入失败:" + clazz.getName(), e); } } // 为静态字段注入Logger public static void inject(Class<?> clazz) { if (!clazz.isAnnotationPresent(InjectLogger.class)) { return; } InjectLogger anno = clazz.getAnnotation(InjectLogger.class); String loggerKey = anno.value().isEmpty() ? clazz.getName() : anno.value(); MXLogger logger = MXLogger.getLogger(loggerKey); try { Field loggerField = clazz.getDeclaredField("logger"); loggerField.setAccessible(true); // 静态字段注入时,target参数传null loggerField.set(null, logger); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException("静态Logger注入失败:" + clazz.getName(), e); } } }
3. 使用示例
实例Logger场景
在目标类中声明logger字段,添加注解后,在构造方法中调用注入:
@InjectLogger public class UserService { private MXLogger logger; // 必须声明该字段,名称固定为logger public UserService() { LoggerInjector.inject(this); } public void createUser() { logger.debug("开始创建用户"); // 业务逻辑... } }
静态Logger场景
如果使用静态Logger,在类的静态代码块中调用注入:
@InjectLogger("user-dao-logger") // 自定义Logger的key public class UserDao { private static MXLogger logger; static { LoggerInjector.inject(UserDao.class); } public void queryUser() { logger.debug("查询用户信息"); // DAO逻辑... } }
二、编译时注解处理器方案(性能更优)
如果对性能有要求,可以用编译时注解处理器在编译阶段自动生成Logger字段的代码,避免运行时反射开销。不过配置相对复杂一些:
1. 定义编译时注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) // 仅在编译阶段保留 public @interface InjectLogger { String value() default ""; }
2. 实现注解处理器
继承AbstractProcessor,处理InjectLogger注解并生成包含Logger字段的代码(这里以生成父类为例,避免修改原类):
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.Writer; import java.util.Set; @SupportedAnnotationTypes("com.yourpackage.InjectLogger") @SupportedSourceVersion(SourceVersion.RELEASE_8) // 根据你的Java版本调整 public class LoggerProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement anno : annotations) { roundEnv.getElementsAnnotatedWith(anno).forEach(element -> { TypeElement typeElement = (TypeElement) element; String className = typeElement.getSimpleName().toString(); String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString(); String loggerKey = typeElement.getAnnotation(InjectLogger.class).value(); if (loggerKey.isEmpty()) { loggerKey = packageName + "." + className; } // 生成包含Logger字段的抽象父类 String parentClassName = "Abstract" + className; try { JavaFileObject file = processingEnv.getFiler().createSourceFile(packageName + "." + parentClassName); try (Writer writer = file.openWriter()) { writer.write("package " + packageName + ";\n"); writer.write("\n"); writer.write("public abstract class " + parentClassName + " {\n"); writer.write(" protected MXLogger logger = MXLogger.getLogger(\"" + loggerKey + "\");\n"); writer.write("}\n"); } } catch (IOException e) { processingEnv.getMessager().printError("生成Logger父类失败:" + e.getMessage()); } }); } return true; } }
3. 使用方式
编译后会自动生成名为AbstractXXX的父类,你的业务类只需继承该父类并添加注解即可:
@InjectLogger public class OrderService extends AbstractOrderService { public void createOrder() { logger.debug("开始创建订单"); // 业务逻辑... } }
注意事项
- 运行时方案中,必须确保类中存在名为
logger的字段,否则会抛出异常。 - 编译时方案需要配置注解处理器到项目构建路径中(比如Maven的
annotationProcessorPaths),具体配置可根据你的构建工具调整。 - 如果不想手动调用注入方法,可以结合类加载器或代理实现自动注入,但会增加系统复杂度。
内容的提问来源于stack exchange,提问作者AhmedRana
相关产品推荐
相关产品推荐

