@自定义注解

1. Java 注解

JDK5.0之后引入的特性。

注解Annotation,用于描述元数据的修饰符,包括类、成员变量、构造方法、成员方法、方法参数和声明。
示例:

1
2
3
4
5
6
public class UserDaoImpl implements UserDao {
@Override /* Hey,快来看!这里有个注解 */
public void add() throws Exception {
System.out.println("add user...");
}
}

1.1 Java 注解作用

注解的作用:

  1. 生成文档或提供配置信息
  2. 跟踪代码依赖性
  3. 编译时进行格式检查

1.2 Java 注解分类

按类型分:

  • 标记注解:注解中没有属性可以设置,如 @Override、@Deprecated
  • 单值注解:注解中只有一个属性(“value=”可省略),如 @SuppressWarings{ String[] value; }
  • 完整注解:注解中有多个属性,如@WebServlet(name = “MyServlet”, urlPatterns = “/demo01”, initParams = {@WebInitParam(name = “root”, value = “1234”)})

按来源分:

  • JDK内置注解
  • 第三方注解(框架注解)
  • 自定义注解

1.3 Java 元注解 - 注解的注解

定义在自定义注解上,规定自定义的作用区域存活策略

  • @Target 规定自定义注解的作用区域(默认全区域)

    1
    2
    3
    4
    5
    6
    7
    public @interface Target {
    ElementType[] value();
    }
    // 使用
    @Target(ElementType.METHOD) // 该注解只能作用在方法上
    public @interface 自定义注解名 {
    }

    当前定义的注解可以应用的范围:

    • TYPE, 类、接口、枚举的类型定义上
    • FIELD, 成员变量上
    • METHOD, 成员方法上
    • PARAMETER, 方法参数上
    • CONSTRUCTOR, 构造方法上
    • LOCAL_VARIABLE, 局部变量上
    • ANNOTATION_TYPE, 注解类型上
    • PACKAGE, 包定义语句上
    • TYPE_PARAMETER, 类型参数声明
    • TYPE_USE, 使用一个类型
  • @Retention 规定自定义注解的存活策略(一般情况下设置RUNTIME)

    1
    2
    3
    4
    5
    6
    7
    public @interface Retention {
    RetentionPolicy value();
    }
    // 使用
    @Retention(RetentionPolicy.RUNTIME) // 运行时可被反射捕获该注解
    public @interface 自定义注解名 {
    }

    当前定义的注解可以存活的方式:

    • SOURCE, 仅仅停留在源码中,编译时即除去
    • CLASS, 保留到编译后的字节码中,运行时无法获取注解信息
    • RUNTIME, 保留到运行时,通过反射机制可以获取注解信息
  • @Documented 文档标记注解(无参数)
    将注解中的内容包含到Javadoc中。

    1
    public @interface Documented { }
  • @Inherited 可随修饰类被继承的标记注解(无参数)
    例如B继承了A,A添加了注解,那么B也会继承同样的注解。

    1
    public @interface Inherited { }

2. 自定义注解

格式:(default 默认值可省略)

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
public @interface 自定义注解名 {
数据类型 属性名1() default 默认值1;
数据类型 属性名2() default 默认值2;
数据类型 属性名3()[] default {元素值1, 元素值2}; // 注解中的数组
}

eg:

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String username() default "root";
int age() default 18;
String citys()[] default {"北京", "上海", "广州", "深圳"};
}

3. 注解信息获取

核心步骤:——反射机制
注解在类/方法上,获取对应的 Class 对象 / Method 对象
判断类/方法上的注解是否存在 .isAnnotationPresent(注解名.class)
获取注解信息 .getAnnotation(注解名.class)

3.1 获取并执行自定义注解修饰的方法(标记注解)

步骤:
1.自定义注解
2.在测试类上使用该自定义注解
3.让自定义注解生效
● 获取测试类的Class对象
● 获取测试类的成员方法
● 判断方法上是否有自定义注解
● 执行添加了注解的方法

自定义注解:

1
2
3
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}

使用自定义注解修饰方法:

1
2
3
4
5
class Test01 {
@MyTest
public void t1() { System.out.println("t1 t1..."); }
public void t2() { System.out.println("t2 t2..."); }
}

