前情提要:
目的是要在别的项目里调用编写好的记录日志jar包:
一开始想的操作是使用openFeign去远程调用日志项目的接口的。这样项目只要导入我们编写的Client包,注入即可使用了,如下图:
package com.hmall.api.client;
import com.hmall.api.config.DefaultFeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Collection;
// 参数中的url为远程调用地址
@FeignClient(url = "http://192.168.0.23:8080",configuration = DefaultFeignConfig.class)
public interface CartClient {
@DeleteMapping("/carts")
void deleteCartItembyIds(@RequestParam("ids") Collection<Long> ids);
}
// 使用
@Autowired
CartClient cartClient;
// 调用
cartClient.deleteCartItembyIds(itemIds);
然后想试下能不能实现类似下面这样去处理代码逻辑,也就是类似Lombok只要添加@Slf4j,不用注入也能直接使用Logger类实例log:
package com.swp.ch.po;
import com.swp.ch.annotation.Hello;
// 自定义的注解
@Hello
public class MyTest {
public void record(){
// 假设log是要记录的日志
String log;
// 自动注入类的实例,类似于@Slf4j的log.info(String msg);
myWebLog.record(log);
}
}
很神奇,于是搜了很久,了解到了Lombok是用注解处理器进行操作的,遂继续深入:
如何自定义注解处理器,并在里面创建新类添加静态方法,像这样调用"MyLog.record(String log) "来记录日志执行相关操作,这个成功了。然后试下能不能改成创建interface,但是interface不能创建静态方法,并且也要使用openFeign调用远程接口,嘻嘻完蛋了(悲)。
分享下如何通过在已有类上添加自定义注解为其创建新类或者接口:
这里附上大佬博客,那时候找资料刷到的:Java注解(三):自定义Java编译时注解处理器_自定义注解处理器-优快云博客https://blog.youkuaiyun.com/u014454538/article/details/122531293?spm=1001.2014.3001.5506
进入正文:
(1)定义注解:
package com.swp.xy.annotation;
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)
source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Hello {
}
(2)定义注解处理器,继承AbstractProcessor,重写其中方法(主要为process方法,该方法返回值为boolean类型,返回为true则不会再被别的注解处理器处理):
package com.swp.xy.processor;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.Set;
// 通过@SupportedAnnotationTypes注册注解处理器,与重写getSupportedAnnotationTypes()方法等价
@SupportedAnnotationTypes("com.swp.xy.annotation.Hello")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
@AutoService(Processor.class)
public class HelloProcessor extends AbstractProcessor {
// 用于消息传递和元素的处理的工具实例
private Messager messager;
private Elements elementUtils;
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 通过init()方法,完成初始化工作
this.messager = processingEnv.getMessager();
this.elementUtils = processingEnv.getElementUtils();
this.filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 一条简单的信息打印,用于辅助process方法是否被执行
messager.printMessage(Diagnostic.Kind.NOTE, "source version -- " + getSupportedSourceVersion());
// 遍历注解处理器可以处理的注解,获得被注解说明的元素
for (TypeElement annotation : annotations) {
String annotationName = annotation.getSimpleName().toString();
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
// 该注解处理器只处理@Hello,而且@Hello的Target为Type,进一步要求为CLASS
if (element.getKind() != ElementKind.CLASS) {
messager.printMessage(Diagnostic.Kind.ERROR, "@" + annotationName + "must be used for a class");
}
// 获取被说明类的包名和类名,为创建对应的Hello类做准备
TypeElement classElement = (TypeElement) element;
String simpleName = classElement.getSimpleName().toString();
PackageElement packageElement = elementUtils.getPackageOf(classElement);
String packageName = packageElement.getQualifiedName().toString();
// 借助JavaPoet模板引擎,生成对应的Hello类
TypeSpec typeSpec = generateClassFile(simpleName, packageName);
// 生成对应的Java文件
JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate Java file for class ", element);
}
}
}
return roundEnv.processingOver();
}
private TypeSpec generateClassFile(String simpleName, String packageName) {
// 定义FieldSpec,存储类名
FieldSpec nameField = FieldSpec.builder(String.class, "className", Modifier.PRIVATE).build();
// 定义无参构造函数,自动完成类名的初始化
MethodSpec constructor = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addStatement("this.$N = $S", nameField, simpleName)
.build();
// 定义sayHello方法
String msg = "Hello, this is ";
MethodSpec sayHello = MethodSpec.methodBuilder("sayHello")
.addModifiers(Modifier.PUBLIC)
.addStatement("System.out.println($S + className)", msg)
.build();
// 创建类
ClassName helloClass = ClassName.get(packageName, simpleName + "Hello");
return TypeSpec.classBuilder(helloClass)
.addModifiers(Modifier.PUBLIC)
.addField(nameField)
.addMethod(constructor)
.addMethod(sayHello)
.build();
}
}
PS: 这里还使用了JavaPoet模板简化创建类的过程,TypeSpec是其独有的类。
// JavaPoet对应Maven依赖
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
(3)使用@AutoService注册,不注册的话会报错,提示"未找到该类":
(3.1)先引入对应auto-service依赖:
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.1.1</version>
</dependency>
(3.2)在自定义的注解处理器上加上@AutoService(Processor.class),be like:
@SupportedAnnotationTypes("com.swp.xy.annotation.Hello")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
// 这里
@AutoService(Processor.class)
// 上面那句
public class HelloProcessor extends AbstractProcessor {