Ceylon 简介 第12部分

发布者:    |       Ceylon

这是介绍 Ceylon 语言的系列文章的最后一篇。请注意,语言的一些功能在最终发布前可能会发生变化。

本文于2011年2月6日更新,以反映对注释约束定义方式的变化。评论线程反映了文章第一版的信息。

注释

如果你已经看到了这一系列文章的这一部分,你已经看到了很多注释。在 Ceylon 中,注释非常重要,以至于不使用它们几乎无法编写任何代码。但我们还没有真正探索注释是什么

让我们最终解决这个问题。答案是简单的:注释是一个顶层方法,它返回ConstrainedAnnotation的子类型。ConstrainedAnnotation以下是我们的老朋友的一些定义

shared Deprecated deprecated() {
    return Deprecated();
}
shared Description doc(String description) {
    return Description(description.normalize());
}
shared Authors by(String... authors) {
    return Authors( from (authors) select (String name) (name.normalize()) );
}

(注意,第三个例子使用了本博客条目中引入的语法)

当然,我们可以定义自己的注释。(这正是全部要点!)

shared Scope scope(Scope s) { return s; }
shared Todo todo(String text) { return Todo(text); }

由于注释是方法,注释名称始终以小写字母开头。

注释参数

当我们在一个程序元素上指定一个非空参数列表的注释时,我们需要为注释的参数指定参数。就像在正常方法调用中一样,我们有位置参数列表或命名参数列表的选择。我们可以写成

doc ("The Hello World program")

或者

doc { description="The Hello World program"; }

同样,我们可以写成

by ("Gavin", "Stephane", "Emmanuel")

或者

by { "Gavin", "Stephane", "Emmanuel" }

但是,对于所有参数都是文字值的注释,我们还有一个第三种选择。我们可以完全消除标点符号,只列出文字值。

doc "The Hello World program"
by "Gavin" 
   "Stephane" 
   "Emmanuel"

作为这种特殊情况的一个例子,如果注释没有参数,我们只需写出注释名称即可。我们经常这样处理注释,如shared, formal, default, actual, 摘要, 已弃用,和变量.

注解类型

注解的返回类型称为 注解类型。多个方法可能会生成相同的注解类型。注解类型必须是一个的子类型ConstrainedAnnotation:

doc "An annotation. This interface encodes
     constraints upon the annotation in its
     type arguments."
shared interface ConstrainedAnnotation<out Value, out Values, in ProgramElement>
        of OptionalAnnotation<Value,ProgramElement> | SequencedAnnotation<Value,ProgramElement>
        satisfies Annotation<Value>
        given Value satisfies Annotation<Value>
        given ProgramElement satisfies Annotated {
    shared Boolean occurs(Annotated programElement) {
        return programElement is ProgramElement;
    }
}

该接口的类型参数表达了约束,即返回注解类型的注解如何出现。第一个类型参数,仅仅是注解类型本身。

注解约束

第二个类型参数,控制了给定程序元素可能返回的注解类型的不同注解的数量。请注意,ConstrainedAnnotation有一个条款告诉我们,只有两个直接子类型。因此,任何注解类型都必须是这两个接口之一的子类型如果注解类型是

  • OptionalAnnotation的子类型,则给定程序元素的最多一个注解可以是此注解类型,否则如果注解类型是
  • SequencedAnnotation的子类型,则给定程序元素可以有一个以上的注解是此注解类型。最后,第三个类型参数
doc "An annotation that may occur at most once at 
     a single program element."
shared interface OptionalAnnotation<out Value, in ProgramElement>
        satisfies ConstrainedAnnotation<Value,Value?,ProgramElement>
        given Value satisfies Annotation<Value>
        given ProgramElement satisfies Annotated {}
doc "An annotation that may occur multiple times at 
     a single program element."
shared interface SequencedAnnotation<out Value, in ProgramElement>
        satisfies ConstrainedAnnotation<Value,Value[],ProgramElement>
        given Value satisfies Annotation<Value>
        given ProgramElement satisfies Annotated {}

ProgramElement,约束了注解可以出现的程序元素类型。到的参数必须是元模型类型。因此,参数ConstrainedAnnotationType<Number>,约束了注解可以出现的程序元素类型。到将约束注解只能出现在声明为Number子类型的程序元素中。参数Attribute<Bottom,String>将约束注解只能出现在声明为String类型的属性的程序元素中。这里有一些我从语言规范直接复制粘贴的例子.

在运行时读取注解值

shared interface Scope
        of request | session | application
        satisfies OptionalAnnotation<Scope,Type<Object>> {}
shared class Todo(String text)
        satisfies SequencedAnnotation<Todo,Annotated> {
    shared actual String string = text;
}

可以通过调用语言模块中定义的顶层方法

annotations()来获取注解值。因此,要获取

shared Values annotations<Value,Values,ProgramElement>(
               Type<ConstrainedAnnotation<Value,Values,ProgramElement>> annotationType,
               ProgramElement programElement)
           given Value satisfies ConstrainedAnnotation<Value,Values,ProgramElement>
           given ProgramElement satisfies Annotated { ... }

