Purpblue原创文章,欢迎转载,但请保留本文的csdn链接。
利用业余时间写了一个注解处理器,用于统一spring中的bean注入与值注入,简化代码。在bean注入时,只需要一个注解@Pbwired即可完成bean的构造函数注入或setter注入而无需编写构造函数或setter方法。同样在值注入时,只需一个注解@Pbvalue即可完成静态或非静态变量的注入,而无需专门为静态变量编写setter方法(非静态变量的话本身就不需要)。
本篇文章主要是介绍相关注解处理器(APT)实现原理。
之前已经写了文章介绍用法,传送门:
基于注解处理器(APT)的Spring注入统一与简化(一)--@Pbwired
基于注解处理器(APT)的Spring注入统一与简化(二)--@Pbvalue
源码地址:GitHub - wbzdwjsm/pbwired: Simplify your injection in Spring, even constant injection.
再贴一下引入及使用(以pom.xml为例,请记得在IDE中打开允许注解处理器的设置):
<dependency>
<groupId>com.purpblue</groupId>
<artifactId>pbwired</artifactId>
<version>1.0.4</version>
<scope>provided</scope>
</dependency>
一、@Pbwired的实现原理
前面的文章已经介绍,@Pbwired的注入方式有两种:setter注入和构造函数注入(默认)。所以注解处理器中分别处理了两种情况。
对于setter注入,简单一些,就是通过APT构造setter方法,加上@Autowired修饰方法即可,如果@Pbwired指定了beanName(像这样:@Pbwired(wireType = WireType.SETTER, name = "testService")),则在setter方法的形参中加入@Qualifier修饰。
至于构造函数注入,分两种情况:开发者主动编写了由@Autowired修饰的构造函数,或者是开发者没有显式编写任何构造函数。对于前一种,会将@Pbwired修饰的变量直接加入@Autowired修饰的构造函数;后一种情况还要麻烦些:需要利用编译器自动添加的无参构造函数,为其添加@Autowired注解,再将需要注入的变量放入其中。
核心逻辑在PbwiredProcessor的processPbwired方法中,代码如下(构造函数处理部分略长,请谅解)。相关逻辑和设计思想我已在代码中作了注释。
private void processPbwired(RoundEnvironment roundEnv) {
Set<? extends Element> pbwiredElements = roundEnv.getElementsAnnotatedWith(Pbwired.class);
//用这个Map来标记当前类是否有了@Autowired注解的构造函数。1表示有,0则表示没有,需要将系统默认的无参构造函数处理成@Autowired修饰
Map<String, Integer> alreadyInit = new HashMap<>();
//循环处理每一个被@Pbwired修饰的元素(@Pbwired只支持修饰FIELD)
for(Element e : pbwiredElements) {
JCTree tree = javacTrees.getTree(e);
//如果该field同时被@Autowired/@Resource修饰,则忽略@Pbwired (1)
Resource r = e.getAnnotation(Resource.class);
if(r != null) {
continue;
}
Autowired a = e.getAnnotation(Autowired.class);
if(a != null) {
continue;
}
//获取该field的@Pbwired注解并获得beanName
Pbwired p = e.getAnnotation(Pbwired.class);
String pName = p.name();
tree.accept(new TreeTranslator() {
@Override
public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
Symbol owner = jcVariableDecl.sym.owner;
//获得该field的宿主类
JCTree.JCClassDecl classDecl = (JCTree.JCClassDecl) javacTrees.getTree(owner);
//初始化“构造函数”的状态,暂时为“没有@Autowired修饰的构造函数”(用0表示),如果后面发现了,会修改这个值为1(用常量AUTOWIRED_CTOR表示)(2)
alreadyInit.putIfAbsent(classDecl.sym.fullname.toString(), 0);
switch (p.wireType()) {
//setter注入,也就是@Pbwired(wireType = WireType.SETTER)这种(3)
case SETTER:
//准备@Autowired,用于放在setter方法上
JCTree.JCAnnotation jcAnnotation = treeMaker.Annotation(classPath(AUTOWIRED_PATH), List.nil());
List<JCTree.JCAnnotation> annotations = List.of(jcAnnotation);
List<JCTree.JCTypeParameter> typeParameters = List.nil();
//创建setter方法中的参数,如果在@Pbwired中指定了非空的"name"属性,则这里会一并准备到@Qualifier注解中
JCTree.JCVariableDecl p0 = treeMaker.VarDef(
makeParamModifiers(p), //准备此参数的修饰符
jcVariableDecl.name, //参数名与field一致
jcVariableDecl.vartype, //参数的类型与field类型一致
null
);
p0.pos = classDecl.pos;
//方法形参
List<JCTree.JCVariableDecl> parameters = List.of(p0);
List<JCTree.JCExpression> throwList = List.nil();
//创建setter方法体,也就是this.xx = xx这种结构(4)
ListBuffer<JCTree.JCStatement> body = new ListBuffer<>();
//常量AUTOWIRED_PATH = "org.springframework.beans.factory.annotation.Autowired"
body.append(
treeMaker.Exec(treeMaker.Assign(
treeMaker.Select(treeMaker.Ident(names.fromString(STRING_THIS)), p0.getName()),
treeMaker.Ident(p0.getName())))
);
JCTree.JCBlock bodyBlock = treeMaker.Block(0, body.toList());
//创建setter方法
JCTree.JCMethodDecl setter0 = treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC, annotations),
names.fromString(generateMethodName(jcVariableDecl.name.toString())),
treeMaker.Type(new Type.JCVoidType()),
typeParameters,
parameters,
throwList,
bodyBlock,
null
);
//将setter方法加入到类的定义体中(5)
classDecl.defs = classDecl.defs.append(setter0);
break;
case CONSTRUCTOR:
//构造函数注入,也就是直接用@Pbwired这种不指定注入方式的情况(默认就是构造函数注入)(6)
for(JCTree t : classDecl.defs) {
//classDecl.defs包含了variables和methods,通过比较Kind找到构造函数 (7)
if (t.getKind().equals(Tree.Kind.METHOD)) {
JCTree.JCMethodDecl m = (JCTree.JCMethodDecl) t;
//构造函数的名字为“<init>”,STRING_CTOR为定义的常量,值正是“<init>”
if (STRING_CTOR.equals(m.getName().toString())) {
//遍历构造函数的注解,寻找@Autowired。(8)
//若是找到,直接在此构造函数中追加注入代码,若没有找到,则利用原始的无参构造函数来打造(9)
for (JCTree.JCAnnotation ann : m.getModifiers().annotations) {
//已寻找到构造函数的@Autowired注解
if (alreadyInit.get(classDecl.sym.fullname.toString()) == AUTOWIRED_CTOR || STRING_AUTOWIRED.equals(ann.annotationType.toString())) {
//定义构造函数中对应当前变量的形参,如果之前在@Pbwired中指定了name属性,则这里一并转化成对应的@Qulifier注解
JCTree.JCVariableDecl var = treeMaker.VarDef(
makeParamModifiers(p),
jcVariableDecl.name,
jcVariableDecl.vartype,
null
);
var.pos = classDecl.pos;
//在这里构造函数优势之一也体现出来了:可以将待注入的变量设置为final以进一步保证变量安全
makeFinalIfPossible(jcVariableDecl);
m.params = m.params.append(var);
//将赋值的逻辑加入到构造函数中(也就是this.xx=xx)
m.body.stats = m.body.stats.append(
treeMaker.Exec(treeMaker.Assign(
treeMaker.Select(treeMaker.Ident(names.fromString(STRING_THIS)), var.name),
treeMaker.Ident(var.name)))
);
//到这里,已确定此类一定有@Autowired修饰的构造函数了,所以将此状态置为1(常量AUTOWIRED_CTOR=1)
alreadyInit.put(classDecl.sym.fullname.toString(), AUTOWIRED_CTOR);
}
}
//若是在上面for循环中找到了构造函数,但是没有在其中找到@Autowired注解,则利用无参构造函数
if (alreadyInit.get(classDecl.sym.fullname.toString()) == 0
&& m.params.size() == 0) {
//准备给构造函数的@Autowired注解
JCTree.JCAnnotation jcAutowird = treeMaker.Annotation(classPath(AUTOWIRED_PATH), List.nil());
m.mods.annotations = m.mods.annotations.append(jcAutowird);
//当前代码是因为检测到有field被@Pbwired注解而进入的,所以这里直接将此变量作为改造后的构造函数的第一个形参
JCTree.JCVariableDecl varDecl = treeMaker.VarDef(
makeParamModifiers(p),
jcVariableDecl.name,
jcVariableDecl.vartype,
null
);
varDecl.pos = classDecl.pos;
//将其设置为final并添加到形参列表
makeFinalIfPossible(jcVariableDecl);
m.params = List.of(varDecl);
//将此this.xx=xx的赋值语句加入
JCTree.JCExpressionStatement assign =
treeMaker.Exec(treeMaker.Assign(
treeMaker.Select(treeMaker.Ident(names.fromString(STRING_THIS)), jcVariableDecl.name),
treeMaker.Ident(jcVariableDecl.name)));
//将语句添加到构造函数的执行体中
m.body.stats = m.body.stats.append(assign);
//将当前类的“构造函数状态”置为“已有@Autowired修饰的构造函数”
alreadyInit.put(classDecl.sym.fullname.toString(), AUTOWIRED_CTOR);
}
}
}
}
break;
default:
break;
}
}
});
}
}
以上代码解释中,有些地方有编号,相关编号说明如下:
(1)因为最终实现就是依赖@Autowired,所以没必要再和@Autowired/@Resource“争宠”。@Pbwired只是帮忙“写”了一些格式化的代码,本身并不改变使用者的编码习惯。
(2)之所以使用putIfAbsent是因为要循环处理同一个类的field,一旦此类的状态某一次被修改成1(有@Autowired修饰的构造函数),自然再也不能回到0(无@Autowired修饰的构造函数)了。
(3)自然这里也可以提供bean名,通过name属性。
(4)如果变量有static修饰,则这里的代码会变成 “类名.xx=xx”(虽然这样做很奇怪)。
(5)(如果您对APT很熟悉,请忽略此解释)这里必须使用com.sun.tools.javac.util.List(不是接口),和我们平时用的java.util.List不一样。相比于LinkedList的“节点化”,此List内部甚至可以用“列表化”来形容。有兴趣的读者可以自行比较源码并结合编译期特点研究这样做的原因。
(6)当然也可以显式指定@Pbwired(wireType = WireType.CONSTRUCTOR),不过无此必要。
(7)构造函数也是方法,所以其Kind也是METHOD,名称为<init>。而类的静态初始化块(static{方法体})或对象的初始化块({方法体})Kind均为BLOCK。
(8)既可以是使用者主动在类中编写的,也可以是后面APT自动生成的,在这里处理时并无区别。
(9)如果编写者没在类中编写任何构造函数,则编译器会自动添加一个无参的(这个当然了),但如果编写者在类中编写了既没有@Autowred注解,也不是无参的构造函数,同时又使用@Pbwired作构造函数注入,这里就会不起作用,导致最终注入失败。不过,似乎无此必要这么做?如果您觉得有必要考虑此种情况,请告诉我,后续迭代时或许可以作兼容。
二、@Pbvalue的实现原理
这个相比之下要简单得多:因为只需要生成setter方法,而且是一个变量独享一个setter,简直不要太舒爽!只需要看一下代码注释,相信您已然心中有数。
private void processPbvalue(RoundEnvironment roundEnv) {
Set<? extends Element> pbvalueElements = roundEnv.getElementsAnnotatedWith(Pbvalue.class);
for(Element e : pbvalueElements) {
JCTree tree = javacTrees.getTree(e);
//若同时存在原始的@Value,则直接忽略@Pbvalue
Value v = e.getAnnotation(Value.class);
if(v != null) {
continue;
}
Pbvalue pbvalue = e.getAnnotation(Pbvalue.class);
tree.accept(new TreeTranslator() {
@Override
public void visitVarDef(JCTree.JCVariableDecl variableDecl) {
super.visitVarDef(variableDecl);
//当前变量所在的类
Symbol owner = variableDecl.sym.owner;
JCTree.JCClassDecl ownClass = (JCTree.JCClassDecl) javacTrees.getTree(owner);
//将@Pbvalue中指定的value值转移到@Value注解的value值中
JCTree.JCExpression jc0 = treeMaker.Assign(treeMaker.Ident(names.fromString(STRING_VALUE)), treeMaker.Literal(pbvalue.value()));
List<JCTree.JCExpression> params = List.of(jc0);
//构建@Value注解,并将前面由@Pbvalue获得的值放入,即完成构建“@Value("${xx.yy}")
JCTree.JCAnnotation valued = treeMaker.Annotation(classPath(VALUE_PATH), params);
List<JCTree.JCAnnotation> annos = List.of(valued);
//创建setter方法形参
JCTree.JCVariableDecl param = treeMaker.VarDef(
treeMaker.Modifiers(Flags.PARAMETER),
variableDecl.name,
variableDecl.vartype,
null
);
param.pos = ownClass.pos;
List<JCTree.JCVariableDecl> paraList = List.of(param);
//setter方法体,对非静态,是“this.xx=xx”,静态,则是“类名.xx=xx”
ListBuffer<JCTree.JCStatement> mbody = new ListBuffer<>();
mbody.append(treeMaker.Exec(
treeMaker.Assign(
treeMaker.Select(treeMaker.Ident(names.fromString(STRING_THIS)), variableDecl.name),
treeMaker.Ident(variableDecl.name)
)
));
JCTree.JCBlock methodBody = treeMaker.Block(0, mbody.toList());
//创建setter方法
JCTree.JCMethodDecl setter = treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC, annos),
names.fromString(generateMethodName(variableDecl.name.toString())),
treeMaker.Type(new Type.JCVoidType()),
List.nil(),
paraList,
List.nil(),
methodBody,
null
);
//将方法加到类定义中
ownClass.defs = ownClass.defs.append(setter);
}
});
}
}
好了,@Pbwired/@Pbvalue核心原理已经介绍完毕,完整的代码请自行查看github。如果能给您带来一点点收获,我将不胜欣喜,如果您能帮忙测试一下,更加不胜感激!