注解整理
1. 注解作用
(1) 代码分析与检查。通过IDE 工具可以在编译期间发现数据配置语法错误。
(2) 代码自动生成。开发者可以更关注于实际应用的业务逻辑,避免写那些重复的代码,从而提高效率。例如测试,日志,事务类文件自动生成等
(3) 代码更简洁,便于代码的阅读和理解。
2. Java 注解
1.1 元注解
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceColumn{
public String name()default "fieldName";
public String setFuncName()default "setField";
public String getFuncName()default "getField";
public boolean defaultDBValue() defaultfalse;
}
@Inherited
public@interfaceGreeting{
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}
1.2 编译类注解
注解的语法比较简单,除了@符号的使用外,他基本与Java固有的语法一致,JavaSE中内置三个标准注解,定义在java.lang中:
@Override:用于修饰此方法覆盖了父类的方法;
@Deprecated:用于修饰已经过时的方法;
@SuppressWarnnings:用于通知java编译器禁止特定的编译警告。
@Override,限定重写父类方法:
@Override 是一个标记注解类型,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种Annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。这个annotaton常常在我们试图覆盖父类方法而确又写错了方法名时发挥威力。
@Deprecated,标记已过时:
同样Deprecated也是一个标记注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。而且这种修饰具有一定的 “延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为 @Deprecated,但编译器仍然要报警。
值得注意,@Deprecated这个annotation类型和javadoc中的 @deprecated这个tag是有区别的:前者是java编译器识别的,而后者是被javadoc工具所识别用来生成文档(包含程序成员为什么已经过时、它应当如何被禁止或者替代的描述)。
在java5.0,java编译器仍然象其从前版本那样寻找@deprecated这个javadoc tag,并使用它们产生警告信息。但是这种状况将在后续版本中改变,我们应在现在就开始使用@Deprecated来修饰过时的方法而不是 @deprecated javadoc tag。
SuppressWarnnings,抑制编译器警告:
@SuppressWarnings 被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。在java5.0,sun提供的javac编译器为我们提供了-Xlint选项来使编译器对合法的程序代码提出警告,此种警告从某种程度上代表了程序错误。例如当我们使用一个generic collection类而又没有提供它的类型时,编译器将提示出"unchecked warning"的警告。通常当这种情况发生时,我们就需要查找引起警告的代码。如果它真的表示错误,我们就需要纠正它。例如如果警告信息表明我们代码中的switch语句没有覆盖所有可能的case,那么我们就应增加一个默认的case来避免这种警告。
有时我们无法避免这种警告,例如,我们使用必须和非generic的旧代码交互的genericcollection类时,我们不能避免这个uncheckedwarning。此时@SuppressWarning就要派上用场了,在调用的方法前增加@SuppressWarnings修饰,告诉编译器停止对此方法的警告。
SuppressWarning不是一个标记注解。它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。对于javac编译器来讲,被-Xlint选项有效的警告 名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。
annotation语法允许在annotation名后跟括号,括号中是使用逗号分割的name=value对用于为annotation的成员赋值。
1.deprecation:使用了不赞成使用的类或方法时的警告;
2.unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
3.fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
4.path:在类路径、源文件路径等中有不存在的路径时的警告;
5.serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
6.finally:任何 finally 子句不能正常完成时的警告;
7.all:关于以上所有情况的警告。
1.3 资源类注解
@PostConstruct:该注解用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。
@PreDestroy:该注解作为回调通知用于各方法,以表示该实例正处于被容器移除的过程中。
@Resource:该注解用于标记应用程序所需要的资源。
1.4 自定义注解
a)编译时注解——注解处理器
在程序中添加的注解,可以在编译时刻或是运行时刻来进行处理。在编译时刻处理的时候,是分成多趟来进行的。如果在某趟处理中产生了新的Java源文件,那么就需要另外一趟处理来处理新生成的源文件。如此往复,直到没有新文件被生成为止。在完成处理之后,再对Java代码进行编译。JDK 5中提供了apt工具用来对注解进行处理。apt是一个命令行工具,与之配套的还有一套用来描述程序语义结构的Mirror API。Mirror API(com.sun.mirror.*)描述的是程序在编译时刻的静态结构。通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供相应的处理逻辑。具体的处理工作交给apt工具来完成。编写注解处理器的核心是AnnotationProcessorFactory和AnnotationProcessor两个接口。后者表示的是注解处理器,而前者则是为某些注解类型创建注解处理器的工厂。
以上面的注解Assignment为例,当每个开发人员都在源代码中更新进度的话,就可以通过一个注解处理器来生成一个项目整体进度的报告。首先是注解处理器工厂的实现。
public class AssignmentApf implementsAnnotationProcessorFactory {
public AnnotationProcessorgetProcessorFor(Set<AnnotationTypeDeclaration> atds,?AnnotationProcessorEnvironment env) {
if (atds.isEmpty()) {
return AnnotationProcessors.NO_OP;
}
return new AssignmentAp(env); //返回注解处理器
}
public Collection<String>supportedAnnotationTypes() {
returnCollections.unmodifiableList(Arrays.asList("annotation.Assignment"));
}
public Collection<String>supportedOptions() {
return Collections.emptySet();
}
}
AnnotationProcessorFactory接口有三个方法:getProcessorFor是根据注解的类型来返回特定的注解处理器;supportedAnnotationTypes是返回该工厂生成的注解处理器所能支持的注解类型;supportedOptions用来表示所支持的附加选项。在运行apt命令行工具的时候,可以通过-A来传递额外的参数给注解处理器,如-Averbose=true。当工厂通过 supportedOptions方法声明了所能识别的附加选项之后,注解处理器就可以在运行时刻通过AnnotationProcessorEnvironment的getOptions方法获取到选项的实际值。注解处理器本身的基本实现如下所示。
public class AssignmentAp implementsAnnotationProcessor {
privateAnnotationProcessorEnvironmentenv;
privateAnnotationTypeDeclarationassignmentDeclaration;
publicAssignmentAp(AnnotationProcessorEnvironment env) {
this.env = env;
assignmentDeclaration= (AnnotationTypeDeclaration) env
.getTypeDeclaration("annotation.Assignment");
}
public void process() {
Collection<Declaration>declarations = env
.getDeclarationsAnnotatedWith(assignmentDeclaration);
for (Declarationdeclaration : declarations) {
processAssignmentAnnotations(declaration);
}
}
private voidprocessAssignmentAnnotations(Declaration declaration) {
Collection<AnnotationMirror>annotations = declaration
.getAnnotationMirrors();
for (AnnotationMirrormirror : annotations) {
if(mirror.getAnnotationType().getDeclaration()
.equals(assignmentDeclaration)){
Map<AnnotationTypeElementDeclaration,AnnotationValue> values = mirror
.getElementValues();
Stringassignee = (String) getAnnotationValue(values,
"assignee");//获取注解的值
}
}
}
}
注解处理器的处理逻辑都在process方法中完成。通过一个声明(Declaration)的getAnnotationMirrors方法就可以获取到该声明上所添加的注解的实际值。得到这些值之后,处理起来就不难了。
在创建好注解处理器之后,就可以通过apt命令行工具来对源代码中的注解进行处理。 命令的运行格式是apt-classpath bin -factory annotation.apt.AssignmentApf src/annotation/work/*.java,即通过-factory来指定注解处理器工厂类的名称。实际上,apt工具在完成处理之后,会自动调用javac来编译处理完成后的源代码。
JDK 5中的apt工具的不足之处在于它是Oracle提供的私有实现。在JDK6中,通过JSR 269把自定义注解处理器这一功能进行了规范化,有了新的javax.annotation.processing这个新的API。对Mirror API也进行了更新,形成了新的javax.lang.model包。注解处理器的使用也进行了简化,不需要再单独运行apt这样的命令行工具,Java编译器本身就可以完成对注解的处理。对于同样的功能,如果用JSR 269的做法,只需要一个类就可以了。
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes("annotation.Assignment")
public classAssignmentProcess extendsAbstractProcessor {
privateTypeElementassignmentElement;
publicsynchronizedvoidinit(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Elements elementUtils = processingEnv.getElementUtils();
assignmentElement =elementUtils.getTypeElement("annotation.Assignment");
}
publicbooleanprocess(Set<?extends TypeElement> annotations,RoundEnvironment roundEnv) {
Set<? extendsElement>elements = roundEnv.getElementsAnnotatedWith(assignmentElement);
for (Element element : elements) {
processAssignment(element);
}
}
privatevoidprocessAssignment(Element element) {
List<? extendsAnnotationMirror>annotations = element.getAnnotationMirrors();
for (AnnotationMirror mirror :annotations) {
if(mirror.getAnnotationType().asElement().equals(assignmentElement)) {
Map<? extendsExecutableElement,? extendsAnnotationValue> values = mirror.getElementValues();
String assignee = (String)getAnnotationValue(values,"assignee"); //获取注解的值
}
}
}
}
仔细比较上面两段代码,可以发现它们的基本结构是类似的。不同之处在于JDK 6中通过元注解@SupportedAnnotationTypes来声明所支持的注解类型。另外描述程序静态结构的javax.lang.model包使用了不同的类型名称。使用的时候也更加简单,只需要通过javac -processor annotation.pap.AssignmentProcess Demo1.java这样的方式即可。
b)运行时注解——反射
上面介绍的这两种做法都是在编译时刻进行处理的。而有些时候则需要在运行时刻来完成对注解的处理。这个时候就需要用到Java的反射API。反射API提供了在运行时刻读取注解信息的支持。不过前提是注解的保留策略声明的是运行时。Java反射API的AnnotatedElement接口提供了获取类、方法和域上的注解的实用方法。比如获取到一个Class类对象之后,通过getAnnotation()、isAnnotationPresent()等方法就可以获取到该类上添加的指定注解类型的注解。
import java.lang.annotation.*;
/**
* Indicates that the annotated method is atest method.
* This annotation should be used only on parameterlessstatic methods.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test { }
public class Foo {
@Test publicstaticvoidm1() { }
public static void m2() { }
@Test publicstaticvoidm3() {
throw new RuntimeException("Boom");
}
public static void m4() { }
@Test publicstaticvoidm5() { }
public static void m6() { }
@Test publicstaticvoidm7() {
throw new RuntimeException("Crash");
}
public static void m8() { }
}
import java.lang.reflect.*;
public class RunTests {
public staticvoidmain(String[] args) throws Exception {
int passed = 0, failed = 0;
for (Method m : Class.forName(args[0]).getMethods()){
if (m.isAnnotationPresent(Test.class)) {
try {
m.invoke(null);
passed++;
} catch (Throwable ex) {
System.out.printf("Test %s failed: %s %n",m, ex.getCause());
failed++;
}
}
}
System.out.printf("Passed: %d, Failed %d%n",passed, failed);
}
}
$ java RunTests Foo
Test public static void Foo.m3()failed: java.lang.RuntimeException: Boom
Test public static void Foo.m7()failed: java.lang.RuntimeException: Crash
Passed:2, Failed 2
1.5 Android内置注解
与lint相关,主要有SuppressLint和TargetApi两个
SuppressLint使用@SuppressLint标注忽略指定的警告
TargetApi @TargetAPI(<api>)用来指示API用给定的<api>,而不是项目中指定的。
3. 依赖注入框架
3.1 依赖注入
依赖注入(Dependency Injection):在类A中要用到一个B的对象(依赖),需要通过新建B的实例或其他一些主动的方式来获取对象,然后才能调用。而通过外部的方式自动将B的对象分配给A(注入),实现相对被动的获取对象,这个过程称为依赖注入。
class TestAppimplements Runnable {
@Inject A a;
@Overridepublicvoidrun() {
a.doit();
}
publicstaticvoidmain(String[] args) {
ObjectGraph.create(new TestModule()).get(TestApp.class).run();
}
@Module(injects ={ TestApp.class})
staticclassTestModule {}
@Singleton
staticclassA {
@Inject A() {}
publicvoiddoit() {};
}
AndroidAnnotationsand ButterKnife does the similar work. RoboGuice, Dagger and Transfuse are inthe same category.
3.2 AndroidAnnotions
特点:
(1)依赖注入:包括view,extras,系统服务,资源等等
(2)简单的线程模型,通过annotation表示方法运行在ui线程还是后台线程
(3)事件绑定:通过annotation表示view的响应事件,不用在写内部类
(4)REST客户端:定义客户端接口,自动生成REST请求的实现
(5)没有你想象的复杂:AndroidAnnotations只是在在编译时生成相应子类
(6)不影响应用性能:仅50kb,在编译时完成,不会对运行时有性能影响。
与roboguice的比较:roboguice通过运行时读取annotations进行反射,所以可能影响应用性能,而AndroidAnnotations在编译时生成子类,所以对性能没有影响
(1).优化的组件
@EActivity ,@EApplication,@EBean,@EFragment,@EProvider,@EReceiver,
@EIntentService,@EService,@EView,@EViewGroup
(2).注入
@AfterInject,@AfterViews,@App,@Bean,@Extra,@FragmentArg,
@FragmentById,@FragmentByTag,@FromHtml,@HttpsClient,
@NonConfigurationInstance,@RootContext,@SystemService,@ViewById
(3).事件绑定
@TextChange,@AfterTextChange,@BeforeTextChange,@FocusChange
@CheckedChange,@Touch,@Click,@LongClick,@ItemClick,@ItemLongClick
@ItemSelect,@OptionsItem,@SeekBarProgressChange,@SeekBarTouchStart
(4).线程
(5).其他
@InstanceState,@WindowFeature,@Fullscreen,@NoTitle,@CustomTitle
@OptionsMenu,@OrmLiteDao,@RoboGuice,@Trace,@Transactional
@OnActivityResult,@HierarchyViewerSupport,@ServiceAction,@Receiver
(6).资源注入
@StringRes,@AnimationRes,@ColorRes,@DimensionPixelOffsetRes
@DimensionPixelSizeRes,@DimensionRes,@BooleanRes,@ColorStateListRes
@DrawableRes,@IntArrayRes,@IntegerRes,@LayoutRes,@MovieRes,
@StringArrayRes,@TextArrayRes,@TextRes,@HtmlRes
(7).Rest API
@Rest,@RestService,@Get,@Post,@Put,@Delete,@Options,@Head,@Accept
@RequiresHeader,@RequiresCookie,@RequiresCookieInUrl,
@RequiresAuthentication,@SetsCookie,@RequiresCookieInUrl
(8).类型安全的SharedPreferences
@DefaultBoolean,@DefaultFloat,@DefaultInt,@DefaultLong,@DefaultString
3.3 RoboGuice
Guice是Google开发的一个轻量级,基于Java5(主要运用泛型与注释特性)的依赖注入框架(IOC),Guice非常小而且快。Guice是类型安全的,它能够对构造函数,属性,方法(包含任意个参数的任意方法,而不仅仅是setter方法)进行注入。
Roboguice则是基于Android和Google Guice开发的适用于Android平台的Dependency Injection 开发包,该框架精简了你的应用代码。更少的代码意味着bug也会更少。也使得阅读代码更加容易,不再纠缠于Android平台的各种特性,而是关注于应用实际的业务逻辑。Android应用程序可以直接使用Google Guice来为普通类进行注入操作,而对和Android平台相关的类如Activity,Context,Service,View等可以使用Roboguice进行注入操作。
大小700kb左右
使用RoboGuice库 :
控件注入:用@InjectViews方法初始化控件,例如:@InjectView(R.id.textview1)TextView textView1。
资源注入:用@InjectResources方法初始化资源,例如:@InjectResource(R.string.app_name)String name。
系统服务注入:用@Inject方法初始化并获取系统服务,例如:@InjectLayoutInflater inflater。
POJO对象注入:用@Inject方法注入并初始化POJO对象,例如:@Inject Foo foo。
@ContextScoped : 表示Scope为Context 范围 Android RoboGuice 使用指南(11):Scopeshttp://www.2cto.com/kf/201205/130102.html
@InjectExtra : Intent的getExtra的注入标记
@InjectPreference: 注入Preference
@InjectResource: 注入Resource,如drawable, icon 等
@InjectView: 注入View
@Inject: Guice标记,可以注入Android平台支持的各种服务,比如LocationManager等。
@SharedPreferencesName: SharedPreferences 名称等
此外,RoboGuice还提供了简单的消息publish/subscribe 机制,以及可以支持Dependency Injection的RoboThread,RoboAsyncTask ,RoboLooperThread 等,将在后面的文章详细说明。
4. AndroidAnnotions & RoboGuice
AndroidAnnotions和ButterKnife都是利用注解器在编译时完成,而RoboGuice, Dagger and Transfuse均是采用运行时反射的方式实现。这里简单笔记下它们各自的特点
|
AndroidAnnotions |
RoboGuice |
大小 | 50kb | 700kb |
性能
| 注解处理器在编译时生成***Activity_子类,用子类***Activity_替换父类***Activity,效率高。 | 所有Activity继承自RoboActivity。运行时读取annotations进行反射,对性能有一定影响 |
内容 | · 支持click类事件绑定注入,例如@Click | 不支持 |
支持REST网络请求注入 | 不支持网络请求 | |
不支持POJO对象注入 不支持私有对象注入 | 支持POJO |
5. 资料
http://blog.csdn.net/linjf2009?viewmode=contents
https://github.com/excilys/androidannotations
https://github.com/roboguice/roboguice/wiki
http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html
http://www.cnblogs.com/peida/archive/2013/04/26/3038503.html
http://java.dzone.com/articles/dependency-injection-roboguice
http://mobile.51cto.com/abased-434125.htm