使用AnnotationProcessor自动生成代码

xiaoxiao2025-06-24  10

在上一篇实现类spring框架的文章中,android基于注解实现类似spring服务端框架使用了注解实现了与web服务本身的解耦,但实现的方式是使用反射得到method对象然后在请求到来时实例化对象然后用反射调用对应的函数,这种方法有一定的性能的损耗,使用上不够极致,这里再用注解处理器的方法实现对注解自动生成代码,实现一样的功能. 注解处理器的基础就不多介绍了,这里直接说说如何实现.

第一步:将注解放在一个library工程

因为注解公是定义,所以只要是一般的java library工程即可,其中一个定义

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Controller { String name() default "/"; boolean needPermissonControl() default true; }

第二步:实现注解处理工程

这个工程就是用来自动生成代码的,这个工程必须为java library工程,因为注解处理器相关类并不在android的类库里,对应的包javax.annotation.processing只有一般java工程才能引用,不过一定要用android library也不是不可以,只是要把相应的类引进来才能正常编译,关于如何引入javax.annotation.processing比较麻烦,github有相关的代码,这里不多做介绍. 建好后引入必要的依赖,看注解工程的build.gradle

// java工程 apply plugin: 'java' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) compileOnly fileTree(dir: '../app/libs', includes: ['*.jar']) // google的注解生成器的封装,也可以不用,但自己实现起来比较麻烦 compileOnly 'com.google.auto.service:auto-service:1.0-rc3' // 生成java代码需要用到 compileOnly 'com.squareup:javapoet:1.9.0' compile project(path: ':Annotations') } sourceCompatibility = "1.7" targetCompatibility = "1.7"

接着定义注解处理类

// 指定这个类是注解处理类,用了这个注解编译时就知道要用这个类来处理注解 @AutoService(Processor.class) public class ControllerProcessor extends AbstractProcessor { // 输出java文件用 private Filer mFiler; // 打印LOG用 private Messager mMessager; // 获取类成员等用 private Elements mElementUtils; // 初始化拿到注解处理相关实现用类,打Log类,输出java代码类 @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mFiler = processingEnvironment.getFiler(); mMessager = processingEnvironment.getMessager(); mElementUtils = processingEnvironment.getElementUtils(); } // 支持的注解类 @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotations = new LinkedHashSet<>(); annotations.add(Controller.class.getCanonicalName()); // annotations.add(RequestMapping.class.getCanonicalName()); return annotations; } // 支持的java版本 @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } // 在编译时会调到这里来,这个函数会被调用多次,注意不要出现重复生成的错误,调用多次的原因应该是会对生成后的代码再扫描一遍 @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) { } }

注解处理类的关键的方法就是上面的几个,其中process方法会在编译扫描源代码的时候会执行进来,处理注解相关的逻辑就在这里实现.这里我定义了一些操作类用来方便生成代码,process函数实现:

