程序注解是从JavaSE5.0开始提供的一项新特性,利用此特性可以通过特定的注解标签为程序提供一些描述性信息,这些描述信息可以在编译或运行时为编译器、运行环境提供附加的信息,已达到简化开发、避免错误的目的。
简单来说:我们平时用的注释是给人看的,而注解是给机器看的。
下面详细讲解自定义注解及其使用:
1、声明自己的注解
使用某一个注解之前需要声明,就像使用类之前需要声明一样:
@interface <注解名称>
{
<注解属性类型> <注解属性名称> [default <默认值>];
}
①、“@interface”表示声明的是注解,在“@interface”后面给出注解的名称。
②、一对大括号包含的是注解体,在注解体中可以声明多个注解属性。
③、对注解属性的声明语法比较特殊,注解属性的名称也就是获取次属性值的方法的名称,例如“java.lang.String userName()”表示有一个名称为userName的注解属性,未来需要获取此属性值时调用userName()方法。
④、注解属性的类型一般给出全称类名。
例如:
@interface myAnnotation{
java.lang.String tableName();
int columnNum() default 1;
}
上例定义了一个注解,有两个属性:tableName、columnNum,到手后使用反射获取该注解对应的对象后可以通过方法 tableName()和columnNum()获取属性的值,其中属性columnNum有默认值1,如果声明中有默认值,则在使用时可以不指定,否则必须指定属性值。
2、确定注解的使用目标
根据使用目标的不同,注解可以有不同的使用目的,使用目的是指注解起作用的目标元素,可以是类、方法、成员变量,其他注解等。要想为自己声明的注解指定使用目标需要使用系统专门提供的注解"Target",语法:
@Target (ElementType.<使用目标>)
ElementType是java.lang.annotation包中的一个类,其静态成员表示各种不同的使用目标,如下表所示:
静态变量名 | 含义说明 |
ANNOTATION_TYPE | 此注解只能用来对注解进行注解 |
METHOD | 次注解只能用来对方法进行注解 |
CONSTRUCTOR | 此注解只能用来对构造器进行注解 |
PACKAGE | 此注解只能用来对包进行注解 |
FIELD | 此注解只能用来对类成员变量进行注解 |
PARAMETER | 此注解只能用来对参数进行注解 |
LOCAL_VARIABLE | 此注解只能用来对本地变量进行注解 |
TYPE | 此注解只能用来对类、接口以及枚举类型进行注解 |
注意:如果没有为注解指定使用目标,则注解在使用时目标没有限制。 |
例如:
①、先声明两个注解,一个用于类,一个用于属性
用于类的注解声明:
@Target(ElementType.TYPE)
@interface ClassAnnotation{
java.lang.String tableName;
}
用于属性的注解声明:
@Target(ElementType.FIELD)
@interface FieldAnnotation{
java.lang.String columnName;
}
②、注解的使用
/**
* 狗日的信息实体
*
* 注意其中的ClassAnnotation仅用于注释类,FieldAnnotation用于注释属性
* @author js
*/
@ClassAnnotation(tableName = "tbl_dog")
public class DogPojo {
@FieldAnnotation(columnName = "dog_name")
private String dogName;
@FieldAnnotation(columnName = "age")
private int age;
public String getDogName() {
return dogName;
}
public void setDogName(String dogName) {
this.dogName = dogName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
3、确定注解的使用时效
根据使用目的的不同,注解可以有不同的使用时效。使用时效是指注解的有效时间,要想为自己声明的注解指定使用时效,需要使用系统提供的“Retention”注解,语法如下:
@Retention (RetentionPolicy.<时效值>)
RetentionPolicy是java.lang.annotation包中的一个类,时效值由其静态成员变量表示,一共有3种选择,如下表所示:
时效值 | 含义说明 |
CLASS | 注解存在于类文件 |
SOURCE | 注解只存在于源代码中,在编译时被去除 |
RUNTIME | 注解存在于类文件中(编译后仍存在于class字节码文件中,在运行时虚拟机可以获取注解信息) |
4、通过反射提取注解信息
若注解的使用时效为RUNTIME,在运行时可以通过反射来提取注解中的信息。具体方法为:根据需要调用不同使用目标对应的反射类对象提供的getAnnotation方法来获取注解对象的引用,具体语法如下:
<注解名称> 引用 = xxx.getAnnotation(<注解名称>.class);
①、“xxx”表示不同的使用目标对应反射类对象的引用(Method、Field等)。
②、若参数指定的注解类型不存在,则返回null值。
各使用目标与反射类的对应关系如下表所示:
静态变量名 | 含义说明 |
CONSTRUCTOR | java.lang.reflect.Constructor |
METHOD | java.lang.reflect.Method |
FIELD | java.lang.reflect.Field |
PACKAGE | java.lang.Package |
TYPE | java.lang.Class |
5、标注性注解的使用
有时使用注解的目的只是对注解目标进行标注,并不需要注解携带具体的信息,这时只要使用注解目标提供的isAnnotationPresent方法来判断特定注解是否存在即可,方法签名如下:
public boolean isAnnotationPresent(Class annotationClass)
①、方法的入口参数为指定的注解对应的Class类对象引用(注解名.class)。
②、方法的返回值为boolean型,当指定类型的注解存在时返回True,否则返回False。提供isAnnotationPresent方法的常用类有:java.lang.Class、java.lang.Package、java.lang.reflect.AccessiableObject类(AccessiableObject类为Constructor、Field、Method类的父类,因此Constructor、Field、Method类都具有isAnnotationPresent方法)。
根据上面的讲解,下面给出一个详细的使用注解的例子,该例子模仿Hibernate注解实体持久化功能:
①、先分别声明用于注解持久化实体类和属性的注解,用于将实体及其属性映射到数据库表和字段
先定义一个表示数据类型的枚举类,用于表示数据库字段类型:
/**
* 定义一个用于表示数据类型的枚举
*
* @author js
*
*/
public enum DataTypeEnum {
INT,FLOAT,DOUBLE,VARCHAR,DECIMAL
}
声明类注解,用于将类映射到表名:
package com.test.my;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于指定一个POJO对象映射的表名的注解
*
* @author js
*/
//指定该注解用于注解类
@Target(ElementType.TYPE)
//用于指定该注解的使用时效为:存在于class字节码中,运行时可以获取注解信息
@Retention(RetentionPolicy.RUNTIME)
public @interface TableAnnotation {
/**表名*/
java.lang.String tableName();
}
声明属性注解,用于将类属性与表列关联起来:
package com.test.my;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于指定一个字段属性映射的表字段的注解
*
* @author js
*
*/
//指定该注解用于注解属性
@Target(ElementType.FIELD)
//用于指定该注解的使用时效为:存在于class字节码中,运行时可以获取注解信息
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnAnnotation {
/**列名*/
java.lang.String columnName();
/**数据类型,默认为整形*/
com.test.my.DataTypeEnum type() default com.test.my.DataTypeEnum.INT;
}
实现Hibernate的实体类持久化功能方法:
package com.test.my;
import java.lang.reflect.Field;
/**
* 对象持久化通用解决方案
* 获取注解上的参数值,动态拼接组装数据
*
* @author js
*/
public class MyHibernate {
public void save(Object obj) throws IllegalArgumentException, IllegalAccessException {
String tableName = null;
String columnSql = null;
String valueSql = null;
Class objClass = obj.getClass();
TableAnnotation classA = (TableAnnotation)objClass.getAnnotation(TableAnnotation.class);
if(null != classA){
tableName = classA.tableName();
}
//获取所有的属性并并处理
Field[] fields = objClass.getFields();
for (Field field : fields) {
ColumnAnnotation fieldA = field.getAnnotation(ColumnAnnotation.class);
if(null == columnSql) {
columnSql = fieldA.columnName();
valueSql = (DataTypeEnum.VARCHAR == fieldA.type()) ? ("'"+field.get(obj).toString()+"'") : field.get(obj).toString();
} else {
columnSql = columnSql+","+fieldA.columnName();
valueSql = valueSql + "," + ((DataTypeEnum.VARCHAR == fieldA.type()) ? ("'"+field.get(obj).toString()+"'") : field.get(obj).toString());
}
}
System.out.println("My Hibernate sql:" + "insert into " + tableName + "("+columnSql+") values("+valueSql+")");
}
}
好了,我们的注解式Hibernate持久化功能完成了,下面定义两个测试实体测试:
用户信息实体类:
package com.test.my;
import com.test.my.DataTypeEnum;
/**
* 用户信息实体
* 为举例操作简便,使用反射的Field类获取属性值,本类中的所有属性定义为public
* 实际使用应该为private,然后使用反射的Method来获取属性值
*
* @see MyHibernate
* @author js
*/
@TableAnnotation(tableName = "tbl_user")
public class UserPojo {
@ColumnAnnotation(columnName = "tbl_id", type = DataTypeEnum.INT)
public int id;
@ColumnAnnotation(columnName = "tbl_name", type = DataTypeEnum.VARCHAR)
public String name;
@ColumnAnnotation(columnName = "tbl_id")
public int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
}
角色信息实体类:
package com.test.my;
/**
* 用户角色信息实体 为举例操作简便,使用反射的Field类获取属性值,本类中的所有属性定义为public
* 实际使用应该为private,然后使用反射的Method来获取属性值
*
* @see MyHibernate
* @author js
*/
@TableAnnotation(tableName = "tbl_role")
public class RolePojo {
@ColumnAnnotation(columnName = "tbl_role_id", type = DataTypeEnum.VARCHAR)
public String roleId;
@ColumnAnnotation(columnName = "tbl_role_name", type = DataTypeEnum.VARCHAR)
public String roleName;
@ColumnAnnotation(columnName = "tbl_role_params", type = DataTypeEnum.FLOAT)
public float params;
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public float getParams() {
return params;
}
public void setParams(float params) {
this.params = params;
}
}
好了,来写个Main函数测试下:
package com.test.my;
public class Main {
public static void main(String[]args) throws IllegalArgumentException, IllegalAccessException {
MyHibernate hibernate = new MyHibernate();
//创建用户信息实体并保存
UserPojo usePojo = new UserPojo();
usePojo.setAge(100);
usePojo.setName("张三");
hibernate.save(usePojo);
//创建角色信息实体并保存
RolePojo rolePojo = new RolePojo();
rolePojo.setRoleId("00001");
rolePojo.setRoleName("超高级管理员");
rolePojo.setParams(30.1F);
hibernate.save(rolePojo);
}
}
运行,打印的sql语句如下:
呵呵,只要你在实体类中配置好注解,是不是我的这个Hibernate和真正的Hibernate一样很智能了,任何对象实体都可用这一个Hibernate进行保存操作了。
下面再稍微讲讲系统自带的常用注解
6、常用的系统注解
除了前面章节介绍的Target与Retention注解外,系统中还提供了一些指导编译器工作的注解,
①、Override注解:目标为METHOD,使用时效为SOURCE,没有属性,为标注性注解。对方法是用该注解的目的是通知编译器此方法为重写的方法,如果不是重写的方法则编译报错。
②、Deprecated注解:目标没有限制,可以应用于类、方法、成员变量等各种目标。对目标使用该注解的目的是告诉系统此目标已经过时,不建议是用。
③、SuppressWarnings(value={<要关闭的警告类型名称列表>}),例如关闭unchecked、deprecation警告:
@SuppressWarnings(value={"unchecked","deprecation"})
7、利用注解方便开发Web服务
从Java SE 6.0开始,Java SE正式支持用Java来开发Web Services。这将大大方便与Web服务相关的开发与测试,提高开发的效率与速度。下面通过注解创建一个Webservice服务端类。
package com.test.wbs;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.ws.Endpoint;
/**
*
* @author js
*/
@WebService(serviceName="HelloService")
@SOAPBinding(style=SOAPBinding.Style.RPC)
public class AnnotationWebServie {
@WebMethod(operationName = "sayHello")
public String serviceMethod(int params1, String params2) {
return "我是Java注解式Webservice服务器端:我收到你的参数分别为:params1=" + params1 + ",params2="+params2;
}
public static void main(String[]args) {
Endpoint.publish("http://192.168.0.138:8080/WebServiceExample/AnnotationWebServie", new AnnotationWebServie());
}
}
运行上面方法,并通过浏览器访问地址:http://192.168.0.138:8080/WebServiceExample/AnnotationWebServie?WSDL,得到如下结果
对应的客户端,有点麻烦,需要通过jdk的bin下面的wsimport命令来获取服务器的wsdl文件并自动生成客户端的一些辅助类,才能进行开发,此处不详细说明,如需了解请查询相关详细资料。
8、注解与代码自动生成
注解不但能在编译与运行时为系统提供附加的信息,而且可以为专用的工具提供信息来自动生成辅助的代码,大大简化开发的工作(专用工具工作原理:无非也是根据注解信息自动生成代码而已)。
如果需要,开发人员也可以提供自己的代码自动生成功能。从JavaSE5.0开始,系统中提供了一个名称为APT(Annotation Processing Tool)的工具,通过使用此工具开发人员可以方便地开发出自己的代码自动生成功能。