实施注释界面

对于Java开发人员来说,每天都需要使用注释。 如果没有别的,简单的@Override注释应该响起。 创建注释要复杂一些。 在运行时通过反射使用“自制”注释或创建编译时调用的注释处理器也是一种复杂性。 但是我们很少“实现”注释接口。 暗中有人暗地里为我们做。

当我们有注释时:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoWithDefMethod {
    String value() default "default value string";
}

然后用这个注解注解的类

@AnnoWithDefMethod("my default value")
public class AnnotatedClass {
}

最后我们在运行时执行期间获取注释

AnnoWithDefMethod awdm = AnnotatedClass.class.getAnnotation(AnnoWithDefMethod.class);

那么我们如何进入变量awdm呢? 它是一个对象。 对象是类的实例,而不是接口。 这意味着Java运行时幕后的某个人“实现了”注释接口。 我们甚至可以打印出对象的特征:

System.out.println(awdm.value());
        System.out.println(Integer.toHexString(System.identityHashCode(awdm)));
        System.out.println(awdm.getClass());
        System.out.println(awdm.annotationType());
        for (Method m : awdm.getClass().getDeclaredMethods()) {
            System.out.println(m.getName());
        }

得到类似的结果

my default value
60e53b93
class com.sun.proxy.$Proxy1
interface AnnoWithDefMethod
value
equals
toString
hashCode
annotationType

因此,我们不需要实现注释接口,但是可以根据需要实现。 但是我们为什么要那样? 到目前为止,我遇到了一种解决方案:配置guice依赖项注入。

Guice是Google的DI容器。 绑定的配置以说明性方式作为Java代码提供,如文档页面中所述 。 您可以将类型绑定到实现,只需声明

bind(TransactionLog.class).to(DatabaseTransactionLog.class);

这样,所有注入的TransactionLog实例将属于DatabaseTransactionLog 。 如果要在代码的不同字段中注入不同的注入,则应以某种方式向Guice发出信号,例如创建注释,将注释放在字段或构造函数参数上并声明

bind(CreditCardProcessor.class)
        .annotatedWith(PayPal.class)
        .to(PayPalCreditCardProcessor.class);

这要求PayPal作为注释接口,并且您需要编写一个新的注释接口,以与每个CreditCardProcessor实现或更多实现相伴,以便您可以在绑定配置中用信号通知和分离实现类型。 仅有太多的注释类,这可能是一个矫kill过正。

除此之外,您还可以使用名称。 您可以使用注解@Named("CheckoutPorcessing")注释注入目标并配置绑定

bind(CreditCardProcessor.class)
        .annotatedWith(Names.named("CheckoutProcessing"))
        .to(CheckoutCreditCardProcessor.class);

这是众所周知的技术,已广泛用于DI容器中。 您指定类型(接口),创建实现,最后使用名称定义绑定类型。 这样做没有问题,只不过在键入处理而不是处理时很难注意到。 在绑定(运行时)失败之前,此类错误将一直隐藏。 您不能简单地使用final static String来保存实际值,因为它不能用作注释参数。 您可以在绑定定义中使用这样的常量字段,但是它仍然是重复的。

这个想法是使用其他东西代替String。 编译器检查的内容。 显而易见的选择是使用一个类。 为了实现可以从NamedImpl的代码学习而创建代码, NamedImpl是实现注释接口的类。 代码是这样的(注意: Klass是这里未列出的注释接口。):

class KlassImpl implements Klass {
    Class<? extends Annotation> annotationType() {
        return Klass.class
    }
    static Klass klass(Class value){
        return new KlassImpl(value: value)
    }
    public boolean equals(Object o) {
        if(!(o instanceof Klass)) {
            return false;
        }
        Klass other = (Klass)o;
        return this.value.equals(other.value());
    }
    public int hashCode() {
        return 127 * "value".hashCode() ^ value.hashCode();
    }
 
     Class value
    @Override
    Class value() {
        return value
    }
}

实际的绑定看起来像

@Inject
  public RealBillingService(@Klass(CheckoutProcessing.class) CreditCardProcessor processor,
      TransactionLog transactionLog) {
    ...
  }
 
    bind(CreditCardProcessor.class)
        .annotatedWith(Klass.klass(CheckoutProcessing.class))
        .to(CheckoutCreditCardProcessor.class);

在这种情况下,编译器很可能会发现任何错字。 实际发生在幕后的事情是什么,为什么我们要求实现注释接口?

配置绑定后,我们提供一个对象。 调用Klass.klass(CheckoutProcessing.class)将创建KlassImpl的实例,并且当Guice尝试确定实际绑定配置是否有效,以将CheckoutCreditCardProcessor绑定到RealBillingService构造函数中的CreditCardProcessor参数时,它仅调用上的equals()方法。注释对象。 如果Java运行时创建的实例(请记住Java运行时创建的实例的名称类似于class com.sun.proxy.$Proxy1 ),并且我们提供的实例相等,那么将使用绑定配置,否则必须进行其他绑定比赛。

还有另一个问题。 实现equals()是不够的。 您可能(并且,如果您是Java程序员(这就是为什么您还要读这篇文章(您当然不是Lisp程序员)),那么您也应该)记住,如果您重写equals()那么您还必须重写hashCode() 。 实际上,您应该提供一个与Java运行时创建的类进行相同计算的实现。 这样做的原因是,该比较可能不会直接由应用程序执行。 Guice可能(确实)正在从Map查找注释对象。 在那种情况下,哈希码用于标识比较对象必须位于其中的存储桶,然后使用equals()方法检查身份。 如果在创建Java运行时的情况下hashCode()方法返回的数字不同,则对象甚至无法匹配。 equals()将返回true,但不会为它们调用它,因为在映射中找不到该对象。

方法hashCode的实际算法在接口java.lang.annotation文档中描述。 我以前看过此文档,但了解我第一次使用Guice并实现类似的注释接口实现类时定义算法的原因。

最后一件事是该类还必须实现annotationType() 。 为什么? 如果我知道了,我会写。

翻译自: https://www.javacodegeeks.com/2016/03/implementing-annotation-interface.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值