先说说学习注解的原因吧!在一次项目中,android需要同步表数据,但是这么多表的创建不可能在本地分别写语句进行创建,不现实,同时也不利于扩展,所以就抽出写一个公共方法用于表的创建,这样我们只需要结合反射就能进行表的创建。学习的初衷很简单,就是要解决这个问题,所以在我们学习后,我们会通过这个小小的案例来实战一把。
一、简介
1、什么是注解?
注解同样是JDK1.5以后引入来的技术,Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。说的在通俗一点,注解就是一种标记,加上注解就是加上一种标记,有了这种标记我们就可以通过反射进行判断。
2、注解的分类?
此处根据使用用途分为3类:
- JDK内置系统注解
- 元注解
- 自定义注解
下面我们分别对这三种注解进行说明:
(1)、系统内置注解:这个我们应该不陌生,最常见的3类分别是:
- @Override注解,它的作用就是标志子类重写父类的方法。如果我们用在一个父类没有的方法上编译器就会报错。在android开发中,@Override注解随处可见,继承Activity,我们需要重写OnCreate()方法、继承AsyncTask类,我们需要重写doInBackground方法。
- @Deprecated注解,Deprecated单词的汉语意思就是不赞成、反对,那么这个注解的作用也是类似。我们知道随着jdk的版本不断变更,肯定有些方法不被推荐使用,但是对于那些老的资深程序,可能是习惯吧!还继续使用,编译器对此方法就会有警告提醒,eclipse里面会有条黄线,看着很不爽,如果想没有这个东西,就在函数上部添加这个注解进行声明。
- @SuppressWarnnings, @SuppressWarnings 被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。
(2)、元注解:元注解就是对注解进行标记的注解,有四类。
-
@Target:目标的意思,表示注解用于什么地方。例如:用于类、还是方法。可选参数有:
ElementType.CONSTRUCTOR: 构造器声明
ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
ElementType.LOCAL_VARIABLE: 局部变量声明
ElementType.METHOD: 方法声明
ElementType.PACKAGE: 包声明
ElementType.PARAMETER: 参数声明
ElementType.TYPE: 类、接口(包括注解类型)或enum声明 -
@Retention:保留的意思,表示注解的声明周期,通俗点就是注解在什么范围内有效。可选参数有:
RetentionPolicy.SOURCE: 停留在java源文件,编译器被丢掉
RetentionPolicy.CLASS:停留在class文件中,但会被VM丢弃(默认)
RetentionPolicy.RUNTIME:内存中的字节码,VM将在运行时也保留注解,因此可以通过反射机制读取注解的信息 - @Documented:文档的意思,没有成员只是一个标记。表示可以被javadoc工具类文档化
- @Inherited:继承的意思,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
(3)自定义注解,这个是我们使用最频繁的注解,我们可以根据自己的需要进行定义。语法是使用@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
自定义注解格式:
public @interface 注解名称
例如:public @interface TableAnnotation
注解参数的可支持数据类型:
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
Annotation类型里面的参数该怎么设定:
第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
第三,如果只有一个参数成员,最好把参数名称设为”value”,后加小括号.例:下面的例子FruitName注解就只有一个参数成员。
实例:
@Target(ElementType.TYPE)
public @interface TableAnnotation {
String name() default "";
boolean isPrimaryKey() default false;
}
注解元素的默认值:
注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在且,并都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。
以上就是关于注解的基础知识点,下面我们就结合上篇的反射来撸一把开篇的案例。
首先我们创建表的注解AnnotationTables:
package com.dsw.annotationdemo;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnnotationTables {
//表的名称
String name() default "";
}
我们创建表的注解,作用范围为ElementType.TYPE为类,RetentionPolicy.RUNTIME为Runtime时期。该表的注解用于标记我们需要创建的表。
接下来我们创建表字段的注解AnnotationColumns:
package com.dsw.annotationdemo;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnnotationColumns {
/**
* 注解就是标记,所以字段需要有哪些标记呢?
* 类型、是否为空、名称、是否为主键
*/
String name() default "";
String type();
boolean isNull() default false;
boolean isPrimaryKey() default false;
}
所需要的注解已经创建完毕,那么就开始创建我们的实体,我们创建一个名为Person的实体。
package com.dsw.annotationdemo;
@AnnotationTables(name="Person")
public class Person {
@AnnotationColumns(name="name",isNull=false,type="String",isPrimaryKey=false)
private String name;
@AnnotationColumns(name="age",isNull =false,type="int",isPrimaryKey=false)
private int age;
@AnnotationColumns(name="id",isNull=false,type="int",isPrimaryKey=true)
private int id;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
我们在创建一个Student类,不使用AnnotationTables来对比:
package com.dsw.annotationdemo;
public class Student {
@AnnotationColumns(name="name",isNull=false,type="String",isPrimaryKey=false)
private String name;
@AnnotationColumns(name="age",isNull=false,type="String",isPrimaryKey=false)
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
好了,所需要的原材料都已准备好了,那么现在就开始利用我们上篇的反射知识来烹饪出一个Person表。在Android系统中,我们首先需要创建一个本地数据库,这就需要我们继承SQLiteOpenHelper类,创建我们的DataBaseHelper类。
package com.dsw.annotationdemo;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DataBaseHelper extends SQLiteOpenHelper {
public DataBaseHelper(Context context, String name,int version) {
//调用系统SQLiteOpenHelper的构造函数进行创建表
super(context, name, null, version);
}
@Override
public void onCreate(SQLiteDatabase arg0) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
创建数据库的关键类我们创建好了,我们不创建Activity来操作了,就继承Application写个子类,在它的onCreate()方法中来实现吧!注意,此时我们要在manifest中对application标签的android:name=”“赋值上我们创建的application的路径名。如:android:name=”com.dsw.annotationdemo.MyApplication”
package com.dsw.annotationdemo;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import android.app.Application;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
List<Class<?>> tableClassList = new ArrayList<Class<?>>();
//创建Person表
tableClassList.add(Person.class);
tableClassList.add(Student.class);
//创建一个SQLiteOpenHelper的对象
DataBaseHelper dbHelper = new DataBaseHelper(getApplicationContext(), "qxt.db", 1);
//获取数据库SQLiteDatabase对象
SQLiteDatabase sqlDatabase = dbHelper.getWritableDatabase();
initTablesToDb(sqlDatabase,tableClassList);
}
/**
* 创建表
* @param sqlDatabase
* 数据库SQLiteDatabase对象
* @param tableClassList
* 需要本地化表的集合
*/
private void initTablesToDb(SQLiteDatabase sqlDatabase,List<Class<?>> tableClassList) {
if(tableClassList != null && tableClassList.size() >0){
for(Class<?> clazz : tableClassList){
StringBuilder sbCreateSQL = new StringBuilder();
sbCreateSQL.append("Create table ");
if(clazz.isAnnotationPresent(AnnotationTables.class)){//判断是否为AnotationTable注解类型
//获取AnnotationTable注解
AnnotationTables tableAnno = clazz.getAnnotation(AnnotationTables.class);
//获取AnnotatbleTable注解的属性值,即表名
String tableName = tableAnno.name();
sbCreateSQL.append(tableName + "(");
//通过反射获取Person实体的成员变量,判断哪些是注解要被创建成Person表中的字段
Field[] fields = clazz.getDeclaredFields();
for(int i=0;i<fields.length;i++){
Field field = fields[i];
//判断字段是否为注解字段
if(field.isAnnotationPresent(AnnotationColumns.class)){
AnnotationColumns columnAnno = field.getAnnotation(AnnotationColumns.class);
String fieldName = columnAnno.name();
String fieldNull = columnAnno.isNull() ? "" : "not null";
String fieldKey = columnAnno.isPrimaryKey() ? "primary key" :"" ;
String fieldType = columnAnno.type();
sbCreateSQL.append(fieldName + " " +fieldType + " "+ fieldKey + " " + fieldNull + ",");
}
}
sbCreateSQL.replace(sbCreateSQL.length() -1, sbCreateSQL.length(), ")");
Log.d("db","db:" + sbCreateSQL.toString());
//创建表
sqlDatabase.execSQL(sbCreateSQL.toString());
}
}
}
}
}
代码注释的比较详细,就不怎么解释了,核心点就是通过反射手段获取注解,然后通过注解获取我们需要的信息,然后创建对应的表。我们直接看结果吧!
通过查看数据库,我们发现Person表被创建了,而且只有被AnnotationColumns注解的字段,Student表没有被创建。