Java注解实践
Java注解实践
标签 : Java基础
注解对代码的语意没有直接影响, 他们只负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但可以通过工具对被注解的代码进行特殊处理.
JDK 基本Annotation
| 注解 | 说明 |
|---|---|
@Override |
重写 |
@Deprecated |
已过时 |
@SuppressWarnings(value = "unchecked") |
压制编辑器警告 |
@SafeVarargs |
修饰”堆污染”警告 |
@FunctionalInterface |
Java8特有的函数式接口 |
value特权
如果使用注解时只需要为value成员变量指定值, 则使用注解时可以直接在该注解的括号中指定value值, 而无需使用name=value的形式. 如@SuppressWarnings("unchecked")(SuppressWarnings的各种参数
请参考解析 @SuppressWarnings的各种参数)- 请坚持使用
@Override注解: 如果在每个方法中使用Override注解来声明要覆盖父类声明, 编译器就可以替你防止大量的错误.
JDK 元Annotation
元
Annotation用于修饰其他的Annotation定义.
| 元注解 | 释义 |
|---|---|
@Retention |
注解保留策略 |
@Target |
注解修饰目标 |
@Documented |
注解文档提取 |
@Inherited |
注解继承声明 |
@Retention注解的保留策略
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
value为SOURCE, CLASS, RUNTIME三值之一:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
@Target指定Annotation可以放置的位置(被修饰的目标)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE
}
@Documented指定被修饰的该Annotation可以被javadoc工具提取成文档.@Inherited指定被修饰的Annotation将具有继承性
如果某个类使用@Xxx注解(该Annotation使用了@Inherited修饰)修饰, 则其子类自动被@Xxx注解修饰.
Annotation
/**
* Created by jifang on 15/12/22.
*/
@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable {
}
Client
public class Client {
@Test
public void client(){
new SubClass();
}
}
@Testable
class SupperClass{
}
class SubClass extends SupperClass{
public SubClass() {
for (Annotation annotation : SubClass.class.getAnnotations()){
System.out.println(annotation);
}
}
}
自定义注解
- 根据
Annotation是否包含成员变量,可以把Annotation分为两类:- 标记
Annotation: 没有成员变量的Annotation; 这种Annotation仅利用自身的存在与否来提供信息; - 元数据
Annotation: 包含成员变量的Annotation; 它们可以接受(和提供)更多的元数据;
- 标记
- 定义新注解使用
@interface关键字, 其定义过程与定义接口非常类似(见上面的@Testable), 需要注意的是:Annotation的成员变量在Annotation定义中是以无参的方法形式来声明的, 其方法名和返回值类型定义了该成员变量的名字和类型, 而且我们还可以使用default关键字为这个成员变量设定默认值.
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Tag {
String name() default "该叫啥才好呢?";
String description() default "这家伙很懒, 啥也没留下...";
}
- 自定义的Annotation继承了
Annotation这个接口, 因此自定义注解中包含了Annotation接口中所有的方法;
public interface Annotation {
/**
* @return true if the specified object represents an annotation
* that is logically equivalent to this one, otherwise false
*/
boolean equals(Object obj);
/**
* @return the hash code of this annotation
*/
int hashCode();
/**
* @return a string representation of this annotation
*/
String toString();
/**
* Returns the annotation type of this annotation.
*/
Class<? extends Annotation> annotationType();
}
提取Annotation信息
- 使用
Annotation修饰了类/方法/成员变量等之后,这些Annotation不会自己生效,必须由这些注解的开发者提供相应的工具来提取并处理Annotation信息(当然,只有当定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)修饰,JVM才会在装载class文件时提取保存在class文件中的Annotation,该Annotation才会在运行时可见,这样我们才能够解析). - Java使用
Annotation接口来代表程序元素前面的注解, 用AnnotatedElement接口代表程序中可以接受注解的程序元素.像ClassConstructorFieldMethodPackage这些类都实现了AnnotatedElement接口.
public final
class Class<T> implements java.io.Serializable,
java.lang.reflect.GenericDeclaration,
java.lang.reflect.Type,
java.lang.reflect.AnnotatedElement {
...
}
public interface AnnotatedElement {
/**
* Returns true if an annotation for the specified type
* is present on this element, else false. This method
* is designed primarily for convenient access to marker annotations.
*/
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
/**
* Returns this element's annotation for the specified type if
* such an annotation is present, else null.
*/
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
/**
* Returns all annotations present on this element.
*/
Annotation[] getAnnotations();
/**
* Returns all annotations that are directly present on this
* element. Unlike the other methods in this interface, this method
* ignores inherited annotations. (Returns an array of length zero if
* no annotations are directly present on this element.) The caller of
* this method is free to modify the returned array; it will have no
* effect on the arrays returned to other callers.
*/
Annotation[] getDeclaredAnnotations();
}
这样, 我们只需要获取到Class Method Filed等这些实现了AnnotatedElement接口的类实例, 就可以获取到我们想要的注解信息了.
/**
* Created by jifang on 15/12/22.
*/
public class Client {
@Test
public void client() throws NoSuchMethodException {
Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.annotationType().getName());
}
}
}
如果需要获取某个注解中的元数据,则需要强转成所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据:
@Tag(name = "client")
public class Client {
@Test
public void client() throws NoSuchMethodException {
Annotation[] annotations = this.getClass().getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof Tag) {
Tag tag = (Tag) annotation;
System.out.println("name: " + tag.name());
System.out.println("description: " + tag.description());
}
}
}
}
模拟Junit框架
我们用@Testable标记哪些方法是可测试的, 只有被@Testable修饰的方法才可以被执行.
/**
* Created by jifang on 15/12/27.
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable {
}
如下定义TestCase测试用例定义了6个方法, 其中有4个被@Testable修饰了:
public class TestCase {
@Testable
public void test1() {
System.out.println("test1");
}
public void test2() throws IOException {
System.out.println("test2");
throw new IOException("我test2出错啦...");
}
@Testable
public void test3() {
System.out.println("test3");
throw new RuntimeException("我test3出错啦...");
}
public void test4() {
System.out.println("test4");
}
@Testable
public void test5() {
System.out.println("test5");
}
@Testable
public void test6() {
System.out.println("test6");
}
}
为了让程序中的这些注解起作用, 必须为这些注解提供一个注解处理工具.
/**
* Created by jifang on 15/12/27.
*/
public class TestableProcessor {
public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
int passed = 0;
int failed = 0;
Object obj = Class.forName(clazz).newInstance();
for (Method method : Class.forName(clazz).getMethods()) {
if (method.isAnnotationPresent(Testable.class)) {
try {
method.invoke(obj);
++passed;
} catch (IllegalAccessException | InvocationTargetException e) {
System.out.println("method " + method.getName() + " execute error: < " + e.getCause() + " >");
e.printStackTrace(System.out);
++failed;
}
}
}
System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
TestableProcessor.process("com.feiqing.annotation.TestCase");
}
}
抛出特定异常
前面介绍的只是一个标记Annotation,程序通过判断Annotation是否存在来决定是否运行指定方法,现在我们要针对只在抛出特殊异常时才成功添加支持,这样就用到了具有成员变量的注解了:
/**
* Created by jifang on 15/12/28.
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestableException {
Class<? extends Throwable>[] value();
}
- TestCase
/**
* Created by jifang on 15/12/27.
*/
public class TestCase {
public void test1() {
System.out.println("test1");
}
@TestableException(ArithmeticException.class)
public void test2() throws IOException {
int i = 1 / 0;
System.out.println(i);
}
@TestableException(ArithmeticException.class)
public void test3() {
System.out.println("test3");
throw new RuntimeException("我test3出错啦...");
}
public void test4() {
System.out.println("test4");
}
@TestableException({ArithmeticException.class, IOException.class})
public void test5() throws FileNotFoundException {
FileInputStream stream = new FileInputStream("xxxx");
}
@Testable
public void test6() {
System.out.println("test6");
}
}
- 注解处理器
public class TestableExceptionProcessor {
public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
int passed = 0;
int failed = 0;
Object obj = Class.forName(clazz).newInstance();
for (Method method : Class.forName(clazz).getMethods()) {
if (method.isAnnotationPresent(TestableException.class)) {
try {
method.invoke(obj, null);
// 没有抛出异常(失败)
++failed;
} catch (InvocationTargetException e) {
// 获取异常的引发原因
Throwable cause = e.getCause();
int oldPassed = passed;
for (Class excType : method.getAnnotation(TestableException.class).value()) {
// 是我们期望的异常类型之一(成功)
if (excType.isInstance(cause)) {
++passed;
break;
}
}
// 并不是我们期望的异常类型(失败)
if (oldPassed == passed) {
++failed;
System.out.printf("Test <%s> failed <%s> %n", method, e);
}
}
}
}
System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
process("com.feiqing.annotation.TestCase");
}
}
注解添加监听器
下面通过使用Annotation简化事件编程, 在传统的代码中总是需要通过addActionListener方法来为事件源绑定事件监听器:
/**
* Created by jifang on 15/12/27.
*/
public class SwingPro {
private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
private JButton ok = new JButton("确定");
private JButton cancel = new JButton("取消");
public void init() {
JPanel jp = new JPanel();
// 为两个按钮设置监听事件
ok.addActionListener(new OkListener());
cancel.addActionListener(new CancelListener());
jp.add(ok);
jp.add(cancel);
mainWin.add(jp);
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.pack();
mainWin.setVisible(true);
}
public static void main(String[] args) {
new SwingPro().init();
}
}
class OkListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "你点击了确认按钮!");
}
}
class CancelListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "你点击了取消按钮!");
}
}
下面我们该用注解绑定监听器:
- 首先, 我们需要自定义一个注解
/**
* Created by jifang on 15/12/27.
*/
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor {
Class<? extends ActionListener> listener();
}
- 然后还要一个注解处理器
/**
* Created by jifang on 15/12/27.
*/
public class ActionListenerInstaller {
public static void install(Object targetObject) throws IllegalAccessException, InstantiationException {
for (Field field : targetObject.getClass().getDeclaredFields()) {
// 如果该成员变量被ActionListenerFor标记了
if (field.isAnnotationPresent(ActionListenerFor.class)) {
// 设置访问权限
field.setAccessible(true);
// 获取到成员变量的值
AbstractButton targetButton = (AbstractButton) field.get(targetObject);
// 获取到注解中的Listener
Class<? extends ActionListener> listener = field.getAnnotation(ActionListenerFor.class).listener();
// 添加到成员变量中
targetButton.addActionListener(listener.newInstance());
}
}
}
}
- 主程序(注意注释处)
public class SwingPro {
private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
/**
* 使用注解设置Listener
*/
@ActionListenerFor(listener = OkListener.class)
private JButton ok = new JButton("确定");
@ActionListenerFor(listener = CancelListener.class)
private JButton cancel = new JButton("取消");
public SwingPro init() {
JPanel jp = new JPanel();
// 使得注解生效
try {
ActionListenerInstaller.install(this);
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace(System.out);
}
jp.add(ok);
jp.add(cancel);
mainWin.add(jp);
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.pack();
mainWin.setVisible(true);
return this;
}
//下同
}
重复注解
在Java5到Java7这段时间里, 同一个程序元素前只能使用一个相同类型的Annotation; 如果需要在同一个元素前使用多个相同的Annotation, 则必须使用Annotation容器(在Java8中, 对这种情况做了改善, 但其实也只是一种写法上的简化, 其本质还是一样的).由于在实际开发中,Java8还未大面积的使用, 因此在此只介绍Java7中重复注解定义与使用.
- Table Annotation定义(代表数据库表)
/**
* Created by jifang on 15/12/27.
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String name() default "表名是啥?";
String description() default "这家伙很懒, 啥也没留下...";
}
- Table 容器
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Tables {
Table[] value();
}
注意: 容器注解的保留期必须比它所包含的注解的保留期更长, 否则JVM会丢弃容器, 相应的注解也就丢失了.
- Client
使用时需要用Table容器来盛装Table注解
@Tables({
@Table(name = "t_user", description = "用户表"),
@Table(name = "t_feed", description = "动态表")
})
public class Client {
@Test
public void client() {
Tables tableArray = this.getClass().getAnnotation(Tables.class);
Table[] tables = tableArray.value();
for (Table table : tables) {
System.out.println(table.name() + " : " + table.description());
}
}
}
在Java8中, 可以直接使用
@Table(name = "t_user", description = "用户表")
@Table(name = "t_feed", description = "动态表")
的形式来注解Client, 但@Tables还是需要开发者来写的, 由此可以看出, 重复注解只是一种简化写法, 这种写法只是一种假象: 多个重复注解其实会被作为容器注解的value成员.
Java注解实践的更多相关文章
- Java注解实践--annotation学习三
注解对代码的语意没有直接影响, 他们只负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但可以通过工具对被注解的代码进行特殊处理. JDK 基本Annotation 注解 说明 @O ...
- attilax.java 注解的本质and 使用最佳实践(3)O7
attilax.java 注解的本质and 使用最佳实践(3)O7 1. 定义pojo 1 2. 建立注解By eclipse tps 1 3. 注解参数的可支持数据类型: 2 4. 注解处理器 2 ...
- 夯实Java基础系列15:Java注解简介和最佳实践
Java注解简介 注解如同标签 Java 注解概述 什么是注解? 注解的用处 注解的原理 元注解 JDK里的注解 注解处理器实战 不同类型的注解 类注解 方法注解 参数注解 变量注解 Java注解相关 ...
- JAVA 注解的几大作用及使用方法详解
JAVA 注解的几大作用及使用方法详解 (2013-01-22 15:13:04) 转载▼ 标签: java 注解 杂谈 分类: Java java 注解,从名字上看是注释,解释.但功能却不仅仅是注释 ...
- JAVA 注解的几大作用及使用方法详解【转】
java 注解,从名字上看是注释,解释.但功能却不仅仅是注释那么简单.注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解 ...
- Java注解处理器(转)
Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...
- Java注解处理器使用详解
在这篇文章中,我将阐述怎样写一个注解处理器(Annotation Processor).在这篇教程中,首先,我将向您解释什么是注解器,你可以利用这个强大的工具做什么以及不能做什么:然后,我将一步一步实 ...
- Java注解处理器
Java注解处理器 2015/03/03 | 分类: 基础技术 | 0 条评论 | 标签: 注解 分享到:1 译文出处: race604.com 原文出处:Hannes Dorfmann Java ...
- 通过项目了解JAVA注解
java自定义注解实践 ² 背景 最近在为公司的技术改造做准备,我设计了一个提高Web开发效率的技术框架,为了增加框架的友好性和易用性,决定采用注解来代替配置文件,于是我查询了很多的资料,进行整理和学 ...
随机推荐
- window对象的属性方法名造成的命名冲突
事件起因: 一次开发中需要获取一个数组的长度,写下如此代码 function func(arr){ length = arr.length; ......//相关操作 } 程序在chrome下正常运行 ...
- Careercup - Facebook面试题 - 5177378863054848
2014-05-02 08:29 题目链接 原题: Write a function for retrieving the total number of substring palindromes. ...
- TI的AM3359的sd卡分区以及sd卡启动说明
[1]sd 卡分区: ti提供了自己的分区shell脚本create-sdcard.sh 脚本目录在:ti-sdk-am335x-evm-05.06.00.00/bin/ (1)插入sd卡(若是笔记 ...
- javascript小实例,PC网页里的拖拽(转)
这是现在的效果,可能改了一些,原来的效果是,里面的这张图是可以上下左右拖动的,然后房子上面的显示的楼栋号,也跟着图片一起移动,当时js能力还不行,未能实现项目经理的要求,不过后来项目经理又把这个效果推 ...
- js收集的一些好的题型
①: var a = {x:1},b=a;var a = a.z = {Y:2}; 求a和b的值? ②: var w = (function a(){return '1'},function b ...
- 【POJ】【2960】S-Nim
博弈论 这题跟 BZOJ 1874 取石子游戏 差不多 先暴力求出10000以内的SG函数(利用定义来求即可) 然后每次询问直接将SG值异或起来即可…… Source Code Problem: Us ...
- Deep Learning and Shallow Learning
Deep Learning and Shallow Learning 由于 Deep Learning 现在如火如荼的势头,在各种领域逐渐占据 state-of-the-art 的地位,上个学期在一门 ...
- linux源代码阅读笔记 find_entry分析
78 static struct buffer_head * find_entry(struct m_inode * dir, 79 const char * name, int namelen, s ...
- DJANGO变动库的一次真实手动经历
在变更库时,由于对字段规划和约束性没考虑完全,需要手工操作数据库,以便可以重复执行. 有以下三点要注意. 1,先迎合错误输出,增删对应的表或字段. 2,必要时,修改migrations文件,以去除唯一 ...
- hdu 4586 Play the Dice
思路:设期望值为s,前m个是再来一次机会,则有 s=(a[1]+s)/n+(a[2]+s)/n+……+(a[m]+s)/n+a[m+1]/n…… 化简:(n-m)s=sum 当sum=0时,为0: 当 ...