`

<转>java注解实例

 
阅读更多
java注解实例
2009-08-10 11:27
Java代码 复制代码
  1. package Test_annotation;   
  2.   
  3. import java.lang.annotation.Documented;   
  4. import java.lang.annotation.Inherited;   
  5. import java.lang.annotation.Retention;   
  6. import java.lang.annotation.Target;   
  7. import java.lang.annotation.ElementType;   
  8. import java.lang.annotation.RetentionPolicy;   
  9.   
  10. /*
  11. * 元注解@Target,@Retention,@Documented,@Inherited
  12. *
  13. *      @Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括:
  14. *          ElemenetType.CONSTRUCTOR 构造器声明
  15. *          ElemenetType.FIELD 域声明(包括 enum 实例)
  16. *          ElemenetType.LOCAL_VARIABLE 局部变量声明
  17. *          ElemenetType.METHOD 方法声明
  18. *          ElemenetType.PACKAGE 包声明
  19. *          ElemenetType.PARAMETER 参数声明
  20. *          ElemenetType.TYPE 类,接口(包括注解类型)或enum声明
  21. *         
  22. *      @Retention 表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括:
  23. *          RetentionPolicy.SOURCE 注解将被编译器丢弃
  24. *          RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃
  25. *          RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。
  26. *         
  27. *      @Documented 将此注解包含在 javadoc 中
  28. *     
  29. *      @Inherited 允许子类继承父类中的注解
  30. *   
  31. */  
  32. @Target(ElementType.METHOD)   
  33. @Retention(RetentionPolicy.RUNTIME)   
  34. @Documented  
  35. @Inherited  
  36. /*
  37. * 定义注解 Test
  38. * 注解中含有两个元素 id 和 description
  39. * description 元素 有默认值 "no description"
  40. */  
  41. public @interface Test {   
  42.     public int id();   
  43.     public String description() default "no description";   
  44. }  
package Test_annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

/*
 * 元注解@Target,@Retention,@Documented,@Inherited
 * 
 *     @Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括:
 *         ElemenetType.CONSTRUCTOR 构造器声明
 *         ElemenetType.FIELD 域声明(包括 enum 实例)
 *         ElemenetType.LOCAL_VARIABLE 局部变量声明
 *         ElemenetType.METHOD 方法声明
 *         ElemenetType.PACKAGE 包声明
 *         ElemenetType.PARAMETER 参数声明
 *         ElemenetType.TYPE 类,接口(包括注解类型)或enum声明
 *         
 *     @Retention 表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括:
 *         RetentionPolicy.SOURCE 注解将被编译器丢弃
 *         RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃
 *         RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。
 *         
 *     @Documented 将此注解包含在 javadoc 中
 *     
 *     @Inherited 允许子类继承父类中的注解
 *   
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/*
 * 定义注解 Test
 * 注解中含有两个元素 id 和 description
 * description 元素 有默认值 "no description"
 */
public @interface Test {
 public int id();
 public String description() default "no description";
}

下面是一个使用注解 和 解析注解的实例

 

Java代码 复制代码
  1. package Test_annotation;   
  2.   
  3. import java.lang.reflect.Method;   
  4.   
  5. public class Test_1 {   
  6.     /*
  7.       * 被注解的三个方法
  8.       */  
  9.     @Test(id = 1, description = "hello method_1")   
  10.     public void method_1() {   
  11.      }   
  12.   
  13.     @Test(id = 2)   
  14.     public void method_2() {   
  15.      }   
  16.   
  17.     @Test(id = 3, description = "last method")   
  18.     public void method_3() {   
  19.      }   
  20.   
  21.     /*
  22.       * 解析注解,将Test_1类 所有被注解方法 的信息打印出来
  23.       */  
  24.     public static void main(String[] args) {   
  25.          Method[] methods = Test_1.class.getDeclaredMethods();   
  26.         for (Method method : methods) {   
  27.             /*
  28.               * 判断方法中是否有指定注解类型的注解
  29.               */  
  30.             boolean hasAnnotation = method.isAnnotationPresent(Test.class);   
  31.             if (hasAnnotation) {   
  32.                 /*
  33.                   * 根据注解类型返回方法的指定类型注解
  34.                   */  
  35.                  Test annotation = method.getAnnotation(Test.class);   
  36.                  System.out.println("Test( method = " + method.getName()   
  37.                          + " , id = " + annotation.id() + " , description = "  
  38.                          + annotation.description() + " )");   
  39.              }   
  40.          }   
  41.      }   
  42.   
  43. }  
package Test_annotation;

import java.lang.reflect.Method;

public class Test_1 {
 /*
  * 被注解的三个方法
  */
 @Test(id = 1, description = "hello method_1")
 public void method_1() {
 }

 @Test(id = 2)
 public void method_2() {
 }

 @Test(id = 3, description = "last method")
 public void method_3() {
 }

 /*
  * 解析注解,将Test_1类 所有被注解方法 的信息打印出来
  */
 public static void main(String[] args) {
  Method[] methods = Test_1.class.getDeclaredMethods();
  for (Method method : methods) {
   /*
    * 判断方法中是否有指定注解类型的注解
    */
   boolean hasAnnotation = method.isAnnotationPresent(Test.class);
   if (hasAnnotation) {
    /*
     * 根据注解类型返回方法的指定类型注解
     */
    Test annotation = method.getAnnotation(Test.class);
    System.out.println("Test( method = " + method.getName()
      + " , id = " + annotation.id() + " , description = "
      + annotation.description() + " )");
   }
  }
 }

}
输出结果如下:
   Test( method = method_1 , id = 1 , description = hello method_1 )
   Test( method = method_2 , id = 2 , description = no description )
   Test( method = method_3 , id = 3 , description = last method )

在开发Java程序,尤其是Java EE应用的时候,总是免不了与各种配置文件打交道。以Java EE中典型的S(pring)S(truts)H(ibernate)架构来说,SpringStrutsHibernate这三个框架都有自己的XML格式的配置文件。这些配置文件需要与Java源代码保存同步,否则的话就可能出现错误。而且这些错误有可能到了运行时刻才被发现。把同一份信息保存在两个地方,总是个坏的主意。理想的情况是在一个地方维护这些信息就好了。其它部分所需的信息则通过自动的方式来生成。JDK 5中引入了源代码中的注解(annotation)这一机制。注解使得Java源代码中不但可以包含功能性的实现代码,还可以添加元数据。注解的功能类似于代码中的注释,所不同的是注解不是提供代码功能的说明,而是实现程序功能的重要组成部分。Java注解已经在很多框架中得到了广泛的使用,用来简化程序中的配置。

 

 

使用注解

在一般的Java开发中,最常接触到的可能就是@Override@SupressWarnings这两个注解了。使用@Override的时候只需要一个简单的声明即可。这种称为标记注解(marker annotation ),它的出现就代表了某种配置语义。而其它的注解是可以有自己的配置参数的。配置参数以名值对的方式出现。使用 @SupressWarnings的时候需要类似@SupressWarnings({"uncheck", "unused"})这样的语法。在括号里面的是该注解可供配置的值。由于这个注解只有一个配置参数,该参数的名称默认为value,并且可以省略。而花括号则表示是数组类型。在JPA中的@Table注解使用类似@Table(name = "Customer", schema = "APP")这样的语法。从这里可以看到名值对的用法。在使用注解时候的配置参数的值必须是编译时刻的常量。

从某种角度来说,可以把注解看成是一个XML元素,该元素可以有不同的预定义的属性。而属性的值是可以在声明该元素的时候自行指定的。在代码中使用注解,就相当于把一部分元数据从XML文件移到了代码本身之中,在一个地方管理和维护。

开发注解

在一般的开发中,只需要通过阅读相关的API文档来了解每个注解的配置参数的含义,并在代码中正确使用即可。在有些情况下,可能会需要开发自己的注解。这在库的开发中比较常见。注解的定义有点类似接口。下面的代码给出了一个简单的描述代码分工安排的注解。通过该注解可以在源代码中记录每个类或接口的分工和进度情况。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Assignment {
    String assignee();
    int effort();
    double finished() default 0;

@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型。可以通过default来声明参数的默认值。在这里可以看到@Retention@Target这样的元注解,用来声明注解本身的行为。@Retention用来声明注解的保留策略,有CLASSRUNTIMESOURCE这三种,分别表示注解保存在类文件、JVM运行时刻和源代码中。只有当声明为RUNTIME的时候,才能够在运行时刻通过反射API来获取到注解的信息。@Target用来声明注解可以被添加在哪些类型的元素上,如类型、方法和域等。

处理注解

在程序中添加的注解,可以在编译时刻或是运行时刻来进行处理。在编译时刻处理的时候,是分成多趟来进行的。如果在某趟处理中产生了新的Java源文件,那么就需要另外一趟处理来处理新生成的源文件。如此往复,直到没有新文件被生成为止。在完成处理之后,再对Java代码进行编译。JDK 5中提供了apt工具用来对注解进行处理。apt是一个命令行工具,与之配套的还有一套用来描述程序语义结构的Mirror API。Mirror API(com.sun.mirror.*)描述的是程序在编译时刻的静态结构。通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供相应的处理逻辑。具体的处理工作交给apt工具来完成。编写注解处理器的核心是AnnotationProcessorFactoryAnnotationProcessor两个接口。后者表示的是注解处理器,而前者则是为某些注解类型创建注解处理器的工厂。

以上面的注解Assignment为例,当每个开发人员都在源代码中更新进度的话,就可以通过一个注解处理器来生成一个项目整体进度的报告。 首先是注解处理器工厂的实现。

public class AssignmentApf implements AnnotationProcessorFactory {  
    public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds,? AnnotationProcessorEnvironment env) {
        if (atds.isEmpty()) {
           return AnnotationProcessors.NO_OP;
        }
        return new AssignmentAp(env); //返回注解处理器
    } 
    public Collection<String> supportedAnnotationTypes() {
        return Collections.unmodifiableList(Arrays.asList("annotation.Assignment"));
    }
    public Collection<String> supportedOptions() {
        return Collections.emptySet();
    }
}

AnnotationProcessorFactory接口有三个方法:getProcessorFor是根据注解的类型来返回特定的注解处理器;supportedAnnotationTypes是返回该工厂生成的注解处理器所能支持的注解类型;supportedOptions用来表示所支持的附加选项。在运行apt命令行工具的时候,可以通过-A来传递额外的参数给注解处理器,如-Averbose=true。当工厂通过 supportedOptions方法声明了所能识别的附加选项之后,注解处理器就可以在运行时刻通过AnnotationProcessorEnvironment的getOptions方法获取到选项的实际值。注解处理器本身的基本实现如下所示。

public class AssignmentAp implements AnnotationProcessor { 
    private AnnotationProcessorEnvironment env;
    private AnnotationTypeDeclaration assignmentDeclaration;
    public AssignmentAp(AnnotationProcessorEnvironment env) {
        this.env = env;
        assignmentDeclaration = (AnnotationTypeDeclaration) env.getTypeDeclaration("annotation.Assignment");
    }
    public void process() {
        Collection<Declaration> declarations = env.getDeclarationsAnnotatedWith(assignmentDeclaration);
        for (Declaration declaration : declarations) {
           processAssignmentAnnotations(declaration);
        }
    }
    private void processAssignmentAnnotations(Declaration declaration) {
        Collection<AnnotationMirror> annotations = declaration.getAnnotationMirrors();
        for (AnnotationMirror mirror : annotations) {
            if (mirror.getAnnotationType().getDeclaration().equals(assignmentDeclaration)) {
                Map<AnnotationTypeElementDeclaration, AnnotationValue> values = mirror.getElementValues();
                String assignee = (String) getAnnotationValue(values, "assignee"); //获取注解的值
            }
        }
    }   

注解处理器的处理逻辑都在process方法中完成。通过一个声明(Declaration)的getAnnotationMirrors方法就可以获取到该声明上所添加的注解的实际值。得到这些值之后,处理起来就不难了。

在创建好注解处理器之后,就可以通过apt命令行工具来对源代码中的注解进行处理。 命令的运行格式是apt -classpath bin -factory annotation.apt.AssignmentApf src/annotation/work/*.java,即通过-factory来指定注解处理器工厂类的名称。实际上,apt工具在完成处理之后,会自动调用javac来编译处理完成后的源代码。

JDK 5中的apt工具的不足之处在于它是Oracle提供的私有实现。在JDK 6中,通过JSR 269把自定义注解处理器这一功能进行了规范化,有了新的javax.annotation.processing这个新的API。对Mirror API也进行了更新,形成了新的javax.lang.model包。注解处理器的使用也进行了简化,不需要再单独运行apt这样的命令行工具,Java编译器本身就可以完成对注解的处理。对于同样的功能,如果用JSR 269的做法,只需要一个类就可以了。

@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes("annotation.Assignment")
public class AssignmentProcess extends AbstractProcessor {
    private TypeElement assignmentElement; 
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        Elements elementUtils = processingEnv.getElementUtils();
        assignmentElement = elementUtils.getTypeElement("annotation.Assignment");
    } 
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(assignmentElement);
        for (Element element : elements) {
            processAssignment(element);
        }
    }
    private void processAssignment(Element element) {
        List<? extends AnnotationMirror> annotations = element.getAnnotationMirrors();
        for (AnnotationMirror mirror : annotations) {
            if (mirror.getAnnotationType().asElement().equals(assignmentElement)) {
                Map<? extends ExecutableElement, ? extends AnnotationValue> values = mirror.getElementValues();
                String assignee = (String) getAnnotationValue(values, "assignee"); //获取注解的值
            }
        }
    } 
}  

仔细比较上面两段代码,可以发现它们的基本结构是类似的。不同之处在于JDK 6中通过元注解@SupportedAnnotationTypes来声明所支持的注解类型。另外描述程序静态结构的javax.lang.model包使用了不同的类型名称。使用的时候也更加简单,只需要通过javac -processor annotation.pap.AssignmentProcess Demo1.java这样的方式即可。

上面介绍的这两种做法都是在编译时刻进行处理的。而有些时候则需要在运行时刻来完成对注解的处理。这个时候就需要用到Java的反射API。反射API提供了在运行时刻读取注解信息的支持。不过前提是注解的保留策略声明的是运行时。Java反射API的AnnotatedElement接口提供了获取类、方法和域上的注解的实用方法。比如获取到一个Class类对象之后,通过getAnnotation方法就可以获取到该类上添加的指定注解类型的注解。

实例分析

下面通过一个具体的实例来分析说明在实践中如何来使用和处理注解。假定有一个公司的雇员信息系统,从访问控制的角度出发,对雇员的工资的更新只能由具有特定角色的用户才能完成。考虑到访问控制需求的普遍性,可以定义一个注解来让开发人员方便的在代码中声明访问控制权限。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredRoles {
    String[] value();
}

下一步则是如何对注解进行处理,这里使用的Java的反射API并结合动态代理。下面是动态代理中的InvocationHandler接口的实现。

public class AccessInvocationHandler<T> implements InvocationHandler {
    final T accessObj;
    public AccessInvocationHandler(T accessObj) {
        this.accessObj = accessObj;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RequiredRoles annotation = method.getAnnotation(RequiredRoles.class); //通过反射API获取注解
        if (annotation != null) {
            String[] roles = annotation.value();
            String role = AccessControl.getCurrentRole();
            if (!Arrays.asList(roles).contains(role)) {
                throw new AccessControlException("The user is not allowed to invoke this method.");
            }
        }
        return method.invoke(accessObj, args);
    } 

在具体使用的时候,首先要通过Proxy.newProxyInstance方法创建一个EmployeeGateway的接口的代理类,使用该代理类来完成实际的操作。

分享到:
评论

相关推荐

    List转Json

    在Java编程中,将`List&lt;Object&gt;`转换为Json格式是一种常见的需求,特别是在处理服务器与客户端之间的数据交换或者存储数据到数据库之前。这个过程涉及到对象序列化和JSON库的使用。以下是一个详细的步骤和知识点讲解...

    map/list集合转化成xml字符串 xml字符串转化成map/list集合

    marshaller.marshal(new JAXBElement&lt;&gt;(new QName("people"), ArrayList.class, list), writer); return writer.toString(); } ``` 接下来,我们讨论如何将XML字符串转换为`Map`和`List`。这里,通常会使用`DOM`...

    继承Mapper实现的方法,无需编写太多mapper.xml文件,即可获得CRUD功能

    3. **动态SQL**:MyBatis还提供了动态SQL的功能,如`&lt;if&gt;`, `&lt;choose&gt;`, `&lt;when&gt;`, `&lt;otherwise&gt;`, `&lt;trim&gt;`, `&lt;where&gt;`, `&lt;set&gt;`等标签,这些在接口方法的注解中同样可以使用,以实现更加灵活的SQL构建。...

    springboot编译jar包后无法扫描子jar包中的注解解决方法

    4. **验证**:最后,使用`java -jar A.jar`命令启动A项目,现在应该可以成功扫描并加载B项目中的注解了。 #### 结论 通过上述方法,我们可以有效地解决Spring Boot项目编译后无法扫描子JAR包中注解的问题。这种...

    Java自定义注解实例

    ### 一、Java注解概述 1. **定义**: 注解是一种声明式的编程元素,以`@`符号开头,后面跟着注解的名称。它们可以应用于类、接口、方法、变量等不同级别。 2. **预定义注解**: Java标准库提供了若干预定义注解,如`...

    用idea 创建maven项目,整合ssm框架

    在Web层,创建Controller类,使用SpringMVC的`@RequestMapping`注解来处理HTTP请求,通过@Autowired注入Service实例。例如: ```java @Controller @RequestMapping("/users") public class UserController { @...

    springBoot入门实践

    - `@Bean`注解放置在方法上,它定义了一个Bean,相当于XML配置文件中的`&lt;bean&gt;`标签。 ### 示例:创建工程及导入依赖 下面示例演示了如何通过Java配置的方式配置Spring,并实现了Spring IOC功能。首先需要创建一个...

    spring 扫描jar maven 打包

    &lt;packaging&gt;jar&lt;/packaging&gt; ... &lt;/project&gt; ``` Maven的打包过程包括以下步骤: - `clean`: 清理目标目录,删除先前构建的产物。 - `compile`: 编译源代码到类文件。 - `test`: 运行所有的单元测试。 - `...

    springboot通过@Profile注解配置不同环境

    同样,我们也可以在`@Bean`注解的方法上使用`@Profile`,使得该bean只在特定环境中实例化。例如: ```java @Configuration public class AppConfig { @Profile("dev") @Bean public DataSource devDataSource()...

    springboot_笔记

    - **@Bean**:应用于方法级别,表示该方法用于定义一个Bean实例,相当于XML配置中的`&lt;bean&gt;`元素。 - **示例**:下面通过一个简单的例子来展示如何使用Java配置方式来实现Spring的依赖注入功能。 #### 三、Spring...

    java 利用Xstream注解生成和解析xml

    String xmlInput = "&lt;person id='1'&gt;&lt;firstName&gt;John&lt;/firstName&gt;&lt;lastName&gt;Doe&lt;/lastName&gt;&lt;/person&gt;"; Person parsedPerson = (Person) xstream.fromXML(xmlInput); ``` 除了基本类型和字段,Xstream还支持序列化...

    iBatis入门实例详细代码

    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt; &lt;version&gt;8.0.22&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.mybatis&lt;/groupId&gt; &lt;artifactId&gt;mybatis&lt;/artifactId&gt; &lt;version&gt;3.5.6&lt;/version&gt; &lt;/...

    xml和java对象互转

    String xmlInput = "&lt;student&gt;&lt;name&gt;John Doe&lt;/name&gt;&lt;age&gt;25&lt;/age&gt;&lt;/student&gt;"; Student deserializedStudent = (Student) xstream.fromXML(xmlInput); ``` 在实际应用中,XStream还支持更复杂的转换,比如处理...

    基于maven的websocket实例

    在Java中,这通常通过扩展`javax.websocket.Endpoint`类或实现`javax.websocket.ServerEndpoint`注解的类完成。下面是一个简单的WebSocket服务器端点示例: ```java import javax.websocket.OnClose; import javax....

    spring cloud 使用Zuul 实现API网关服务问题

    &lt;javaVersion&gt;1.8&lt;/javaVersion&gt; &lt;/properties&gt; &lt;!-- 使用dependencyManagement进行版本管理 --&gt; &lt;dependencyManagement&gt; &lt;dependencies&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt; ...

    springmvc+jdbc数据库实例

    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt; &lt;version&gt;8.0.30&lt;/version&gt; &lt;/dependency&gt; &lt;/dependencies&gt; ``` 接下来是配置文件部分。在Spring MVC中,通常使用`applicationContext.xml`或`dispatcher-...

    json java 转换

    TypeReference&lt;List&lt;User&gt;&gt; typeRef = new TypeReference&lt;List&lt;User&gt;&gt;() {}; List&lt;User&gt; deserializedList = mapper.readValue(jsonList, typeRef); ``` 7. **自定义序列化和反序列化** 如果需要对特定类型或...

    java把list集合转化为json所需的jar包

    List&lt;Person&gt; people = new ArrayList&lt;&gt;(); people.add(new Person("Alice", 25)); people.add(new Person("Bob", 30)); ``` 4. 使用Jackson转化:创建一个`ObjectMapper`实例,然后调用`writeValueAsString()`...

    java使用EasyExcel导入导出excel(csdn)————程序.pdf

    &lt;groupId&gt;org.apache.poi&lt;/groupId&gt; &lt;artifactId&gt;poi&lt;/artifactId&gt; &lt;version&gt;3.17&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.apache.poi&lt;/groupId&gt; &lt;artifactId&gt;poi-ooxml-schemas&lt;/artifactId&gt; ...

    jaxb2 生产java类demo实例

    **Java Architecture for XML Binding (JAXB)** 是Java平台上的一个标准...在本实例中,我们学习了如何使用注解创建Java类,生成XML,以及如何从XML反序列化回Java对象。希望这个教程能帮助你更好地理解和使用JAXB2。

Global site tag (gtag.js) - Google Analytics