04-@自定义注解

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配置文件齐全

04-@自定义注解
https://janycode.github.io/2017/05/22/04_网页技术/04_Servlet/04-@自定义注解/
作者
Jerry(姜源)
发布于
2017年5月22日
许可协议