调用执行自定义注解修饰的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestMyTest {
public static void main(String[] args) {
/* 使用反射去扫描 Test01 中有哪些方法有 @MyTest注解 */
// 1.获取 Class 对象
Class<Test01> clazz = Test01.class;
// 2.获取类里成员方法(@Test注解的方法被强制public且无参)
Method[] methods = clazz.getMethods();
Arrays.stream(methods).forEach(method -> {
// method 就是单个方法对象
// 3.判断方法上的注解
boolean flag = method.isAnnotationPresent(MyTest.class);
//System.out.println(method.getName() + ":" + flag);
if (flag) {
try {
method.invoke(clazz.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}

运行结果:
Java注解

3.2 获取注解参数信息用于数据库初始化(完整注解)

步骤:
1.自定义注解@JDBCInfo
2.在DBUtils工具类上使用@JDBCInfo
3.在DBUtils工具类的静态代码中获取注解中的属性(反射读取注解信息)

自定义注解:

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
public @interface JDBCInfo {
String driverClass() default "com.mysql.jdbc.Driver";
String url() default "jdbc:mysql://localhost:3306/demo";
String username() default "root";
String password() default "1234";
}

JDBC工具类中获取注解默认值信息(覆盖了密码值):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@JDBCInfo(password = "123456")
public class DBUtils {
private static final String DRIVERCLASS;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;

static {
Class<DBUtils> dbClass = DBUtils.class;
boolean present = dbClass.isAnnotationPresent(JDBCInfo.class);
if (present) {
JDBCInfo jdbcInfo = dbClass.getAnnotation(JDBCInfo.class);
DRIVERCLASS = jdbcInfo.driverClass();
URL = jdbcInfo.url();
USERNAME = jdbcInfo.username();
PASSWORD = jdbcInfo.password();
System.out.println(DRIVERCLASS + " " + URL + " " + USERNAME + " " + PASSWORD);
} else {
// 没有注解从 Properties 中取
Properties properties = new Properties();
InputStream is = DBUtils.class.getClassLoader().getResourceAsStream("database.properties");
try {
properties.load(is);
} catch (Exception e) {
e.printStackTrace();
}
DRIVERCLASS = properties.getProperty("driver");
URL = properties.getProperty("url");
USERNAME = properties.getProperty("username");
PASSWORD = properties.getProperty("password");
}
}

public static void loadDriver() throws Exception {
Class.forName(DRIVERCLASS);
}

public static Connection getConnection() throws Exception {
return DriverManager.getConnection(URL, USERNAME, PASSWORD);
}
}

测试类调用:

1
2
3
4
5
public class TestJDBCInfo {
public static void main(String[] args) throws Exception {
new DBUtils();
}
}

运行结果:
Java注解

4. 注解+反射+动态代理设计模式 实现【日志记录】

① 自定义注解:

1
2
@Retention(RetentionPolicy.RUNTIME)
public @interface SysLog { }

② UserDao接口:

1
2
3
4
5
6
7
8
public interface UserDao {
@SysLog
void add();
@SysLog
void del();
void upd();
void get();
}

③ UserDaoImpl实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserDaoImpl implements UserDao {
@Override
public void add() { System.out.println("add method executed..."); }

@Override
public void del() { System.out.println("del method executed..."); }

@Override
public void upd() { System.out.println("upd method executed..."); }

@Override
public void get() { System.out.println("get method executed..."); }
}

④ 动态代理设计模式 - 测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestSysLog {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
UserDao p = (UserDao) Proxy.newProxyInstance(
userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
(proxy, method, argss) -> {
// 1.检查方法上是否有对应注解
boolean present = method.isAnnotationPresent(SysLog.class);
if (present) {
String className = UserDao.class.getName();
String methodName = method.getName();
// 2.拼接日志需要的时间和必要信息
String currTimeStr = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss").format(new Date());
System.out.println("[" + currTimeStr + "]<" + className + "> " + methodName + "():");
}
// 3.打印日志(有注解) + 执行原方法(有/无注解)
return method.invoke(userDao);
}
);
p.add();
p.del();
p.upd();
p.get();
}
}

运行结果:
Java注解

5. 常用注解说明

5.1 JDK 内置注解

  • @Override 方法覆盖注解
    判断子类中的方法是否是一个父类方法的重写方法。

  • @Deprecated 过时标记注解
    标记一个类/方法/变量是否是过时的,调用时会有删除线。

  • @SupressWarning("all") 压制警告注解
    可以消除编译器/开发工具烦人的警告的提示,一般给 all 即可。

  • @FunctionaInterface 函数式接口注解
    该方法只有1个公开抽象方法,用于 Lambda 表达式简写匿名内部类。

  • @SafeVarargs 安全的可变参数注解
    jdk1.7引入的适用于可变参数与泛型能够更好结合的一个注解,也就是说如果你认为你的方法或者构造方法是类型安全的,那么你也就可以使用 @SafeVarargs 来跳过@SuppressWarnings(“unchecked”) 检查。

5.2 Junit 单元测试注解

  • @Test 单元测试注解
    该注解的方法被强制public且无参,可以直接运行被改注解修饰的方法,底层还是 main 方法运行(通过反射执行)。

  • @Ignore 忽略测试注解
    暂时不运行某些测试方法\测试类,可以在方法前加上这个注解。(不建议经常这么做)

  • @BeforeClass 优先测试注解
    在测试类里所有用例运行之前,运行一次这个方法。例如创建数据库连接、读取文件等。

还有其他的,比较简单,暂不赘述,随用随查即可。

5.3 Servlet 配置注解

  • @WebServlet web资源配置注解
    Servlet3.0+版本支持,可以替代web.xml配置。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @WebServlet(
    name = "TestWebServlet", // 设置 Servlet 的映射名字(通常与类名一致)
    /*value = {"/demo", "/web"},*/
    urlPatterns = {"/demo01", "/web01"}, // 设置 Servlet 访问资源名(value同urlPatterns)
    loadOnStartup = 1, // 设置 Servlet 启动优先级
    initParams = { // 设置 Servlet 启动参数
    @WebInitParam(name = "username", value = "root"),
    @WebInitParam(name = "password", value = "123456"),
    }
    )

5.4 其他框架注解

——更多第三方注解后续汇总…

6. xml与注解优劣对比

xml
优点

  1. 降低耦合,使容易扩展
  2. 对象之间的关系一目了然
  3. xml配置文件比注解功能齐全

缺点

  1. 配置文件配置工作量相对注解要大

注解
优点

  1. 在class文件中,可以降低维护成本
  2. 提高开发效率

缺点

  1. 如果对注解文件(Annotation)进行修改,需要重新编译整个工程
  2. 业务类之间的关系不如xml配置那样一目了然
  3. 程序中过多的注解,对于代码的简洁度有一定影响
  4. 注解功能没有xml配置文件齐全

@自定义注解
https://janycode.github.io/2017/05/22/08_框架技术/02_Spring/06_Servlet/@自定义注解/
作者
Jerry(姜源)
发布于
2017年5月22日
许可协议