在上一篇实现类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