当前位置:   article > 正文

【JAVA学习】注解学习_java source注解

java source注解

简介

JAVA开发者肯定都用过注解,但是大部分可能跟我一样,只是用到runtime的场景,这段时间了解了一下另外两种场景,简单总结一下。

注解开发

开发注解最常见的有两个元注解:@Target和@Retention
@Target用于说明该注解可以应用到哪些项上

元素类型元素适用场合
ANNOTATION_TYPE注解类型声明
PACKAGE
TYPE类(包括enum)及接口(包括注解类型)
METHOD方法
CONSTRUCTOR构造器
FIELD成员域(包括enum常量)
PARAMETER方法或构造器参数
LOCAL_VARIABLE局部变量

一条没有@Target限制的注解可以应用于任何项上。
@Retention元注解用于指定一条注解应该保留多长时间。

保留规则描述
SOURCE不包括在类文件中的注解
CLASS包括在类文件中的注解,但是虚拟机不需要将它们载入
RUNTIME包括在类文件中的注解,并由虚拟机载入。可通过反射获取

@Documented元注解为像Javadoc这样的归档工具提供了一些提示,归档注解应该按照其他一些像protected或static这样用于归档目的的修饰符来处理。

@Inherited元注解只能应用于对类的注解。如果一个雷具有继承注解,那么它的所有子类都自动具有同样的注解。

SOURCE类型的注解处理器

从JAVA SE6开始,可以将注解处理器添加到JAVA编译器中。为了调用注解处理器,需要运行:

javac -processor processor1,processor2 sourcefiles
  • 1

编译器会定位源代码中的注解,然后选择可以应用的注解处理器。每个注解处理器会依次执行。如果某个注解处理器创建了一个新的源文件,那么将重复执行这个处理过程。如果某次处理循环没有再产生任何新的源文件,那么就编译所有的源文件。划重点:SOURCE类型的注解处理器需要通过产生新的源文件来进行处理。
注解处理器通常通过扩展AbStractProcessor类来实现Processor接口,使用时需要指定你的处理器支持哪些注解。
做个试验,上代码,简单说明一下使用。

注解处理器最好单独打成一个jar,便于使用。当然,不这样也行,命令行稍微麻烦些而已。

@SupportedAnnotationTypes("注解")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class FirstProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try {
            String beanClassName = null;
            for (TypeElement t : annotations) {
                Map<String[], Set<Modifier>> fields = new LinkedHashMap();
                System.out.println(t.getQualifiedName());
                for (Element e : roundEnv.getElementsAnnotatedWith(t)) {
                    String name = e.getSimpleName().toString();
                    beanClassName = e.getEnclosingElement().toString() + "." + e.getSimpleName().toString();
                    System.out.println(name + " : " + e.getKind().toString());
                    System.out.println(name + " : " + e.getEnclosingElement().toString());
                    System.out.println("----------------");
                    for (Element x : e.getEnclosedElements()) {
                        System.out.println(name + " : " + x.getKind().toString());
                        System.out.println(name + " : " + x.getSimpleName().toString());
                        System.out.println(name + " : " + x.asType());
                        x.getModifiers().stream().forEach(System.out::println);
                        if (x.getKind().isField()) {
                            fields.put(new String[]{x.getSimpleName().toString(), x.asType().toString()},
                                    x.getModifiers());
                        }
                    }
                }
                // 模拟生成源文件
                writeBeanInfoFile(beanClassName, fields);
            }
        } catch (Exception classNotFoundException) {
            classNotFoundException.printStackTrace();
        }
        return false;
    }

    private void writeBeanInfoFile(String beanClassName, Map<String[], Set<Modifier>> fields) throws IOException {
        JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(beanClassName + "Info");
        PrintWriter out = new PrintWriter(sourceFile.openWriter());
        out.println("//test");
        out.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

maven工程使用注解处理器:

		<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <generatedSourcesDirectory>
                        ${project.build.directory}/generated-sources/
                    </generatedSourcesDirectory>
                    <annotationProcessors>
                        <annotationProcessor>
                            xx.processor 
                        </annotationProcessor>
                    </annotationProcessors>
                </configuration>
            </plugin>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

这样在编译的时候,就可以直接触发processor进行处理了。

CLASS类型注解处理器

像前面介绍的,注解信息会保留在类文件中,但是不会被虚拟机加载,也就是反射拿不到。
用javap看下class文件:

RuntimeInvisibleAnnotations:xxx
  • 1

从这个描述或者限制看,这种类型只能用于字节码处理层面的,找了相关资料,介绍是类似的:bytecode post-processing.
而字节码是在编译后生成,在虚拟机加载后运行,那可能用到该类型注解处理的地方就在于编译后改写class文件、或虚拟机加载时改写字节码。

JAVAAGENT字节码工程(CLASS注解处理器实现方式之一)

使用javaagent机制可以在运行时改变类文件:

java -javaagent:agent.jar=参数 -cp xx.jar test
  • 1

实验用的字节码工具是apache的bcel,为bean文件增加get方法。
agent:实现premain方法:

public class PropertyAgent {
    public static void premain(String arg, Instrumentation instr) {
        instr.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className,
                                    Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain,
                                    byte[] classfileBuffer) throws IllegalClassFormatException {
                 // 修改命令行参数指定的类
                if (!className.replace("/", ".").equals(arg)) {
                    return null;
                }
                System.out.println("begin transform: " + classfileBuffer.length);
                try {
                    System.out.println("begin parse");
                    ClassParser parser = new ClassParser(new ByteArrayInputStream(classfileBuffer), className);
                    JavaClass jc = parser.parse();
                    System.out.println("begin cg");
                    ClassGen cg = new ClassGen(jc);
                    PropertyProcessor processor = new PropertyProcessor(cg);
                    System.out.println("begin convert");
                    processor.convert();
                    System.out.println("end transform");
                    return cg.getJavaClass().getBytes();
                } catch (Exception e) {
                    System.out.println("transform failed: " + e.getMessage());
                    e.printStackTrace();
                    return null;
                }
            }
        });
    }
}

