文章目录
- JSR-269 & 什么是插入式注解处理器
- 1、快速开始
- 2、语法树相关简介
- 2.1、JCTree
- 2.2、TreeMaker
- 2.2.1、TreeMaker.Modifiers
- 2.2.2、TreeMaker.ClassDef
- 2.2.3、TreeMaker.MethodDef
- 2.2.4、TreeMaker.VarDef
- 2.2.5、TreeMaker.Ident
- 2.2.6、TreeMake.Return
- 2.2.7、TreeMaker.Select
- 2.2.8、TreeMaker.NewClass
- 2.2.9、TreeMaker.Apply
- 2.2.10、TreeMaker.Assign
- 2.2.11、TreeMaker.Exec
- 2.2.12、TreeMaker.Block
- 2.3、com.sun.tools.javac.util.List
- 2.4、com.sun.tools.javac.util.ListBuffer
- 3.5、 tricky
- 3.5.1、创建一个构造方法
- 3.5.2、创建一个方法的参数
- 3.5.3、创建一条赋值语句
- 3.5.4、创建一条new语句
- 3.5.5、创建一条方法调用语句
- 3.3.6、从JCTree.JCVariable中获取类型信息
- 注意事项
JSR-269 & 什么是插入式注解处理器
在Javac源码中,插入式注解处理器的初始化过程是在initPorcessAnnotations() 方法中完成的,而它的执行过程则是在processAnnotations()方法中完成的,这个方法判断是否还有新的注解处理器需要执行,如果有的话,通过com.sun.tools.javac.processing.JavacProcessingEnvironment类的 **doProcessing()**方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理。
在JDK1.5之后,Java语言提供了对注解(Annotation)的支持,这些注解与普通的Java代码一样,是在运行期间发挥作用的。在**JDK1.6中实现了JSR-269规范,JSR-269:Pluggable Annotations Processing API(插入式注解处理API)。提供了一组插入式注解处理器的标准API在编译期间对注解进行处理。**我们可以把它看做是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,这是一个回环过程。
1、快速开始
需求,模拟lombok生成get/set方法。
实现步骤:
-
编写get/set处理器,该类需要继承AbstractProcessor
-
编写支持版本,使用注解**@SupportSourceVersion** 或重新方法getSupportedSourceVersion
-
处理支持的注解,使用注解**@SupportedAnnotationTypes** 或重写方法getSupportedAnnotationTypes
-
重写init方法
-
重写process方法
-
在根目录下创建文件META-INF/services/javax.annotation.processing.Processor可在resources目录下创建,其内容如下
xin.spring.java.processor.GetterProcessor xin.spring.java.processor.SetterProcessor
在此基础上需要提供两个注解:
package xin.spring.java.processor; 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 Getter { } package xin.spring.java.processor; 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 Setter { }
get编码实现:
package xin.spring.java.processor; import com.sun.source.tree.Tree; import com.sun.tools.javac.api.JavacTrees; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeTranslator; import com.sun.tools.javac.util.*; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import java.util.Set; //对Getter感兴趣 @SupportedAnnotationTypes("xin.spring.java.processor.Getter") //支持的版本,使用1.8就写这个 //@SupportedSourceVersion(SourceVersion.RELEASE_8) public class GetterProcessor extends AbstractProcessor { // 编译时期输入日志的 private Messager messager; // 将Element转换为JCTree的工具,提供了待处理的抽象语法树 private JavacTrees trees; // 封装了创建AST节点的一些方法 private TreeMaker treeMaker; // 提供了创建标识符的方法 private Names names; @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.messager = processingEnv.getMessager(); this.trees = JavacTrees.instance(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); this.treeMaker = TreeMaker.instance(context); this.names = Names.instance(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 获取被@Getter注解标记的所有元素(这个元素可能是类、变量、方法等等) Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class); set.forEach(element -> { // 将Element转换为JCTree JCTree jcTree = trees.getTree(element); jcTree.accept(new TreeTranslator() { /*** * JCTree.Visitor有很多方法,我们可以通过重写对应的方法,(从该方法的形参中)来获取到我们想要的信息: * 如: 重写visitClassDef方法, 获取到类的信息; * 重写visitMethodDef方法, 获取到方法的信息; * 重写visitVarDef方法, 获取到变量的信息; * @param jcClassDecl */ @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { //创建一个变量语法树节点的List List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil(); // 遍历defs,即是类定义的详细语句,包括字段、方法的定义等等 for (JCTree tree : jcClassDecl.defs) { if (tree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); } } // 对于变量进行生成方法的操作 jcVariableDeclList.forEach(jcVariableDecl -> { messager.printMessage(Diagnostic.Kind.NOTE, "get " + jcVariableDecl.getName() + " has been processed"); treeMaker.pos = jcVariableDecl.pos; //类里的前面追加生成的Getter方法 jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl)); }); super.visitClassDef(jcClassDecl); } }); }); //我们有修改过AST,所以返回true return true; } private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) { /*** * JCStatement:声明语法树节点,常见的子类如下 * JCBlock:语句块语法树节点 * JCReturn:return语句语法树节点 * JCClassDecl:类定义语法树节点 * JCVariableDecl:字段/变量定义语法树节点 * JCMethodDecl:方法定义语法树节点 * JCModifiers:访问标志语法树节点 * JCExpression:表达式语法树节点,常见的子类如下 * JCAssign:赋值语句语法树节点 * JCIdent:标识符语法树节点,可以是变量,类型,关键字等等 */ ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()))); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); return treeMaker.MethodDef( treeMaker.Modifiers(Flags.PUBLIC),//mods:访问标志 getNewMethodName(jcVariableDecl.getName()),//name:方法名 jcVariableDecl.vartype,//restype:返回类型 List.nil(),//typarams:泛型参数列表 List.nil(),//params:参数列表 List.nil(),//thrown:异常声明列表 body,//方法体 null); } private Name getNewMethodName(Name name) { String s = name.toString(); return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length())); } }
set编码实现:
package xin.spring.java.processor; import com.sun.source.tree.Tree; import com.sun.tools.javac.api.JavacTrees; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeTranslator; import com.sun.tools.javac.util.*; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import java.util.Set; @SupportedAnnotationTypes("xin.spring.java.processor.Setter") //@SupportedSourceVersion(SourceVersion.RELEASE_8) public class SetterProcessor extends AbstractProcessor { private Messager messager; private JavacTrees trees; private TreeMaker treeMaker; private Names names; @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.messager = processingEnv.getMessager(); this.trees = JavacTrees.instance(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); this.treeMaker = TreeMaker.instance(context); this.names = Names.instance(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Setter.class); set.forEach(element -> { JCTree jcTree = trees.getTree(element); jcTree.accept(new TreeTranslator() { @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil(); for (JCTree tree : jcClassDecl.defs) { if (tree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); } } jcVariableDeclList.forEach(jcVariableDecl -> { messager.printMessage(Diagnostic.Kind.NOTE, "set " + jcVariableDecl.getName() + " has been processed"); treeMaker.pos = jcVariableDecl.pos; jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl)); }); super.visitClassDef(jcClassDecl); } }); }); return true; } private JCTree.JCMethodDecl makeSetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) { ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); statements.append( treeMaker.Exec( treeMaker.Assign( treeMaker.Select( treeMaker.Ident(names.fromString("this")), names.fromString(jcVariableDecl.name.toString()) ), treeMaker.Ident(names.fromString(jcVariableDecl.name.toString())) ) ) ); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); // 生成入参 JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), jcVariableDecl.getName(),jcVariableDecl.vartype, null); List<JCTree.JCVariableDecl> paramList = List.of(param); return treeMaker.MethodDef( treeMaker.Modifiers(Flags.PUBLIC), // 方法限定值 setNewMethodName(jcVariableDecl.getName()), // 方法名 treeMaker.Type(new Type.JCVoidType()), // 返回类型 List.nil(), paramList, // 入参 List.nil(), body, null ); } private Name setNewMethodName(Name name) { String s = name.toString(); return names.fromString("set" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length())); } }
如上我们就编写好了一个插入式处理器,此插入式处理器用于生成get/set方法
2、语法树相关简介
2.1、JCTree
JCTree是语法树元素的基类,包含一个重要的字段pos,该字段用于指明当前语法树节点(JCTree)在语法树中的位置,因此我们不能直接用new关键字来创建语法树节点,及时创建了也没有意义,此外,结合访问者模式,将数据结构与数据的处理进行解耦,部分源码如下:
public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public int pos = -1; ... public abstract void accept(JCTree.Visitor visitor); ... }
JCTree的子类:
1、JCStatement:声明语法树节点,常见的子类如下:
- JCBlock:语句块语法树节点
- JCReturn:return语句语法树节点
- JCClassDecl:类定义语法树节点
- JCVariableDecl:字段/变量定义语法树节点
2、JCMethodDecl:方法定义语法树节点
3、JCModifiers:访问标志语法树节点
4、JCExpression:表达式语法树节点,常见子类如下:
- JCAssign:赋值语句语法树节点
- JCIdent:标识符语法树节点,可以是变量,类型,关键字等等
2.2、TreeMaker
TreeMaker用于创建一系列语法树节点,创建时会为创建出来的JCTree设置pos字段,所以必须用上下文相关的TreeMaker对象来创建语法树节点,而不能直接new语法树节点
源码可以参考:TreeMaker DOC
2.2.1、TreeMaker.Modifiers
TreeMaker.Modifiers方法用于创建访问标志语法树节点(JCModifiers),源码如下:
- flags:访问标志
- annotations:注解列表
public JCModifiers Modifiers(long flags) { return Modifiers(flags, List.< JCAnnotation >nil()); } public JCModifiers Modifiers(long flags, List<JCAnnotation> annotations) { JCModifiers tree = new JCModifiers(flags, annotations); boolean noFlags = (flags & (Flags.ModifierFlags | Flags.ANNOTATION)) == 0; tree.pos = (noFlags && annotations.isEmpty()) ? Position.NOPOS : pos; return tree; }
其中入参flags
可以用枚举类型com.sun.tools.javac.code.Flags
,且支持拼接(枚举值经过精心设计)
例子:treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC + Flags.FINAL);
2.2.2、TreeMaker.ClassDef
TreeMaker.ClassDef用于创建类定义语法树节点(JCClassDecl),源码如下:
- mods:访问标志
- name:类名
- typarams:泛型参数列表
- extending:父类
- implementing:接口列表
- defs:类定义的详细语句,包括字段,方法定义等等
public JCClassDecl ClassDef(JCModifiers mods, Name name, List<JCTypeParameter> typarams, JCExpression extending, List<JCExpression> implementing, List<JCTree> defs) { JCClassDecl tree = new JCClassDecl(mods, name, typarams, extending, implementing, defs, null); tree.pos = pos; return tree; }
2.2.3、TreeMaker.MethodDef
TreeMake.MethodDef用于创建语法定义语法树节点(JCMethodDecl),源码如下:
- mods:访问标志
- name:方法名
- restype:返回类型
- typarams:参数列表
- thrown:异常声明列表
- body:方法体
- defaultValue:默认方法(可能是interface中的那个default)
- m:方法符号
- mtype:方法类型。包含多种类型,泛型参数类型、方法参数类型、异常参数类型、返回参数类型
public JCMethodDecl MethodDef(JCModifiers mods, Name name, JCExpression restype, List<JCTypeParameter> typarams, List<JCVariableDecl> params, List<JCExpression> thrown, JCBlock body, JCExpression defaultValue) { JCMethodDecl tree = new JCMethodDecl(mods, name, restype, typarams, params, thrown, body, defaultValue, null); tree.pos = pos; return tree; } public JCMethodDecl MethodDef(MethodSymbol m, Type mtype, JCBlock body) { return (JCMethodDecl) new JCMethodDecl( Modifiers(m.flags(), Annotations(m.getAnnotationMirrors())), m.name, Type(mtype.getReturnType()), TypeParams(mtype.getTypeArguments()), Params(mtype.getParameterTypes(), m), Types(mtype.getThrownTypes()), body, null, m).setPos(pos).setType(mtype); }
其中,返回类型填null
或者treeMaker.TypeIdent(TypeTag.VOID)
都代表返回void类型
2.2.4、TreeMaker.VarDef
TreeMaker.VarDef用于创建字段/变量定义语法树节点(JCVariableDecl),源码如下:
- mods:访问标志
- vartype:类型
- init:初始化语句
- v:变量符号
public JCVariableDecl VarDef(JCModifiers mods, Name name, JCExpression vartype, JCExpression init) { JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null); tree.pos = pos; return tree; } public JCVariableDecl VarDef(VarSymbol v, JCExpression init) { return (JCVariableDecl) new JCVariableDecl( Modifiers(v.flags(), Annotations(v.getAnnotationMirrors())), v.name, Type(v.type), init, v).setPos(pos).setType(v.type); }
2.2.5、TreeMaker.Ident
TreeMaker.Ident用于创建标识符语法树节点(JCIdent),源码如下:
public JCIdent Ident(Name name) { JCIdent tree = new JCIdent(name, null); tree.pos = pos; return tree; } public JCIdent Ident(Symbol sym) { return (JCIdent)new JCIdent((sym.name != names.empty) ? sym.name : sym.flatName(), sym) .setPos(pos) .setType(sym.type); } public JCExpression Ident(JCVariableDecl param) { return Ident(param.sym); }
2.2.6、TreeMake.Return
TreeMaker.Return用于创建return语句语法树节点(JCReturn),源码如下:
public JCReturn Return(JCExpression expr) { JCReturn tree = new JCReturn(expr); tree.pos = pos; return tree; }
2.2.7、TreeMaker.Select
TreeMaker.Select用于创建域访问/方法访问(这里的方法访问只是取到名字,方法的调用需要调用TreeMaker.Apply)语法树节点(JCFieldAccess),源码如下:
- selected: **.**运算符左边的表达式
- selector: **.**运算符右边的名字
public JCFieldAccess Select(JCExpression selected, Name selector) { JCFieldAccess tree = new JCFieldAccess(selected, selector, null); tree.pos = pos; return tree; } public JCExpression Select(JCExpression base, Symbol sym) { return new JCFieldAccess(base, sym.name, sym).setPos(pos).setType(sym.type); }
2.2.8、TreeMaker.NewClass
TreeMaker.NewClass用于创建new语句语法树节点(JCNewClass),源码如下:
- encl:不太明白此参数含义
- typeargs:参数类型列表
- clazz:待创建对象的类型
- args:参数列表
- def:类定义
public JCNewClass NewClass(JCExpression encl, List<JCExpression> typeargs, JCExpression clazz, List<JCExpression> args, JCClassDecl def) { JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def); tree.pos = pos; return tree; }
2.2.9、TreeMaker.Apply
TreeMaker.Apply用于创建方法调用语法树节点(JCMethodInvocation),源码如下:
- typeargs:参数类型列表
- fn:调用语句
- args:参数列表
public JCMethodInvocation Apply(List<JCExpression> typeargs, JCExpression fn, List<JCExpression> args) { JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args); tree.pos = pos; return tree; }
2.2.10、TreeMaker.Assign
TreeMaker.Assign用于创建赋值语句语法树节点(JCAssign),源码如下:
- lhs:赋值语句左表达式
- rhs:赋值语句右表达式
public JCAssign Assign(JCExpression lhs, JCExpression rhs) { JCAssign tree = new JCAssign(lhs, rhs); tree.pos = pos; return tree; }
2.2.11、TreeMaker.Exec
TreeMaker.Exec用于创建可执行语句语法树节点(JCExpressionStatement),源码如下:
public JCExpressionStatement Exec(JCExpression expr) { JCExpressionStatement tree = new JCExpressionStatement(expr); tree.pos = pos; return tree; }
例如,TreeMaker.Apply以及TreeMaker.Assign就需要外面包一层TreeMaker.Exec来获得一个JCExpressionStatement
2.2.12、TreeMaker.Block
TreeMaker.Block用于创建组合语句语法树节点(JCBlock),源码如下:
- flags:访问标志
- stats:语句列表
public JCBlock Block(long flags, List<JCStatement> stats) { JCBlock tree = new JCBlock(flags, stats); tree.pos = pos; return tree; }
2.3、com.sun.tools.javac.util.List
上述JSR-269API中会涉及到一个List,这个List不是java.util.List,它是com.sun.tools.javac.util.List,这个List的操作比较奇特,不支持链式操作。下面给出部分源码,List包含两个字段,head和tail,其中head只是一个节点,而tail是一个List
public class List<A> extends AbstractCollection<A> implements java.util.List<A> { public A head; public List<A> tail; private static final List<?> EMPTY_LIST = new List<Object>((Object)null, (List)null) { public List<Object> setTail(List<Object> var1) { throw new UnsupportedOperationException(); } public boolean isEmpty() { return true; } }; List(A head, List<A> tail) { this.tail = tail; this.head = head; } public static <A> List<A> nil() { return EMPTY_LIST; } public List<A> prepend(A var1) { return new List(var1, this); } public List<A> append(A var1) { return of(var1).prependList(this); } public static <A> List<A> of(A var0) { return new List(var0, nil()); } public static <A> List<A> of(A var0, A var1) { return new List(var0, of(var1)); } public static <A> List<A> of(A var0, A var1, A var2) { return new List(var0, of(var1, var2)); } public static <A> List<A> of(A var0, A var1, A var2, A... var3) { return new List(var0, new List(var1, new List(var2, from(var3)))); } ... }
2.4、com.sun.tools.javac.util.ListBuffer
由于com.sun.tools.javac.util.ListBuffer用起来不是很方便,而ListBuffer的行为与java.util.List的行为类似,并且提供了转换成com.sun.tools.javac.util.List的方法
ListBuffer<JCTree.JCStatement> jcStatements = new ListBuffer<>(); //添加语句 " this.xxx = xxx; " jcStatements.append(...); //添加Builder模式中的返回语句 " return this; " jcStatements.append(...); List<JCTree.JCStatement> lst = jcStatements.toList();
3.5、 tricky
3.5.1、创建一个构造方法
注意点:方法的名字就是
treeMaker.MethodDef( treeMaker.Modifiers(Flags.PUBLIC), //访问标志 names.fromString("<init>"), //名字 treeMaker.TypeIdent(TypeTag.VOID), //返回类型 List.nil(), //泛型形参列表 List.nil(), //参数列表 List.nil(), //异常列表 jcBlock, //方法体 null //默认方法(可能是interface中的那个default) );
3.5.2、创建一个方法的参数
注意点:访问标志设置成:Flags.PARAMETER
treeMaker.VarDef( treeMaker.Modifiers(Flags.PARAMETER), //访问标志。极其坑爹!!! prototypeJCVariable.name, //名字 prototypeJCVariable.vartype, //类型 null //初始化语句 );
3.5.3、创建一条赋值语句
treeMaker.Exec( treeMaker.Assign( treeMaker.Select( treeMaker.Ident(names.fromString(THIS)), jcVariable.name ), treeMaker.Ident(jcVariable.name) ) )
3.5.4、创建一条new语句
treeMaker.NewClass( null, //尚不清楚含义 List.nil(), //泛型参数列表 treeMaker.Ident(builderClassName), //创建的类名 List.nil(), //参数列表 null //类定义,估计是用于创建匿名内部类 )
3.5.5、创建一条方法调用语句
treeMaker.Exec( treeMaker.Apply( List.nil(), treeMaker.Select( treeMaker.Ident(getNameFromString(IDENTIFIER_DATA)), jcMethodDecl.getName() ), List.of(treeMaker.Ident(jcVariableDecl.getName())) //传入的参数集合 ) )
3.3.6、从JCTree.JCVariable中获取类型信息
注意,直接拿vartype字段,而不是type字段或者**getType()**方法
- vartype的类型是JCTree.JCExpression
- type的类型是com.sun.tools.javac.code.Type,这是个非标准api,理应不该使用
注意事项
【tip】:如果是maven项目需要在pom文件中配置如下内容:
目的:避免注解处理器编译出错问题
<build>
<!-- 配置一下resources标签,过滤掉META-INF文件夹,这样在编译的时候就不会找到services的配置 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>META-INF/**/*</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${maven.compiler.target}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- 在打包前(prepare-package生命周期)再把services文件夹重新拷贝过来 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>process-META</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/classes</outputDirectory>
<resources>
<resource>
<directory>${basedir}/src/main/resources/</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Source attach plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
**注:**部分代码和灵感来源
Java-JSR-269-插入式注解处理器