一、注解的基本概念
注解是JDK1.5及以后版本引入的,它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以‘@注解名’在代码中存在的,它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。(摘自
百度百科)
按照运行机制,注解可以分为三类:1、源码注解:即注解只在源代码中存在,在编译成.class文件后就不存在了;2、编译时注解:即在源码和编译的.class文件中都存在,例如常见的JDK 注解@Override、@Deprecated、@SuppressWarings;3、运行时注解:即注解在运行时仍然存在,甚至可能会影响程序的运行逻辑,例如Spring框架中用到的@Autowired注解。
我们也可以按照来源来将注解划分为三类:1、来自JDK的注解,就比如前面提到的@Override、@Deprecated、@SuppressWarings;2、来自第三方框架的注解,例如Spring的@Autowired;3、当然还有我们自己定义的注解。
还有一种特殊的注解,元注解,就是指注解的注解,这在自定义注解时经常使用。
二、自定义注解
我们可以通过自定义注解来实现我们自己想要的功能,下面通过一个简单的例子来讲解一下自定义注解的一些知识,下面的代码是一个简单的自定义注解。
package com.imooc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String name();
int age() default 18;
}
首先我们可以看到,我们是使用@interface关键字定义注解的,其中注解里定义的类似于一个方法的例如String name();其实是注解的成员,注解的成员通常以无参无异常的方式声明的,我们也可以用default关键字为成员指定一个默认值。这里需要注意的是注解的成员的类型是受限的,只能是基本数据类型或者String,Class,Annotation,Enumeration这几种类型,如果一个注解只有一个成员,那么这个成员就必须取名为value(),在使用注解时可以不显式声明成员名称和等于号(=)。注解也可以没有成员,没有成员的注解称为标识注解。
除了注解定义的成员,我们发现在注解定义上面还有几个注解,这些注解就是元注解,下面来简单分析以下这几个元注解:@Target表示定义的注解的作用域,通常作用域有constructor(构造方法声明),field(字段声明),local_variable(局部变量声明),method(方法声明),package(包声明),parameter(参数声明),type(类,接口声明);@Retention表示定义的注解的运行周期,主要包括source:只在源码显示,编译时会丢弃,class:编译时会记录到class中,运行时忽略,runtime:运行时存在,可以通过反射读取;@Inherited表示被注解可以被子类继承;@Documented表示生成JavaDoc文档时会将注解写入文档。
当我们自定义了一个注解后就可以使用这个注解了,使用注解的语法是:@注解名(成员名=成员值,成员名=成员值……)
三、解析注解
在我们自定义了注解之后,可以通过反射获取类、方法或成员上 运行时的注解信息,从而实现动态控制程序运行时逻辑的目的。下面通过一个实例来解析上面的例子中定义的注解。我们先定义一个类,类名为Boy,在这个类上和类的方法上使用注解,代码如下。package com.imooc.annotation;
@Description(name="A Class boy", age=20)
public class Boy {
@Override
@Description(name="A Method boy", age=20)
public String name() {
return null;
}
@Override
public int age() {
return 0;
}
}
然后我们再写一个解析注解的类TestAnnotation,通过反射来获取注解的信息。
package com.imooc.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class TestAnnotation {
public static void main(String[] args) {
//1、获取Class信息
Class clazz = Boy.class;
//2、通过反射判断类上是否存在注解
if(clazz.isAnnotationPresent(Description.class)) {
//3、获取到类上的注解
Description desc = (Description) clazz.getAnnotation(Description.class);
//4、获取类上注解的信息
System.out.println("name=" + desc.name() + ",age=" + desc.age());
//5、通过反射获取类的方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//6、判断方法上是否有注解
if(method.isAnnotationPresent(Description.class)) {
//7、获取方法上的注解
Description methodDesc = method.getAnnotation(Description.class);
//8、获取方法上注解的信息
System.out.println("第一种方式获得注解:name=" + methodDesc.name() + ",age=" + methodDesc.age());
}
//9、获取方法上所有的注解信息
Annotation[] anns = method.getAnnotations();
for (Annotation annotation : anns) {
//10、遍历注解获取对应的注解信息
if(annotation instanceof Description) {
Description methodDesc = (Description) annotation;
System.out.println("第二种方式获得注解:name=" + methodDesc.name() + ",age=" + methodDesc.age());
}
}
}
}
}
}
运行程序,程序执行如下结果,说明我们自定义的注解解析成功了。
name=A Class boy,age=20
第一种方式获得注解:name=A Method boy,age=20
第二种方式获得注解:name=A Method boy,age=20
接下来我们思考一下,定义一个接口Person,然后让Boy实现这个接口,然后在Person上使用注解,代码如下:
package com.imooc.annotation;
@Description(name="A Class boy", age=20)
public interface Person {
@Description(name="A Method boy", age=20)
public String name();
public int age();
}
package com.imooc.annotation;
public class Boy implements Person {
@Override
public String name() {
return null;
}
@Override
public int age() {
return 0;
}
}
这时再运行TestAnnotation,我们发现控制台并没有打印任何输出,这说明@Inherited这个元注解对与接口的implements是不起作用的,那如果我们将接口Person改成抽象类,让Boy继承这个抽象类结果会是怎样呢?
package com.imooc.annotation;
@Description(name="A Class boy", age=20)
public abstract class Person {
@Description(name="A Method boy", age=20)
public String name(){
return null;
}
public int age(){
return 0;
}
}
package com.imooc.annotation;
public class Boy extends Person {
@Override
public String name() {
return null;
}
@Override
public int age() {
return 0;
}
}
运行程序,控制台打印如下输出。
name=A Class boy,age=20
这说明@Inherited这个元注解对继承extends是起作用的,但是只会继承类上的注解,并不会继承该类方法上的注解。
四、一个简单实例的记录
这里简单记录一个注解实现的实例,主要用于根据数据库表和表中的字段进行组合,自动生成相应的SQL,下面是代码。package com.imooc.annotation.test;
@Table("user")
public class UserBean {
@Column("id")
private int id;
@Column("user_name")
private String userName;
@Column("nick_name")
private String nickName;
@Column("age")
private int age;
@Column("sex")
private String sex;
@Column("city")
private String city;
@Column("email")
private String email;
@Column("phone")
private String phone;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
package com.imooc.annotation.test;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Table {
String value();
}
package com.imooc.annotation.test;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Column {
String value();
}
package com.imooc.annotation.test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class SQLUtil {
public static void main(String[] args) throws Exception {
UserBean user = new UserBean();
user.setId(1);
user.setUserName("Jack");
user.setEmail("test@qq.com,test@126.com,test@163.com");
System.out.println(generateSQL(user));
}
public static String generateSQL(Object obj) throws Exception {
StringBuffer sb = new StringBuffer();
Class clazz = obj.getClass();
if(clazz.isAnnotationPresent(Table.class)) {
Table table = (Table) clazz.getAnnotation(Table.class);
sb.append("select * form ").append(table.value()).append(" where 1=1 ");
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields) {
if(field.isAnnotationPresent(Column.class)) {
Column column = field.getAnnotation(Column.class);
String columnName = column.value();
String getMethodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
Method getMethod = clazz.getMethod(getMethodName);
Object returnValue = getMethod.invoke(obj);
if(returnValue instanceof String) {
if(returnValue != null) {
if(((String) returnValue).contains(",")) {
String[] spiltStrs = ((String) returnValue).split(",");
sb.append(" and " + columnName + " in(");
for (String spiltStr : spiltStrs) {
sb.append(" '" + spiltStr + "', ");
}
sb.append(") ");
} else {
sb.append(" and " + columnName + "='" + returnValue + "' ");
}
}
}
if(returnValue instanceof Integer) {
if((int)returnValue != 0) {
sb.append(" and " + columnName + "=" + returnValue + " ");
}
}
}
}
}
return sb.toString();
}
}