public class PropertyProcessor {
    private ClassGen cg;
    private ConstantPoolGen cpg;

    public PropertyProcessor(ClassGen cg) {
        this.cg = cg;
        cpg = cg.getConstantPool();
    }

    public void convert() throws IOException {
        for (Field f : cg.getFields()) {
            cg.addMethod(insertGetMethod(f));
        }
    }
	
	// 修改bean文件,增加get函数
    private Method insertGetMethod(Field field) {
        String className = cg.getClassName();
        String methodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
        int accessFlags = 1; // public
        InstructionList patch = new InstructionList();
        InstructionFactory factory = new InstructionFactory(cg);
        MethodGen mg = new MethodGen(accessFlags, field.getType(), new Type[0], new String[0],
                methodName, className, patch, cpg);

        patch.append(InstructionConstants.ALOAD_0);
        patch.append(factory.createFieldAccess(className, field.getName(), field.getType(), Constants.GETFIELD));
        patch.append(InstructionFactory.createReturn(field.getType()));
        mg.setMaxStack();
        mg.setMaxLocals();

        return mg.getMethod();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

打包时指定premainclass(手动的情况需要在MF文件中增加对应行), 同时建议依赖都打到一个包中,也可以不这样做,只是命令行会比较麻烦。

<plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                            <manifestEntries>
                                <Premain-Class>xx.PropertyAgent</Premain-Class>
                                <Can-Redefine-Classes>false</Can-Redefine-Classes>
                                <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

总结

一路看下来:
1 SOURCE在编译期处理,需要生成新文件,个人感觉使用场景比较受限,毕竟如果是模板类型的,那解决方法很多;很复杂的场景,是不是直接代码实现更合适?那除非是工具型的,就是这些文件必须,但是可以减少开发者的代码开发工作量,或者隐藏无需开发者关注的细节。
2 CLASS类型的,感觉比SOURCE灵活些,毕竟可以在class文件中读取注解信息,只要在加载前改写掉,就可以达到目的。
说到这,Lombok是怎么实现的?疑问很多,使用Lombok时,编译不会出错,说明肯定在编译期而不是运行期做了工作;但是大家在使用时又没有单独指定processor,说明使用的不是processor机制。只能学习一下了,单独开一篇记录下Lombok实现原理。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/924924
推荐阅读
相关标签
  

闽ICP备14008679号