@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) { try { // 循环遍历有Controller注解的类 for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Controller.class)) { // Check if a class has been annotated with @Factory if (annotatedElement.getKind() != ElementKind.CLASS) { throw new ProcessingException(annotatedElement, "Only classes can be annotated with @%s", Controller.class.getSimpleName()); } // We can cast it, because we know that it of ElementKind.CLASS TypeElement typeElement = (TypeElement) annotatedElement; ControllerAnnotatedClass annotatedClass = new ControllerAnnotatedClass(typeElement, mMessager); note("process " + annotatedClass.getSimpleName()); // 扫描到Controller类,加入list classes.add(annotatedClass); } // 将扫描到有Controller注解的类生成代码 classes.generateCode(roundEnv, mElementUtils, mFiler, mMessager); classes.clear(); return true; } catch (ProcessingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; }

这里介绍讲解一下Element这个类,编译找描代码的过程中,每个类或者函数或者变量都是Element,如下所示:

package com.example; // PackageElement public class Foo { // TypeElement private int a; // VariableElement private Foo other; // VariableElement public Foo () {} // ExecuteableElement public void setA ( // ExecuteableElement int newA // VariableElement ) {} }

生成代码的逻辑主要看具体的需求,这里的需求是有Controller注解的类被当作http请求响应类,其中在这种类下面有RequestMapping注解的方法来处理具体的请求.

public void generateCode(RoundEnvironment roundEnv, Elements elementUtils, Filer filer, Messager messager) throws IOException { // 如果为空,不处理,防止重复生成 if (list.size() <= 0) { return; } // ...... //generate each handler class for (ControllerAnnotatedClass annotatedClass : list) { TypeElement typeElement = annotatedClass.getTypeElement(); DesktopApp desktopApp = typeElement.getAnnotation(DesktopApp.class); if (desktopApp != null) { sb.append(annotatedClass.getQualifiedName()).append(".class").append(','); } annotatedClass.generateCode(roundEnv, elementUtils, filer, messager); annotatedClass.generateInjectCode(method, elementUtils, messager); } }

这里是对每个类进行生成代码

/** * 生成相关处理类 * @param roundEnv * @param elementUtils * @param filer * @param messager * @throws IOException */ public void generateCode(RoundEnvironment roundEnv, Elements elementUtils, Filer filer, Messager messager) throws IOException { // 方法列表 ArrayList<MethodSpec> methodSpecs = new ArrayList<>(); // 构造函数 MethodSpec constructionMethod = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .build(); methodSpecs.add(constructionMethod); // get方法 MethodSpec.Builder getMethod = MethodSpec.methodBuilder("get") .addParameter(IHTTPSession.class, "session") .addModifiers(Modifier.PUBLIC) .addException(IllegalAccessError.class) .addException(IllegalAccessException.class) .addException(InstantiationError.class) .addException(InstantiationException.class) .returns(TypeName.get(Response.class)); Controller controller = typeElement.getAnnotation(Controller.class); if (controller.needPermissonControl()) { getMethod.beginControlFlow("if (!org.cmdmac.enlarge.server.AppNanolets.PermissionEntries.isRemoteAllow(session.getRemoteIpAddress())) ") .addStatement("return org.nanohttpd.protocols.http.response.Response.newFixedLengthResponse(\"not allow\")") .endControlFlow(); } // 生成子方法 for (Element ee : elementUtils.getAllMembers(typeElement)) { ExecutableElement eElement = (ExecutableElement) ee; Annotation annotation = eElement.getAnnotation(RequestMapping.class); if (annotation != null) { RequestMapping requestMapping = (RequestMapping) annotation; String invokeAndReturnStatement = String.format("return invoke_%s(params)",eElement.getSimpleName()); // ...... MethodSpec m = generateMethodCode(className, eElement.getSimpleName().toString(), eElement.getParameters(), messager); methodSpecs.add(m); } } getMethod.addStatement("return null"); methodSpecs.add(getMethod.build()); //创建类,增加方法 ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(pkgName, className))); FieldSpec fieldSpec = FieldSpec.builder(typeName, "cls").initializer(getQualifiedName() + ".class").build(); TypeSpec.Builder classSpec = TypeSpec.classBuilder(className + "_Proxy") .addModifiers(Modifier.PUBLIC) .addSuperinterface(TypeName.get(BaseController.class)) .addField(fieldSpec); for (MethodSpec methodSpec : methodSpecs) { classSpec.addMethod(methodSpec); } JavaFile.builder(pkgName, classSpec.build()).build().writeTo(filer); }

生成代码的逻辑其实并不复杂,其实就是按照正常写代码的过程来实现,比如这里先生成一个构造方法,再生成一个get方法,get方法里的逻辑根据RequestMapping再生成子方法,最后调用JavaFile写入filer即可. 这里遍历的关键在于: elementUtils.getAllMembers这是获取类下面的所有函数的TypeElement,另外可能遇到的是如何生成泛型定义:如Map<String, List>,这要借助TypeName来实现:

ParameterizedTypeName parameterSpec = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class), ParameterizedTypeName.get(List.class, String.class));

这里是使用ParameterizedTypeName来生成,第一个参数是泛型类的TypeName,通过ClassName.get(Map.class)得到,第二个参数是第一个泛型参数ClassName.get(String.class),第三个参数是第二个泛型参数,由于本身又是一个泛型所以再一次用ParameterizedTypeName生成.具体就不多做介绍了,源码在Enlarge-Android

第三步:使用生成的代码

注解处理器有一个缺点是,不能修改已经生成的代码,就是本身的项目中存在代码是不能修改的,只能新生成代码,所以生成代码后,需要有个调用的地方:

public static class UriRouter extends BaseRouter { RouterMatcher mStaticMatcher = new RouterMatcher("", StaticPageHandler.class); public UriRouter() { super(); //使用生成的代码,一键注册所有Controller类,后面只需增加和修改有Controller注解的类 ControllerInject.inject(this); // addRoute(StaticPageHandler.class); mappings.add(mStaticMatcher); } @Override public void addRoute(Class<?> handler) { mappings.add(new ControllerMatcher("/", handler)); } public void addRoute(String url, Class<?> handler) { mappings.add(new ControllerMatcher(url, handler)); } @Override public Response process(IHTTPSession session) { Response response = super.process(session); if (response == null) { return mStaticMatcher.process(null, session); } else { return response; } } }

总结:

annotation processor是一个强大的工具,用来可以很容易实现代码的解耦而不损失性能,目前有很多开源的项目如Butternife,Dagger,EventBus都使用了这个工具.实现这个工具一开始可能会不太适应,主要是没有定义好要生成代码的样子,所以要快速上手,可以先把想要生成的代码范例自己手写一遍,再根据这个代码来填充成通用的.全部代码放在Enlarge-Android

转载请注明原文地址: https://www.6miu.com/read-5032400.html

最新回复(0)