1.概念
类的组成部分,可以给类携带一些额外的信息,提供一种安全的类似注释标记的机制,用来将任何信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。
- 注解是给编译器或 JVM 看的,编译器或 JVM 可以根据注解来完成对应的功能
- 注解类似修饰符,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中
- 父类中的注解是不能被子类继承的
注解作用:
- 标记
- 框架技术多半都是在使用注解和反射,都是属于框架的底层基础技术
- 在编译时进行格式检查,比如方法重写约束 @Override、函数式接口约束 @FunctionalInterface.
注解本质是特殊接口,继承了 Annotation ,其具体实现类是 Java 运行时生成的动态代理类,通过反射获取注解时,返回的是运行时生成的动态代理对象 $Proxy1,通过代理对象调用自定义注解(接口)的方法,回调 AnnotationInvocationHandler 的 invoke 方法,该方法会从 memberValues 这个 Map 中找出对应的值,而 memberValues 的来源是 Java 常量池
1.Java中的常见注解
JDK自带注解
三种标准注解
- @Override
- @Deprecated
- @SuppressWarnings
先定义一个接口,初始时有2个方法:
public interface Person {
String name();
@Deprecated //后期发现并不是所有人都会唱歌,但是这个方法也不能删,因为还有人在使用
void sing();
}
实现该接口的子类:
public class Child implements Person {
// @Override 表示覆写了父类的方法,如果把父类的 name 这个方法注释掉,则不能通过编译
@Override
public String name() {
return null;
}
@Override
public void sing() {
System.out.println("I like sing");
}
}
再测试该方法:
@SuppressWarnings("deprecation") //加上该注解后就可以忽略"该方法已弃用"的警告,前提是自己要确定这个人的确会sing
public void sing() {
Person p = new Child();
p.sing();
}
4种元注解
@Target:约束自定义注解可以标记的范围,默认值为任何元素,表示该注解用于什么地方,可用值定义在 ElementType 类中:
ElementType.CONSTRUCTOR:用于描述构造器ElementType.FIELD:成员变量、对象、属性(包括 enum 实例)ElementType.LOCAL_VARIABLE:用于描述局部变量ElementType.METHOD:用于描述方法ElementType.PACKAGE:用于描述包ElementType.PARAMETER:用于描述参数ElementType.TYPE:用于描述类、接口(包括注解类型)或 enum 声明
@Retention:定义该注解的生命周期,申明注解的作用范围:编译时,运行时,可使用的值定义在 RetentionPolicy 枚举类中:
RetentionPolicy.SOURCE:在编译阶段丢弃,这些注解在编译结束之后就不再有任何意义,只作用在源码阶段,生成的字节码文件中不存在,@Override、@SuppressWarnings都属于这类注解RetentionPolicy.CLASS:在类加载时丢弃,在字节码文件的处理中有用,运行阶段不存在,默认值RetentionPolicy.RUNTIME: 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息,自定义的注解通常使用这种方式
@Inherited:表示修饰的自定义注解可以被子类继承
@Documented:表示是否将自定义的注解信息添加在 Java 文档中
常见第三方注解
Spring:
- @Autowired
- @Service
- @Repository
Mybatis:
- @InsertProvider
- UpdateProvider
- @Options
2.注解的分类
按照运行机制分
- 源码注解:注解只在源码中存在,编译成.class文件就不存在了
- 编译时注解:注解在源码和.class文件中都存在。@Override、@Deprecated、@SuppressWarnings都属于这类
- 运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解
按来源分
- 来自JDK的注解
- 来自第三方的注解
- 我们自己定义的注解
3.自定义注解
语法规则:
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
修饰符 @interface 注解名 { //使用 @interface 关键字定义注解
// 1.属性类型是受限的,合法的类型包括基本类型及String,Class,Annotation,Enumeration
// 2.如果注解只有一个属性,则属性名必须取名为value(),在使用时可以忽略属性名和赋值号(=)
// 3.注解类可以没有属性,没有属性的注解称为标识注解
String desc(); //属性以无参无异常方式声明
String author();
int age() default 18; //可以用default为成员指定一个默认值
}
4.解析注解
开发中经常要知道一个类的成分上面到底有哪些注解,注解有哪些属性数据,这都需要进行注解的解析
Class 类 API :
Annotation[] getDeclaredAnnotations():获得当前对象上使用的所有注解,返回注解数组T getDeclaredAnnotation(Class<T> annotationClass):根据注解类型获得对应注解对象T getAnnotation(Class<T> annotationClass):根据注解类型获得对应注解对象boolean isAnnotationPresent(Class<Annotation> class):判断对象是否使用了指定的注解boolean isAnnotation():此 Class 对象是否表示注释类型
解析注解数据的原理:注解在哪个成分上,就先拿哪个成分对象,比如注解作用在类上,则要该类的 Class 对象,再来拿上面的注解
public class ParseAnn {
public static void main(String[] args) {
//1.定位Class类对象
Class c = Child.class;
//2.判断这个类上是否使用了某个注解
boolean isCExist = c.isAnnotationPresent(Description.class);
if (isCExist) {
//3.获取这个注解对象
Description d = (Description) c.getAnnotation(Description.class);
System.out.println(d.desc());
}
//4.找到方法上的注解
Method[] methods = c.getMethods();
for (Method method : methods) {
boolean isMExist = method.isAnnotationPresent(Description.class);
if (isMExist) {
Description m = method.getAnnotation(Description.class);
System.out.println(m.desc());
}
}
//另一种解析方法
for (Method method : methods) {
Annotation[] as = method.getAnnotations();
for (Annotation a : as) {
if (a instanceof Description) {
Description d = (Description) a;
System.out.println(d.desc());
}
}
}
}
}
5.注解实战
需求:
- 有一张用户表,字段包括用户id,用户名,昵称,年龄,性别,所在城市,邮箱,手机号
- 方便的对每个字段或字段的组合条件进行检索,并打印出SQL
- 使用方式要足够简单,见代码示例
一个注解,将实体类与表名映射
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
一个注解,将实体类的属性名与表的字段映射
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value();
}
一个实体类
@Table("user")
public class Filter {
@Column("id")
private int id;
@Column("user_name")
private String userName;
@Column("nick_name")
private String nickName;
@Column("age")
private int age;
@Column("city")
private String city;
@Column("email")
private String email;
@Column("mobile")
private String mobile;
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 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 getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
测试类,包括注解的解析
public class Test {
public static void main(String[] args) {
Filter f1 = new Filter();
f1.setId(10); //查询id为10的用户
Filter f2 = new Filter();
f2.setAge(18);
f2.setUserName("lucy"); //模糊查询用户名为lucy,年龄为18的用户
Filter f3 = new Filter();
f3.setEmail("liu@sina.com,zh@163.com,77777@qq.com");//查询邮箱为其中任意一个的用户
String sql1 = query(f1);
String sql2 = query(f2);
String sql3 = query(f3);
System.out.println(sql1);
System.out.println(sql2);
System.out.println(sql3);
}
private static String query(Object f) {
StringBuilder sb = new StringBuilder();
//1.获取到class
Class c = f.getClass();
//2.获取table的名字
boolean exists = c.isAnnotationPresent(Table.class);
if (!exists) {
return null;
}
Table t = (Table) c.getAnnotation(Table.class);
String tableName = t.value();
sb.append("select * from ").append(tableName).append(" where 1= 1");
//3.遍历所有的字段
Field[] fArray = c.getDeclaredFields();
for (Field field : fArray) {
//4.处理每个字段对应的sql
//4.1拿到字段名
boolean fExists = field.isAnnotationPresent(Column.class);
if (!fExists) {
continue;
}
Column column = field.getAnnotation(Column.class);
String columnName = column.value();
//4.2拿到字段值
String fieldName = field.getName();
String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Object fieldValue = null;
try {
Method getMethod = c.getMethod(getMethodName);
fieldValue = getMethod.invoke(f);
} catch (Exception e) {
e.printStackTrace();
}
//4.3拼装sql
if (fieldValue == null ||
(fieldValue instanceof Integer && (Integer) fieldValue == 0)) {
continue;
}
sb.append(" and ").append(fieldName);
if (fieldValue instanceof String) {
if (((String) fieldValue).contains(",")) {
String[] values = ((String) fieldValue).split(",");
sb.append(" in (");
for (String value : values) {
sb.append("'").append(value).append("'").append(",");
}
sb.deleteCharAt(sb.length() - 1);
sb.append(")");
} else {
sb.append(" = ").append("'").append(fieldValue).append("'");
}
} else if (fieldValue instanceof Integer) {
sb.append(" = ").append(fieldValue);
}
}
return sb.toString();
}
}
测试代码的打印输出结果
