在我们写代码时,有时候会有一些配置信息,比如Spring里面的每个Service,最开始我们是在xml里面定义这些服务的名称和路径的,后来呢,觉得这些配置文件需要与Java源代码时刻同步,很容易写了一个服务后忘记在xml里面定义了。
于是,就有了注解,通过直接给一个类加上表示它是Service的注解,就无需再去在xml里面定义它了,这样就只需要在一个地方维护这些信息就好了。其它部分所需的信息则通过自动的方式来生成。
注解有点类似注释,不同的是注解不是提供代码功能的说明,而是实现程序功能的重要组成部分。
比如,我们可以自定义一个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface IName {
String value() default "";
}
好了,注解已经定义好了,那么,如何应用自定义的注解呢?一般来说,有两种方案:运行时通过反射获取注解的值,编译时通过apt技术获取注解的值
定义一个类,使用@IName注解
@IName("苹果")
public class Fruit {
}
然后在调用的地方,这样获取注解值
public class AnnoClient {
public static void main(String[] args) {
Fruit fruit = new Fruit();
Class extends Fruit> clazz = fruit.getClass();
if (clazz.isAnnotationPresent(IName.class)) {
IName annotation = clazz.getAnnotation(IName.class);
String name = annotation.value();
System.out.println("水果的名称是:" + name);
}
}
}
输出结果如下,说明我们成功获取到了注解的值。这就是注解+反射的一个经典应用场景。
水果的名称是:苹果
下面,我就用反射做一个简单的Android上的IOC框架
定义一个注解:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface IView {
int value() default 0;
}
根据反射处理注解的工具类
public class InjectUtil {
public static void bind(Activity activity) {
Class extends Activity> clazz = activity.getClass();
//遍历所有的字段
for (Field field : clazz.getDeclaredFields()) {
//处理字段
if (field.isAnnotationPresent(IView.class)) {
IView anno = field.getAnnotation(IView.class);
int value = anno.value();
try {
field.setAccessible(true);
field.set(activity, activity.findViewById(value));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
然后在Activity中调用
public class ReflectActivity extends AppCompatActivity {
@IView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectUtil.bind(this);
tv.setText("ReflectActivity");
}
}
这样,我们就不用再去重复写findViewById了。不过呢,反射比较耗性能,为了提高逼格,下面我们用APT技术实现同样的功能。
APT(Annotation Processing Tool 的简称),可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入。
现在有很多主流库都用上了 APT,比如 Dagger2, ButterKnife, EventBus3 等,我们要紧跟潮流,与时俱进呐!
不过,普通的APT技术在根据注解自动生成Java文件时,那个书写的语法简直太蛋疼了(相当于你用StringBuffer通过不断的append,写了一个Java类),所以呢,这里隆重推荐一个APT的编辑库:JavaPoet
首先,在AndroidStudio上配置一下apt和JavaPoet,因为Android环境中找不到AbstractProcessor,所以呢,我们需要添加一个Java Library(我命名为aptlib)来写自动生成代码的代码。
1、配置Project的build.gradle文件:
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
2、配置aptlib这个mudule的build.gradle文件:
apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.7.0'
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
3、配置app的build.gradle文件:
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.soubu.aptstudy"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.1.0'
testCompile 'junit:junit:4.12'
compile project(':aptlib')
apt project(':aptlib')
compile project(':reflectlib')
}
4、开始写具体的代码
先来个简单的栗子:
假如我想要用注解+apt自动帮我生成这么一个类:
package com.soubu.helloworld;
import java.lang.String;
import java.lang.System;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, 哈哈哈");
}
}
首先,我定义一个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Hello {
String value() default "var";
}
然后,写apt的注解处理器
package com.soubu.hello;
import com.google.auto.service.AutoService;
import com.soubu.di.DIActivity;
import com.soubu.di.DIView;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
/**
* 作者:余天然 on 2017/2/6 上午11:01
*/
@AutoService(Processor.class)
public class HelloProcessor extends AbstractProcessor {
@Override
public Set getSupportedAnnotationTypes() {
return Collections.singleton(Hello.class.getCanonicalName());
}
/**
* package com.example.helloworld;
* public final class HelloWorld {
* public static void main(String[] args) {
* System.out.println("Hello, APT");
* }
* }
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Hello.class);
for (Element element : elements) {
Hello hello = element.getAnnotation(Hello.class);
String var = hello.value();
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, " + var)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.soubu.helloworld", helloWorld)
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
最后,在app的场景类使用这个注解
@Hello("哈哈哈")
public class HelloClient {
}
其实,这个场景类啥都没干,就定义了这个注解的值为“哈哈哈”而已。然后,编译一下项目
就可以在这里看到自动生成的类了。
好了,apt自动生成代码的栗子我们已经了解了,下面,我们就也用APT做一个简单的Android上的IOC框架
首先,定义了两个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DIView {
int value() default 0;
}
然后,使用JavaPoet来编写注解处理器
package com.soubu.di;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
/**
* 作者:余天然 on 2017/2/6 上午11:21
*/
@AutoService(Processor.class)
public class DIProcessor extends AbstractProcessor {
private Elements elementUtils;
@Override
public Set getSupportedAnnotationTypes() {
// 规定需要处理的注解
return Collections.singleton(DIActivity.class.getCanonicalName());
}
/**
* public final class DIMainActivity extends MainActivity {
* public static void bindView(MainActivity activity) {
* activity.tv = (android.widget.TextView) activity.findViewById(R.id.tv);
* }
* }
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(DIActivity.class);
for (Element element : elements) {
// 判断是否Class
TypeElement typeElement = (TypeElement) element;
List extends Element> members = elementUtils.getAllMembers(typeElement);
MethodSpec.Builder bindViewMethodSpecBuilder = MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeName.VOID)
.addParameter(ClassName.get(typeElement.asType()), "activity");
for (Element item : members) {
DIView diView = item.getAnnotation(DIView.class);
if (diView == null) {
continue;
}
bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s) activity.findViewById(%s)", item.getSimpleName(), ClassName.get(item.asType()).toString(), diView.value()));
}
TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
.superclass(TypeName.get(typeElement.asType()))
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(bindViewMethodSpecBuilder.build())
.build();
JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
private String getPackageName(TypeElement typeElement) {
return elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
}
最后,在我们的app的Activity中使用
@DIActivity
public class AptActivity extends AppCompatActivity {
@DIView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DIAptActivity.bindView(this);
tv.setText("ReflectActivity");
}
}
注意,这里的DIAptActivity是我们自动生成的,要使用的话,必须先编译一下项目。
现在,关于注解的两种使用方式:运行时注解和编译时注解,想必大家都有了一定认识了,那么,还不赶紧玩一把?
最后,奉上源码:Github-AnnoDemo