java学习脚印: 反射与注释(Annotation)
1.为什么使用注释——作用与应用场合
提示: 注释的处理部分,需要利用java反射机制,如果感觉困难,请参见我的另一篇博客《java学习脚印:Class类与反射机制》。
1.1 注释的引入
注释(Annontation,也译作注解)是那些插入到源代码中用于工具处理的标签。这些标签可以在与源代码层次上进行处理,或者可以通过编译器将它们纳入到类文件中。
值得注意的是除了少数系统提供的注释外,注释不会改变对编写的程序的编译方式;注释并不直接影响程序的执行,需要利用相关的注释处理工具来对注释进行分析,例如第三方框架,这在Hibernate,struts等框架中经常使用。
1.2 注释的应用场合
注释像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
下面是注释可能使用的场合:
- 附属文件的自动生成,例如部署描述符或者bean信息类,例如最新的servlet有一个注释如:
@WebServlet(description = "first servlet testing", urlPatterns = { "/HelloWorld" })可以用来映射servlet类文件和url路径。
- 测试,日志,事物语义等代码的自动生成。例如Junit测试框架中经常使用的@Test来标记将某个方法加入到测试用例中。
1.3 初识注释的例子——不做细致探讨,直观感受即可
例子AnnotationDemo1给出一个JAXB(ava API for XML Processing,提供解析和验证XML文档的能力)根据注释,自动将一个类序列化和反序列化的例子(改编自:http://www.vogella.com/tutorials/JAXB/article.html),这里不要求去理解JAXB的细节,只是来直观感受下注释。
清单1.3-1 Book.java
package com.learningjava;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* original author: Lars Vogel
* link: http://www.vogella.com/tutorials/JAXB/article.html
*/
@XmlRootElement(name = "书籍")
@XmlType(propOrder = { "name", "author","isbn","publisher" })
public class Book {
// change this name for your XML-Output:
@XmlElement(name = "书名")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlElement(name = "作者")
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@XmlElement(name = "出版社")
public String getPublisher() {
return publisher;
}
public void setPublisher(String publisher) {
this.publisher = publisher;
}
@XmlElement(name = "国际标准书号")
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
private String name;
private String author;
private String publisher;
private String isbn;
}
清单1.3-2 AnnotationDemo1.java
package com.learningjava;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
/**
* JAXB using annotation to serialize object to xml file
* original author: Lars Vogel
* link: http://www.vogella.com/tutorials/JAXB/article.html
*/
public class AnnotationDemo1 {
public static void main(String[] args) throws JAXBException, IOException {
final String BOOK_XML = "./books.xml";
// create book
Book book1 = new Book();
book1.setIsbn("978-0060554736");
book1.setName("The Game");
book1.setAuthor("Neil Strauss");
book1.setPublisher("Harpercollins");
// create JAXB context and instantiate marshaller
JAXBContext context = JAXBContext.newInstance(Book.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
// Write to System.out
m.marshal(book1, System.out);
// Write to File
m.marshal(book1, new File(BOOK_XML));
// reconstruct Book object from our xml file
System.out.println();
System.out.println("Output from our XML File: ");
Unmarshaller um = context.createUnmarshaller();
Book book2 = (Book) um.unmarshal(new FileReader(BOOK_XML));
System.out.println("Book: " + book2.getName() + " from "
+ book2.getAuthor());
}
}
输出结果:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<书籍>
<书名>The Game</书名>
<作者>Neil Strauss</作者>
<国际标准书号>978-0060554736</国际标准书号>
<出版社>Harpercollins</出版社>
</书籍>
Output from our XML File:
Book: The Game from Neil Strauss
2.注释的定义与类型
2.1 注释的定义
一个注释是有一个注释接口定义的。注释定义的模板如下:
modifiers @interface AnnotationName {
element declaration1
element declaration2
}
其中元素声明具有这种形式: type elementName() default value;
每个元素的声明类似与方法的声明,都有名称和类型,注释元素的类型为下列之一(来自:《java核心技术卷二》机工第八版):
- 基本类型
- 一个String
- 一个Class,具有一个可供选择的参数,如Class<? extends MyClass>
- 一个enum
- 一个注释类型
- 一个如前所述类型的数组
注释一个项目时,提供默认值的元素,可以不用提供;只具有一个元素的注释,元素名也可以省去,这种注释称为单值注释。
这里定义一个简单注释如下(来自: http://tutorials.jenkov.com/java/annotations.html):
public @interface MyAnnotation {
String value();
String name();
int age();
String[] newNames();
}
2.2 注释类型
注释分为两种,标准注释和自定义注释。
2.2.1标准注释
标准注释分为三个正规注释为编译器提供指示,还有四个注释是元注释(meta annotation,所谓元注释就是关于注释的注释,类似于数据库中的元数据)。
三个正规注释分别是(转载自: 51 cto zhao_xiao_long 的BLOG Java注解annotation用法和自定义注解处理器):
- @Override,表示当前的方法定义覆盖了父类中的方法。必须要有相同的方法签名即(方法名,参数类型,参数顺序,参数个数)都一样。否则在编译过程中发出错误提示。
- @Deprecated,对不应该再使用的方法添加注释,当使用这个方法的时候,会在编译时候显示提示信息。
- @SuppressWarnings,关闭不当的编译器报警信息。
这个大家用的很熟悉.
重点记下元注释,元注释如下:
- @Target,表示该注解可以用什么地方。
包括8个地方:
- ElementType.ANNOTATION_TYPE 用在注释类型声明
- ElementType.CONSTRUCTOR 用在构造器
- ElementType.FIELD 用在域
- ElementType.LOCAL_VARIABLE 局部变量
- ElementType.METHOD 方法
- ElementType.PACKAGE 包
- ElementType.PARAMETER 方法或构造器参数
- ElementType.TYPE 类(包括enum)及接口(包括注释类型)
-
@Retention,表示需要在什么级别保存该注释信息。
SOURCE,进在源代码中,不包括在类文件和运行时;
CLASS,注释保留在类文件中,但运行时不可获得。
RUNTIME,类文件中的注释,并由虚拟机载入,可以通过反射机制获得。
-
@Documented,将此注释包含到Javadoc中。
-
@Inherited,允许子类继承父类的注释。
继承注释示例如下:
java.lang.annotation.Inherited
@Inherited
public @interface MyAnnotation {
}
@MyAnnotation
public class MySuperClass { ... }
public class MySubClass extends MySuperClass { ... }
这里由于MySuperClass定义了MyAnnotation注释,并且MyAnnotation注释具有继承属性,因此MySubClass也将具有MyAnnotation注释。
2.2.2 自定义注释
自定义注释需要做两个工作,按照上面注释定义所述标准定义你所需的注释,第二提供处理注释的工具,这种工具可以自己编写,也可以由其他方式获取。在综合案例部分我们给出一个自己定义的注释,来国际化菜单和工具栏。
3.综合案例——注释实现菜单及提示的国际化
通常要实现菜单和工具栏的国际化,需要获取不用语言包中的指定字符串,这里我自定义一个注释MenuItemAndAction来实现这一功能(尚未研究更科学的方法,这里提供一个参考方式,欢迎指教)。
通过选择语言组合框中一种,即可切换界面语言,运行结果如下:
英文界面:
中文界面:
法文界面:
对这一实例感兴趣的,可以到下载我的资源中去下载,此文也附上代码清单(编译工程时将语言包放到与src目录下即可)。注意重点理解注释的定义(MenuItemAndAction.java )和处(MenuItemAndActionProcessor)两个部分,关于国际化部分了解即可。
清单3-1 MenuItemAndAction.java 定义注释
package com.learningjava;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
/**
* this annotation used to set name and tooltip for MenuItem or action
* @author wangdq
* 2014-2-27
*/
public @interface MenuItemAndAction {
String name();
String tooltip() default "";
}
清单3-2 MenuItemAndActionProcessor.java 定义处理注释的工具类
package com.learningjava;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.Action;
/**
* Annotation processor
* set name and tooltip for menu or action
* @author wangdq
* 2014-2-27
*/
public class MenuItemAndActionProcessor {
/**
* annotation processer
* @param obj the object which to process
*/
public static void processAnnotation(Object obj) {
Class<? extends Object> cl = obj.getClass();
//get all fields
Field[] fields = cl.getDeclaredFields();
for(Field field: fields) {
//get the specified annotation
MenuItemAndAction annotation = field.getAnnotation(MenuItemAndAction.class);
if(annotation != null) {
if(!field.isAccessible()) {
field.setAccessible(true);
}
//set the name and tooltip for the menu or action
String name = resourceBundle.getString(annotation.name());
String tooltip = "";
if(!annotation.tooltip().isEmpty()) {
tooltip = resourceBundle.getString(annotation.tooltip());
}
try {
if(field.getType() == JMenuItem.class || field.getType() == JMenu.class) {
JMenuItem menuItem = (JMenuItem)field.get(obj);
menuItem.setText(name);
System.out.println(name);
menuItem.setToolTipText(tooltip);
}else if(field.getType() == Action.class){
Action action = (Action)field.get(obj);
action.putValue(Action.NAME, name);
action.putValue(Action.SHORT_DESCRIPTION, tooltip);
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
//change the resource bundle according to the locale
public static void setResourceBundle(Locale locale) {
resourceBundle = ResourceBundle.getBundle("textResource",locale);
}
private static ResourceBundle resourceBundle;
static {
resourceBundle = ResourceBundle.getBundle("textResource");
}
}
清单3-3 MainFrame.java 提供菜单和工具栏的Frame类
package com.learningjava;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Locale;
import javax.swing.*;
/**
* Test frame with menu and toolbar
* @author wangdq
* 2014-2-27
*/
public class MainFrame extends JFrame{
public MainFrame() {
this.setTitle("AnnotationDemo");
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
textArea = new JTextArea(10,10);
menuAndToolBar = new MenuAndToolbar(textArea);
panel.add(textArea);
this.add(panel,BorderLayout.CENTER);
//set menu
JMenuBar menubar = new JMenuBar();
menubar.add(menuAndToolBar.getFileMenu());
this.setJMenuBar(menubar);
//set toolbar
JToolBar toolbar = menuAndToolBar.getToolbar();
toolbar.setFloatable(false);
this.add(toolbar,BorderLayout.NORTH);
//init text
MenuItemAndActionProcessor.processAnnotation(menuAndToolBar);
}
private JTextArea textArea;
private MenuAndToolbar menuAndToolBar;
private static final long serialVersionUID = 1L;
}
/**
* menu and toolbar to using annotation on the field
* @author wangdq
* 2014-2-27
*/
class MenuAndToolbar {
public MenuAndToolbar(JTextArea textArea) {
this.textArea = textArea;
initMenuAndToolBar();
}
public JMenu getFileMenu() {
return fileMenu;
}
public JToolBar getToolbar() {
return toolbar;
}
private void initMenuAndToolBar() {
fileMenu = new JMenu();
toolbar = new JToolBar();
//new file
actNew = new AbstractAction(){
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
textArea.setText("new");
}
private static final long serialVersionUID = 1L;
};
fileMenu.add(new JMenuItem(actNew));
toolbar.add(actNew);
//open file
actOpen = new AbstractAction(){
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
textArea.setText("open");
}
private static final long serialVersionUID = 1L;
};
fileMenu.add(new JMenuItem(actOpen));
toolbar.add(actOpen);
//save file
actSave = new AbstractAction(){
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
textArea.setText("save");
}
private static final long serialVersionUID = 1L;
};
fileMenu.add(new JMenuItem(actSave));
toolbar.add(actSave);
fileMenu.addSeparator();
toolbar.addSeparator();
//exit
actExit = new AbstractAction(){
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.exit(0);
}
private static final long serialVersionUID = 1L;
};
fileMenu.add(new JMenuItem(actExit));
toolbar.add(actExit);
//languange options combobox
JComboBox<String> languangeBox = new JComboBox<String>
(new String[]{"en_US","zh_CN","fr_FR"});
languangeBox.addItemListener(new ItemListener(){
@Override
public void itemStateChanged(ItemEvent e) {
// TODO Auto-generated method stub
@SuppressWarnings("unchecked")
JComboBox<String> box = (JComboBox<String>)e.getSource();
String text = (String)box.getSelectedItem();
String[] str = text.split("\\_");
Locale locale = new Locale(str[0],str[1]);
System.out.println(locale.toString());
MenuItemAndActionProcessor.setResourceBundle(locale);
MenuItemAndActionProcessor.processAnnotation(MenuAndToolbar.this);
}
});
toolbar.add(languangeBox);
}
@MenuItemAndAction(name="New",tooltip="TipNew")
private Action actNew;
@MenuItemAndAction(name="Open",tooltip="TipOpen")
private Action actOpen;
@MenuItemAndAction(name="Save",tooltip="TipSave")
private Action actSave;
@MenuItemAndAction(name="Exit",tooltip="TipExit")
private Action actExit;
@MenuItemAndAction(name="File")
private JMenu fileMenu;
private JToolBar toolbar;
private JTextArea textArea;
}
清单3-4 ProgramDrive.java 测试驱动类
package com.learningjava;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
/**
* test the annotation
* @author wangdq
* 2014-2-27
*/
public class ProgramDrive {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
JFrame frame = new MainFrame();
frame.setSize(400, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
清单3-5 默认语言包 textResource.properties
File = File
New = New
TipNew = create a new file
Open = Open
TipOpen = open a file
Save = Save
TipSave = save the current file
Exit = Exit
TipExit = exit the system
清单3-6 中文语言包 textResource_zh_CN.properties
File = \u6587\u4EF6
New = \u65B0\u5EFA
TipNew = \u65B0\u5EFA\u4E00\u4E2A\u6587\u4EF6
Open = \u6253\u5F00
TipOpen = \u6253\u5F00\u4E00\u4E2A\u6587\u4EF6
Save = \u4FDD\u5B58
TipSave = \u4FDD\u5B58\u5F53\u524D\u6587\u4EF6
Exit = \u9000\u51FA
TipExit = \u9000\u51FA\u7CFB\u7EDF
清单3-7 法语语言包 textResource_fr_FR.properties
File = dossier
New = nouveau
TipNew = Créer un nouveau fichier
Open = Ouvrir
TipOpen = Ouvrir un fichier
Save = sauver
TipSave = Enregistrez le fichier en cours
Exit = quitter
TipExit = Quittez le système
清单3-8 英文语言包 textResource _en_US.properties
File = File
New = New
TipNew = create a new file
Open = Open
TipOpen = open a file
Save = Save
TipSave = save the current file
Exit = Exit
TipExit = exit the system
: