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
: