1. 注解
注解(Annontion)是Java5开始引入的新特征。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。
注解的分类(按照运行机制分类)
- 源码注解:注解只在源码中存在,编译成.class文件就不存在了,如lombok中的@Data。
- 编译时注解:注解在源码和.class文件中都存在,如@Override。
- 运行时注解:在运行阶段会起作用,甚至影响运行逻辑的注解,如Spring中的@Autowired。
注解的分类(按照来源分类)
- 原生JDK
- 来自第三方
- 自定义
2. 元注解
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解有以下几种:
- @Target
- @Retention
- @Documented
- @Inherited
2.1 @Target
用于描述注解的范围,即注解在哪用。它说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)等。取值类型(ElementType)有以下几种:
- CONSTRUCTOR: 构造方法声明
- FIELD: 类成员变量声明
- LOCAL_VARIABLE: 局部变量声明
- METHOD: 方法声明
- PACKAGE: 包声明
- PARAMETER: 方法参数声明
- TYPE: 类、接口(包括注解类型) 或enum声明
- TYPE_PARAMETER:1.8版本开始,描述类、接口或enum参数的声明
- TYPE_USE:1.8版本开始,描述一种类、接口或enum的使用声明
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Log {
......
}
// 表示Log注解可以用在类、接口、enum和方法上
2.2 @Retention
用于描述注解的生命周期,表示需要在什么级别保存该注解,即保留的时间长短。取值类型(RetentionPolicy)有以下几种:
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
......
}
// 示例使用RetentionPolicy.RUNTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理。
2.3 @Documented
标识注解,用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
......
}
2.4 @Inherited
标识注解,用于表示某个被标注的注解是可以被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。 不能用于接口实现。只能继承父级类上的注解,不能继承父级方法上的注解。
2.5 综合案例
package com.okgo.annotation;
import java.lang.annotation.*;
//测试原注解
@MyAnnotation
public class Test01 {
public void test() {
}
}
//定义一个注解
//Target 表示我们的注解可以用在哪些地方
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//Retention 表示我们的注解在什么地方有效 runtime>class>source
@Retention(value = RetentionPolicy.RUNTIME)
//Documented 表示是否将我们的注解生成在JAVAdoc中
@Documented
//Inherited 子类可以继承父类的注解
@Inherited
@interface MyAnnotation{
}
3. 自定义注解
3.1 语法要求
import java.lang.annotation.*;
/**
* 自定义注解的语法要求
* 1. 使用 @interface 关键字定义注解
* 2. 以“接口的方法”为成员,以无参无异常方式声明
* 可以使用 default 给成员指定一个默认值
* 成员的类型是受限制的,合法的类型包括基本类型及 String, Class, Annotation, Enumeration
* 如果注解只有一个成员,则成员取名必须为 value(), 且在使用时可以忽略成员名和赋值符号(=)
* 注解类可以没有成员,没有成员的注解称为标识注解
*
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String desc();
String author();
int age() default 18;
}
@Description(desc = "i am interface anno")
public class Person {
@Description(desc = "i am interface method anno")
String name(){
return "name";
}
int age(){
return 0;
}
@Deprecated
void sing(){
}
}
@Description(desc = "i am class anno")
public class Child extends Person{
@Description(desc = "i am method anno")
@Override
public String name() {
return null;
}
@Override
public int age() {
return 0;
}
@Override
public void sing() {
}
}
3.2 解析注解
/**
* 解析注解:通过反射获取类、方法或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。
*
* 1:使用类加载器加载类
* Class c = Class.forName("com.ann.test.Child");
*
* 2:找到类上面的注解
* isAnnotationPresent(类类型):Class对象的方法,判断当前类类型是否存在某个类类型的注解,返回类型为boolean。
* boolean isExist = c.isAnnotationPresent(Description.class);
*
* 3:获取类上自定义注解实例,需要强制类型转换。
* Description d =(Description)c.getAnnotation(Description.class);
*/
public class ParseAnno {
public static void main(String[] args) {
try {
Class<?> c = Class.forName("com.okgo.spring.annotation.Child");
boolean present = c.isAnnotationPresent(Description.class);
if (present) {
Description descAnno = c.getAnnotation(Description.class);
System.out.println(descAnno.desc());
}
Method[] methods = c.getMethods();
for (Method method : methods) {
boolean b = method.isAnnotationPresent(Description.class);
if (b) {
Description desAnno = method.getAnnotation(Description.class);
System.out.println(desAnno.desc());
}
}
//
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof Description) {
Description desc = (Description) annotation;
System.out.println(desc.desc());
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
3.3 最佳实践
// User 实体类,对应数据库表user
@Table("user")
public class User {
@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;
}
}
// 两个自定义注解
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)
public @interface Table {
String value();
}
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)
public @interface Column {
String value();
}
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
User u1 = new User();
u1.setId(10);
User u2 = new User();
u2.setUserName("lucy");
User u3 = new User();
u3.setEmail("1@gmail.com,2@gmail.com,3@gmail.com");
String sql1 = query(u1);
String sql2 = query(u2);
String sql3 = query(u3);
System.out.println(sql1);
System.out.println(sql2);
System.out.println(sql3);
}
private static String query(User u) {
StringBuilder sb = new StringBuilder();
//1. 获取到class
Class<? extends User> cls = u.getClass();
//2. 获取到table name
boolean present = cls.isAnnotationPresent(Table.class);
if (!present) {
return null;
}
Table table = cls.getAnnotation(Table.class);
String tableName = table.value();
sb.append("select * from ").append(tableName).append(" where 1 = 1");
//3. 遍历所有字段
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
//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 fieldValve = null;
try {
Method getMethod = cls.getMethod(getMethodName);
fieldValve = getMethod.invoke(u);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
//4.3 拼装SQL
if (fieldValve == null || (fieldValve instanceof Integer && (Integer)fieldValve == 0)) continue;
sb.append("and").append(columnName);
if (fieldValve instanceof String) {
if (((String) fieldValve).contains(",")) {
String[] strings = ((String) fieldValve).split(",");
sb.append(" in (");
for (String v : strings) {
sb.append("'").append(v).append("'").append(",");
}
sb.deleteCharAt(sb.length()-1).append(")");
}else {
sb.append(" = ").append("'").append(fieldValve).append("'");
}
}
sb.append(" = ").append(fieldValve);
}
return sb.toString();
}
}