Java之注解的定义及使用

栏目: Java · 发布时间: 5年前

内容简介:本文会介绍以

Java 的注解在实际项目中使用得非常的多,特别是在使用了 Spring 之后。

本文会介绍 Java 注解的语法,以及在 Spring 中使用注解的例子。

注解的语法

注解的例子

Junit 中的 @Test 注解为例

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    long timeout() default 0L;
}

可以看到 @Test 注解上有 @Target()@Retention() 两个注解。

这种注解了注解的注解,称之为 元注解

跟声明了数据的数据,称为元数据是一种意思。

之后的注解的格式是

修饰符 @interface 注解名 {   
    注解元素的声明1 
    注解元素的声明2   
}

注解的元素声明有两种形式

type elementName();
type elementName() default value;  // 带默认值

常见的元注解

@Target 注解

@Target 注解用于限制注解能在哪些项上应用,没有加 @Target 的注解可以应用于任何项上。

java.lang.annotation.ElementType 类中可以看到所有 @Target 接受的项

TYPE
FIELD
METHOD
PARAMETER
CONSTRUCTOR
LOCAL_VARIABLE
ANNOTATION_TYPE
PACKAGE
TYPE_PARAMETER
TYPE_USE

@Test 注解只允许在方法上使用。

@Target(ElementType.METHOD)
public @interface Test { ... }

如果要支持多项,则传入多个值。

@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation { ... }

此外元注解也是注解,也符合注解的语法,如 @Target 注解。

@Target(ElementType.ANNOTATION_TYPE) 表明 @Target 注解只能使用在注解上。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

@Retention 注解

@Retention 指定注解应该保留多长时间,默认是 RetentionPolicy.CLASS

java.lang.annotation.RetentionPolicy 可看到所有的项

SOURCE
CLASS
RUNTIME

@Test 注解会载入到虚拟机,可以通过代码获取

@Retention(RetentionPolicy.RUNTIME)
public @interface Test { ... }

@Documented 注解

主要用于归档 工具 识别。被注解的元素能被 Javadoc 或类似的工具文档化。

@Inherited 注解

添加了 @Inherited 注解的注解,所注解的类的子类也将拥有这个注解

注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation { ... }

父类

@MyAnnotation 
class Parent { ... }

子类 Child 会把加在 Parent 上的 @MyAnnotation 继承下来

class Child extends Parent { ... }

@Repeatable 注解

Java 1.8 引入的注解,标识注解是可重复使用的。

注解1

public @interface MyAnnotations {   
    MyAnnotation[] value();   
}

注解2

@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {   
    int value();
}

有使用 @Repeatable() 时的使用

@MyAnnotation(1)
@MyAnnotation(2)
@MyAnnotation(3)
public class MyTest { ... }

没使用 @Repeatable() 时的使用, @MyAnnotation 去掉 @Repeatable 元注解

@MyAnnotations({
    @MyAnnotation(1), 
    @MyAnnotation(2),
    @MyAnnotation(3)})
public class MyTest { ... }

这个注解还是非常有用的,让我们的代码变得简洁不少,

Spring@ComponentScan 注解也用到这个元注解。

元素的类型