Person类的doc注解的值,我们写注意,表达式

String? description = annotations(Description, Person)?.description;

返回类的元模型对象注解的值,我们写,一个注解的值,我们写ConcreteClass<Person>的实例.

为了确定名为Thread的类的stop()方法是否已弃用,我们可以写

Boolean deprecated = annotations(Deprecated, Thread.stop) exists;

返回类的元模型对象Thread.stop返回方法的元模型对象Thread条款告诉我们,只有两个直接子类型。因此,任何注解类型都必须是这两个接口之一的子类型stop()ConcreteClass<Person>Method<Thread,Void>.

这里是另外两个例子,以确保你理解了这一点

Scope scope = annotations(Scope, Person) ? request;
Todo[] todos = annotations(Todo, method);

是的,一切都是设置好的,以便来获取注解值。返回范围?可选注解类型Scope,和Todo[]序列化注解类型Todo。不错,对吧?

当然,更常见的是在通用代码中处理注解,所以你更有可能编写这样的代码

Entry<Attribute<Bottom,Object?>,String>[] attributeColumnNames(Class<Object> clazz) {
	return from (clazz.members(Attribute<Bottom,Object?>))
	        select (Attribute<Bottom,Object?> att) (att->columnName(att));
}

String columnName(Attribute<Bottom,Object?> member) {
    return annotations(Column, member)?.name ? member.name;
}

如你所见,Ceylon注解是框架开发者的天堂。

定义注解

我们已经看到了许多Ceylon内置的注解示例。应用程序开发人员很少定义自己的注解,但框架开发人员经常这样做。让我们看看我们如何在Ceylon中定义一个声明式事务管理的注解。

Transactional transactional(Boolean requiresNew = false) {
    return Transactional(requiresNew);
}

该方法只是生成一个Transactional类的实例,该实例将附加到注解方法或属性的元模型上。元注解指定该注解可以应用于方法和属性,并且最多可以在任何成员上出现一次。

shared class Transactional(Boolean requiresNew) 
        satisfies OptionalAnnotation<Transactional,Member<Bottom,Void>> {
    shared Boolean requiresNew = requiresNew;
}

现在我们可以将我们的注解应用于任何类的任何方法。

shared class OrderManager() {
    shared transactional void createOrder(Order order) { ... }
    ...
}

我们可以使用位置参数列表显式指定transactional的参数

shared transactional (true) 
void createOrder(Order order) { ... }

或者,我们可以使用命名参数列表

shared transactional { requiresNew=true; }
void createOrder(Order order) { ... }

我们不需要在我们的示例中使用反射,因为Ceylon的模块架构包括用于使用注解将拦截器添加到方法和属性的特殊内置支持。

拦截器

拦截器允许框架对方法调用、类实例化或属性评估等事件做出反应。我们不需要编写任何特殊的注解扫描代码来使用拦截器。Ceylon在类加载时为我们处理这些。

我们只需让我们的Transactional类实现MethodAnnotationAttributeAnnotation:

shared class Transactional(Boolean requiresNew)
        satisfies OptionalAnnotation<Transactional,Member<Bottom,Void>> &
                  MethodAnnotation & AttributeAnnotation {
        
    shared Boolean requiresNew = requiresNew;
    
    doc "This method is called whenever Ceylon loads a class with a method
         annotated |transactional|. It registers a transaction management
         interceptor for the method."
    shared actual void onDefineMethod<Instance,Result,Argument...>(OpenMethod<Instance,Result,Argument...> method) {
        method.intercept()
                onInvoke(Instance instance, Result proceed(Argument... args), Argument... args) {
            if (currentTransaction.inProcess || !requiresNew) {
                return proceed(args);
            }
            else {
                currentTransaction.begin();
                try {
                    Result result = proceed(args);
                    currentTransaction.commit();
                    return result;
                }
                catch (Exception e) {
                    currentTransaction.rollback();
                    throw e;
                }
            }
        }
    }
    
    doc "This method is called whenever Ceylon loads a class with an attribute
         annotated |transactional|. It registers a transaction management
         interceptor for the attribute."
    shared actual void onDefineAttribute<Instance,Result>(OpenAttribute<Instance,Result> attribute) {
        attribute.intercept()
                onGet(Instance instance, Result proceed()) {
            if (currentTransaction.inProcess || !requiresNew) {
                return proceed();
            }
            else {
                currentTransaction.begin();
                try {
                    Result result = proceed();
                    currentTransaction.commit();
                    return result;
                }
                catch (Exception e) {
                    currentTransaction.rollback();
                    throw e;
                }
            }
        }
    }
    
}

接口intercept()方法注册拦截器 - 一种回调方法。同样,我们在这里使用了讨论的语法

结论

我认为现在可能是结束这个系列的合适时机,因为我已经涵盖了这一阶段所有经过良好设计的Ceylon特性。在某个时候,我会回过头来更新/清理这些帖子,甚至可能重新组织一下材料。感谢您的关注,请继续告诉我们您的反馈!


返回顶部