学习Java的注解有几个好处:首先是能够读懂别人的代码,特别是框架相关的代码;其次是能够让编程更加简洁、代码更清晰;最后让代码看起来高大上(高逼格^^);
1.常见注解与分类
即使你对Java注解有多么不熟悉,下面Java自带的注解几个你也一定遇到过:
- @Override:覆盖父类方法
- @Deprecated:表示该方法已过时,不建议使用,只是为了前向兼容
- @SuppressWarning:忽略警告
如果是经常用框架类的同学,那么对第三方注解应该也是很熟悉的:
- 比如Spring: @Autowired、@Service、@Repository
- 比如Mybatis: @InsertProvider、@UpdateProvider、@Options
Java注解通常可以有不同维度分类,比如基于注解生效的时间进行分类,或者基于注解来源进行分类(Java自带注解、第三方注解等)。如果基于注解生效的时间对注解进行分类,可以分为下面三类:
- 源码注解:注解只在源码中存在,编译成.class文件后就不存在,比如@Override、@Deprecated等。
- 编译时注解:注解在源码和.class文件中都存在,
- 运行时注解:通常采用反射机制来实现注解处理器,在程序运行阶段起作用,能够影响代码运行逻辑的注解,比如:@Autowired、@Service等。
2.自定义注解
自定义Java注解时需要遵循一定的语法要求:
- 使用@interface关键字定义注解
- 成员以无参无异常的方式说明
注意:
- 可以用default为成员制定一个默认值
- 成员类型是受限的,合法的类型包括原始类型、String、Class等
- 如果注解只有一个成员,则成员名必须取名为value(),在使用时可以忽略成员名和赋值号(=)
- 注解类可以没有成员,没有成员的注解称为标识注解
自定义注解时涉及到的元注解(解释注解的注解)
- @Target:声明该注解的作用域,包括method、field、package、type(类或者接口)。
- @Retention:声明注解的运行生命周期,包括runtime(运行时注解)、source(源码注解)、class(编译注解)
- @Inherited:声明子类是否能继承使用了该注解的父类的注解,该元注解只对普通类有效,对于接口继承无效。
- @Documented:生成javadoc时会包含注解的信息
使用注解的语法:@<注解名>(<成员名1>=<成员值1>,<成员名2>=<成员值2>)
3.自定义运行时注解
运行时注解时通过反射获取类、方法或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。
自定义注解类:
package com.yongfxu.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
//@Retention(RetentionPolicy.SOURCE)
//@Retention(RetentionPolicy.CLASS)
@Inherited // 是否允许子类继承使用了该注解的父类的注解,对接口继承无效,对普通类继承有效
@Documented // 是否生成javadoc
public @interface Description {
String desc();
String author();
int age() default 18;
}
定义一个接口,声明要实现的方法:
package com.yongfxu.annotation;
public interface Person {
String name();
String sing();
int age();
}
声明一个实现类,实现上述Person接口的方法,并增加注解:
package com.yongfxu.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@Description(desc = "descClass", author = "authorClass")
public class Child implements Person {
@Override
@Description(desc = "descMethodName", author = "authorMethodName")
public String name() {
return "hahaha";
}
@Override
@Description(desc = "descMethodSing", author = "authorMethodSing")
public String sing() {
return null;
}
@Override
@Description(desc = "descMethodAge", author = "authorMethodAge")
public int age() {
return 0;
}
public static void main(String[] args) {
/*
* 使用类加载器的反射解析注解的方式,该注解必须是@Retention(RetentionPolicy.RUNTIME),
* 否则执行不出效果,因为类加载就是运行时执行的
*/
try {
// 1.使用类加载器加载类
Class aClass = Class.forName("com.yongfxu.annotation.Child");
// 2.找到类上的注解
boolean isExist = aClass.isAnnotationPresent(Description.class);
if (isExist) {
// 3.拿到注解实例
Description description = (Description) aClass.getAnnotation(Description.class);
System.out.println(description.author());
}
// 4.找到方法上的注解
Method[] methods = aClass.getMethods();
for (Method method : methods) {
boolean isMExist = method.isAnnotationPresent(Description.class);
if (isMExist) {
Description description = (Description) method.getAnnotation(Description.class);
System.out.println(description.author());
}
}
// 5.另外一种解析方法
for (Method method : methods) {
Annotation[] as = method.getAnnotations();
for (Annotation annotation : as) {
if (annotation instanceof Description) {
Description description = (Description) annotation;
System.out.println(description.author());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行上述类的main方法进行测试,得到下面的结果:
authorClass
authorMethodName
authorMethodSing
authorMethodAge
authorMethodName
authorMethodSing
authorMethodAge
4.Java注解的应用
下面根据所学的注解,实现一个持久层架构的注解,用于代替Hibernate,核心代码就是通过注解来实现。下面直接上代码。
声明一个Table的注解类,用于标注数据库的表名:
package com.yongfxu.mock;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
String value();
}
声明一个Column的注解类,用于标注待查询的字段名:
package com.yongfxu.mock;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String value();
}
声明一个基础Bean对象,用于承载数据库获取到的数据。为该类加上上述两注解:
package com.yongfxu.mock;
@Table("user")
public class User {
@Column("user_name")
private String userName;
@Column("city")
private String city;
@Column("profession")
private String profession;
@Column("age")
private int age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getProfession() {
return profession;
}
public void setProfession(String profession) {
this.profession = profession;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
最后,声明一个Filter类,用于实现注解的解析流程:
package com.yongfxu.mock;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Filter {
public static void main(String[] args) {
// 根据用户名和年龄生成sql查询语句
User user1 = new User();
user1.setUserName("yongfxu");
user1.setAge(36);
// 根据用户名和其中一个岗位生成sql查询语句
User user2 = new User();
user2.setUserName("yongfxu");
user2.setProfession("programmer, manager, engineer");
System.out.println("sql1: " + query(user1));
System.out.println("sql2: " + query(user2));
}
private static String query(User user) {
// 1.获取对象的class
Class userClass = user.getClass();
// 2.验证注解是否存在
boolean isExist = userClass.isAnnotationPresent(Table.class);
if (!isExist) {
return null;
}
StringBuffer stringBuffer = new StringBuffer("select * from ");
// 3.获取注解Table中的内容
Table table = (Table) userClass.getAnnotation(Table.class);
String tableName = table.value();
stringBuffer.append(tableName).append(" where 1 = 1");
Field[] fields = userClass.getDeclaredFields();
// 4.处理每个字段对应的SQL
for (Field field : fields) {
// 4.1 拿到字段的名字
boolean isFieldExist = field.isAnnotationPresent(Column.class);
if (!isFieldExist) {
continue;
}
Column column = field.getAnnotation(Column.class);
String columnName = column.value();
// 4.2 拿到字段的值
String fieldName = field.getName();
String methodName = new StringBuffer("get").append(fieldName.substring(0, 1).toUpperCase()).append(fieldName.substring(1)).toString();
Object fieldValue = null;
try {
Method method = userClass.getMethod(methodName);
fieldValue = method.invoke(user);
} catch (Exception e) {
e.printStackTrace();
}
if (fieldValue == null || (fieldValue instanceof Integer && (Integer) fieldValue == 0)) {
// 字段值为null或者int为0
continue;
}
// 4.3 拼接SQl
stringBuffer.append(" and ");
if (fieldValue instanceof Integer) {
stringBuffer.append(columnName).append(" = ").append(fieldValue);
} else if (fieldValue instanceof String) {
if (((String) fieldValue).contains(",")) {
String[] strArray = ((String) fieldValue).split(",");
stringBuffer.append(columnName).append(" in (");
for (String string : strArray) {
stringBuffer.append("'").append(string.trim()).append("',");
}
stringBuffer.deleteCharAt(stringBuffer.length() - 1);
stringBuffer.append(")");
} else {
stringBuffer.append(columnName).append(" = ").append("'").append(fieldValue).append("'");
}
}
}
return stringBuffer.toString();
}
}
至此,已经完成注解的实现。执行Filter类的main函数进行测试,得到下述结论:
sql1: select * from user where 1 = 1 and user_name = 'yongfxu' and age = 36
sql2: select * from user where 1 = 1 and user_name = 'yongfxu' and profession in ('programmer','manager','engineer')
进程已结束,退出代码0