支持的元素类型

  • 8种基本数据类型( byteshortcharintlongfloatdoubleboolean
  • String
  • Class
  • enum
  • 注解类型
  • 数组(所有上边类型的数组)

例子

枚举类

public enum Status {
    GOOD,
    BAD
}

注解1

@Target(ElementType.ANNOTATION_TYPE)
public @interface MyAnnotation1 {
    int val();
}

注解2

@Target(ElementType.TYPE)
public @interface MyAnnotation2 {
    
    boolean boo() default false;
    
    Class<?> cla() default Void.class;
    
    Status enu() default Status.GOOD;
    
    MyAnnotation1 anno() default @MyAnnotation1(val = 1);
    
    String[] arr();
    
}

使用时,无默认值的元素必须传值

@MyAnnotation2(
        cla = String.class,
        enu = Status.BAD,
        anno = @MyAnnotation1(val = 2),
        arr = {"a", "b"})
public class MyTest { ... }

Java 内置的注解

@Override 注解

告诉编译器这个是个覆盖父类的方法。如果父类删除了该方法,则子类会报错。

@Deprecated 注解

表示被注解的元素已被弃用。

@SuppressWarnings 注解

告诉编译器忽略警告。

@FunctionalInterface 注解

Java 1.8 引入的注解。该注释会强制编译器 javac 检查一个接口是否符合函数接口的标准。

特别的注解

有两种比较特别的注解

  • 标记注解 : 注解中没有任何元素,使用时直接是 @XxxAnnotation , 不需要加括号
  • 单值注解 : 注解只有一个元素,且名字为 value ,使用时直接传值,不需要指定元素名 @XxxAnnotation(100)

利用反射获取注解

JavaAnnotatedElement 接口中有 getAnnotation() 等获取注解的方法。

MethodFieldClassPackage 等类均实现了这个接口,因此均有获取注解的能力。

例子

注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface MyAnno {   
    String value();   
}

被注解的元素

@MyAnno("class")
public class MyClass {
    
    @MyAnno("feild")
    private String str;
    
    @MyAnno("method")
    public void method() { }
    
}

获取注解

public class Test {
    
    public static void main(String[] args) throws Exception {
    
        MyClass obj = new MyClass();
        Class<?> clazz = obj.getClass();
        
        // 获取对象上的注解
        MyAnno anno = clazz.getAnnotation(MyAnno.class);
        System.out.println(anno.value());
        
        // 获取属性上的注解
        Field field = clazz.getDeclaredField("str");
        anno = field.getAnnotation(MyAnno.class);
        System.out.println(anno.value());
        
        // 获取方法上的注解
        Method method = clazz.getMethod("method");
        anno = method.getAnnotation(MyAnno.class);
        System.out.println(anno.value());
    }
    
}

Spring 中使用自定义注解

注解本身不会有任何的作用,需要有其他代码或工具的支持才有用。

需求

设想现有这样的需求,程序需要接收不同的命令 CMD

然后根据命令调用不同的处理类 Handler

很容易就会想到用 Map 来存储命令和处理类的映射关系。

由于项目可能是多个成员共同开发,不同成员实现各自负责的命令的处理逻辑。

因此希望开发成员只关注 Handler 的实现,不需要主动去 Map 中注册 CMDHandler 的映射。

最终效果

最终希望看到效果是这样的

@CmdMapping(Cmd.LOGIN)
public class LoginHandler implements ICmdHandler {
    @Override
    public void handle() {
        System.out.println("handle login request");
    }
}

@CmdMapping(Cmd.LOGOUT)
public class LogoutHandler implements ICmdHandler {
    @Override
    public void handle() {
        System.out.println("handle logout request");
    }
}

开发人员增加自己的 Handler ,只需要创建新的类并注上 @CmdMapping(Cmd.Xxx) 即可。

具体做法

具体的实现是使用 Spring 和一个自定义的注解

定义 @CmdMapping 注解

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface CmdMapping {
    int value();   
}

@CmdMapping 中有一个 int 类型的元素 value ,用于指定 CMD 。这里做成一个单值注解。

这里还加了 Spring@Component 注解,因此注解了 @CmdMapping 的类也会被Spring创建实例。

然后是 CMD 接口,存储命令。

public interface Cmd {
    int REGISTER = 1;
    int LOGIN    = 2;
    int LOGOUT   = 3;
}

之后是处理类接口,现实情况接口会复杂得多,这里简化了。

public interface ICmdHandler { 
    void handle();   
}

上边说过,注解本身是不起作用的,需要其他的支持。下边就是让注解生效的部分了。

使用时调用 handle() 方法即可。

@Component
public class HandlerDispatcherServlet implements 
    InitializingBean, ApplicationContextAware {

    private ApplicationContext context;

    private Map<Integer, ICmdHandler> handlers = new HashMap<>();
    
    public void handle(int cmd) {
        handlers.get(cmd).handle();
    }
    
    public void afterPropertiesSet() {
        
        String[] beanNames = this.context.getBeanNamesForType(Object.class);

        for (String beanName : beanNames) {
            
            if (ScopedProxyUtils.isScopedTarget(beanName)) {
                continue;
            }
            
            Class<?> beanType = this.context.getType(beanName);
            
            if (beanType != null) {
                
                CmdMapping annotation = AnnotatedElementUtils.findMergedAnnotation(
                        beanType, CmdMapping.class);
                
                if(annotation != null) {
                    handlers.put(annotation.value(), (ICmdHandler) context.getBean(beanType));
                }
            }
        }
        
    }

    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {   
        this.context = applicationContext;
    }

}

主要工作都是 Spring 做,这里只是将实例化后的对象 putMap 中。

测试代码

@ComponentScan("pers.custom.annotation")
public class Main {

    public static void main(String[] args) {
        
        AnnotationConfigApplicationContext context 
            = new AnnotationConfigApplicationContext(Main.class);
            
        HandlerDispatcherServlet servlet = context.getBean(HandlerDispatcherServlet.class);
        
        servlet.handle(Cmd.REGISTER);
        servlet.handle(Cmd.LOGIN);
        servlet.handle(Cmd.LOGOUT);

        context.close();
    }
}

> 完整项目

总结

可以看到使用注解能够写出很灵活的代码,注解也特别适合做为使用框架的一种方式。

所以学会使用注解还是很有用的,毕竟这对于上手框架或实现自己的框架都是非常重要的知识。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms Unlocked

Algorithms Unlocked

Thomas H. Cormen / The MIT Press / 2013-3-1 / USD 25.00

Have you ever wondered how your GPS can find the fastest way to your destination, selecting one route from seemingly countless possibilities in mere seconds? How your credit card account number is pro......一起来看看 《Algorithms Unlocked》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具