`

使用Java实现内部领域特定语言

    博客分类:
  • DSLs
阅读更多

使用Java实现内部领域特定语言

作者 Alex Ruiz and Jeff Bay 译者 沙晓兰 发布于 2008年3月12日 上午1时4分

社区
Java
主题
领域特定语言
标签
语言特性,
模式,
语言

简介

领域特定语言(DSL)通常被定义为一种特别针对某类特殊问题的计算机语言,它不打算解决其领域外的问题。对于DSL的正式研究已经持续很多年,直到最近,在程序员试图采用最易读并且简炼的方法来解决他们的问题的时候,内部DSL意外地被写入程序中。近来,随着关于Ruby和其他一些动态语言的出现,程序员对DSL的兴趣越来越浓。这些结构松散的语言给DSL提供某种方法,使得DSL允许最少的语法以及对某种特殊语言最直接的表现。但是,放弃编译器和使用类似Eclipse这样最强大的现代集成开发环境无疑是该方式的一大缺点。然而,作者终于成功地找到了这两个方法的折衷解决方式,并且,他们将证明该折衷方法不但可能,而且对于使用Java这样的结构性语言从面向DSL的方式来设计API很有帮助。本文将描述怎样使用Java语言来编写领域特定语言,并将建议一些组建DSL语言时可采用的模式。

Java适合用来创建内部领域特定语言吗?

在我们审视Java语言是否可以作为创建DSL的工具之前,我们首先需要引进“内部DSL”这个概念。一个内部DSL在由应用软件的主编程语言创建,对定制编译器和解析器的创建(和维护)都没有任何要求。Martin Fowler曾编写过大量各种类型的DSL,无论是内部的还是外部的,每种类型他都编写过一些不错的例子。但使用像Java这样的语言来创建DSL,他却仅仅一笔带过。

另外还要着重提出的很重要的一点是,在DSL和API两者间其实很难区分。在内部DSL的例子中,他们本质上几乎是一样的。在联想到DSL这个词汇的时候,我们其实是在利用主编程语言在有限的范围内创建易读的API。“内部DSL”几乎是一个特定领域内针对特定问题而创建的极具可读性的API的代名词。

任何内部DSL都受它基础语言的文法结构的限制。比如在使用Java的情况下,大括弧,小括弧和分号的使用是必须的,并且缺少闭包和元编程有可能会导致DSL比使用动态语言创建来的更冗长。

但从光明的一面来看,通过使用Java,我们同时能利用强大且成熟的类似于Eclipse和IntelliJ IDEA的集成开发环境,由于这些集成开发环境“自动完成(auto-complete)”、自动重构和debug等特性,使得DSL的创建、使用和维护来的更加简单。另外,Java5中的一些新特性(比如generic、varargs 和static imports)可以帮助我们创建比以往任何版本任何语言都简洁的API。

一般来说,使用Java编写的DSL不会造就一门业务用户可以上手的语言,而会是一种业务用户也会觉得易读的语言,同时,从程序员的角度,它也会是一种阅读和编写都很直接的语言。和外部DSL或由动态语言编写的DSL相比有优势,那就是编译器可以增强纠错能力并标识不合适的使用,而Ruby或Pearl会“愉快接受”荒谬的input并在运行时失败。这可以大大减少冗长的测试,并极大地提高应用程序的质量。然而,以这样的方式利用编译器来提高质量是一门艺术,目前,很多程序员都在为尽力满足编译器而非利用它来创建一种使用语法来增强语义的语言。

利用Java来创建DSL有利有弊。最终,你的业务需求和你所工作的环境将决定这个选择正确与否。

将Java作为内部DSL的平台

动态构建SQL是一个很好的例子,其建造了一个DSL以适合SQL领域,获得了引人注意的优势。

传统的使用SQL的Java代码一般类似于:

String sql = "select id, name " +
             "from customers c, order o " +
             "where " +
             "c.since >= sysdate - 30 and " +
             "sum(o.total) > " + significantTotal + " and " +
             "c.id = o.customer_id and " +
             "nvl(c.status, 'DROPPED') != 'DROPPED'"; 

从作者最近工作的系统中摘录的另一个表达方式是:

Table c = CUSTOMER.alias();
Table o = ORDER.alias();
Clause recent = c.SINCE.laterThan(daysEarlier(30));
Clause hasSignificantOrders = o.TOTAT.sum().isAbove(significantTotal);
Clause ordersMatch = c.ID.matches(o.CUSTOMER_ID);
Clause activeCustomer = c.STATUS.isNotNullOr("DROPPED");
String sql = CUSTOMERS.where(recent.and(hasSignificantOrders)
                       .and(ordersMatch)
                       .and(activeCustomer)
                       .select(c.ID, c.NAME)
                       .sql(); 

这个DSL版本有几项优点。后者能够透明地适应转换到使用PreparedStatement的方法——用String拼写SQL的版本则需要大量的修改才能适应转换到使用捆绑变量的方法。如果引用不正确或者一个integer变量被传递到date column作比较的话,后者版本根本无法通过编译。代码“nvl(foo, 'X') != 'X'”是Oracle SQL中的一种特殊形式,这个句型对于非Oracle SQL程序员或不熟悉SQL的人来说很难读懂。例如在SQL Server方言中,该代码应该这样表达“(foo is null or foo != 'X')”。但通过使用更易理解、更像人类语言的“isNotNullOr(rejectedValue)”来替代这段代码的话,显然会更具阅读性,并且系统也能够受到保护,从而避免将来为了利用另一个数据库供应商的设施而不得不修改最初的代码实现。

使用Java创建内部DSL

创建DSL最好的方法是,首先将所需的API原型化,然后在基础语言的约束下将它实现。DSL的实现将会牵涉到连续不断的测试来肯定我们的开发确实瞄准了正确的方向。该“原型-测试”方法正是测试驱动开发模式(TDD-Test-Driven Development)所提倡的。

在使用Java来创建DSL的时候,我们可能想通过一个连贯接口(fluent interface)来创建DSL。连贯接口可以对我们所想要建模的领域问题提供一个简介但易读的表示。连贯接口的实现采用方法链接(method chaining)。但有一点很重要,方法链接本身不足以创建DSL。一个很好的例子是Java的StringBuilder,它的方法“append”总是返回一个同样的StringBuilder的实例。这里有一个例子:

StringBuilder b = new StringBuilder();
b.append("Hello. My name is ")
  .append(name)
  .append(" and my age is ")
  .append(age); 

该范例并不解决任何领域特定问题。

除了方法链接外,静态工厂方法(static factory method)和import对于创建简洁易读的DSL来说是不错的助手。在下面的章节中,我们将更详细地讲到这些技术。

1.方法链接(Method Chaining)

使用方法链接来创建DSL有两种方式,这两种方式都涉及到链接中方法的返回值。我们的选择是返回this或者返回一个中间对象,这决定于我们试图要所达到的目的。

1.1 返回this

在可以以下列方式来调用链接中方法的时候,我们通常返回this

  • 可选择的
  • 以任何次序调用
  • 可以调用任何次数

我们发现运用这个方法的两个用例:

  1. 相关对象行为链接
  2. 一个对象的简单构造/配置

1.1.1 相关对象行为链接

很多次,我们只在企图减少代码中不必要的文本时,才通过模拟分派“多信息”(或多方法调用)给同一个对象而将对象的方法进行链接。下面的代码段显示的是一个用来测试Swing GUI的API。测试所证实的是,如果一个用户试图不输入她的密码而登录到系统中的话,系统将显示一条错误提示信息。

DialogFixture dialog = new DialogFixture(new LoginDialog());
dialog.show();
dialog.maximize();
TextComponentFixture usernameTextBox = dialog.textBox("username");
usernameTextBox.clear();
usernameTextBox.enter("leia.organa");
dialog.comboBox("role").select("REBEL");
OptionPaneFixture errorDialog = dialog.optionPane();
errorDialog.requireError();
errorDialog.requireMessage("Enter your password"); 

尽管代码很容易读懂,但却很冗长,需要很多键入。

下面列出的是在我们范例中所使用的TextComponentFixture的两个方法:

public void clear() {
   target.setText("");
}

public void enterText(String text) {
   robot.enterText(target, text);
} 

我们可以仅仅通过返回this来简化我们的测试API,从而激活方法链接:

public TextComponentFixture clear() {
   target.setText("");
   return this;
}

 public TextComponentFixture enterText(String text) {
   robot.enterText(target, text);
   return this;
} 

在激活所有测试设施中的方法链接之后,我们的测试代码现在缩减到:

DialogFixture dialog = new DialogFixture(new LoginDialog());
dialog.show().maximize();
dialog.textBox("username").clear().enter("leia.organa");
dialog.comboBox("role").select("REBEL");
dialog.optionPane().requireError().requireMessage("Enter your password"); 

这个结果代码显然更加简洁易读。正如先前所提到的,方法链接本身并不意味着有了DSL。我们需要将解决领域特定问题的对象的所有相关行为相对应的方法链接起来。在我们的范例中,这个领域特定问题就是Swing GUI测试。

1.1.2 对象的简单构造/配置

这个案例和上文的很相似,不同是,我们不再只将一个对象的相关方法链接起来,取而代之的是,我们会通过连贯接口创建一个“builder”来构建和/或配置对象。

下面这个例子采用了setter来创建“dream car”:

DreamCar car = new DreamCar();
car.setColor(RED);
car.setFuelEfficient(true);
car.setBrand("Tesla"); 

DreamCar类的代码相当简单:

// package declaration and imports

public class DreamCar {

   private Color color;
   private String brand;
   private boolean leatherSeats;
   private boolean fuelEfficient;
   private int passengerCount = 2;

   // getters and setters for each field
} 

尽管创建DreamCar非常简单,并且代码也十分可读,但我们仍能够使用car builder来创造更简明的代码:

// package declaration and imports

public class DreamCarBuilder {

   public static DreamCarBuilder car() {
     return new DreamCarBuilder();
   }

   private final DreamCar car;

   private DreamCarBuilder() {
     car = new DreamCar();
   }

   public DreamCar build() { return car; }

   public DreamCarBuilder brand(String brand) {
     car.setBrand(brand);
     return this;
   }

   public DreamCarBuilder fuelEfficient() {
     car.setFuelEfficient(true);
     return this;
   }

   // similar methods to set field values
} 

通过builder,我们还能这样重新编写DreamCar的创建过程:

DreamCar car = car().brand("Tesla")
                     .color(RED)
                     .fuelEfficient()
                     .build(); 

使用连贯接口,再一次减少了代码噪音,所带来的结果是更易读的代码。需要指出的很重要的一点是,在返回this的时候,链中任何方法都可以在任何时候被调用,并且可以被调用任何次数。在我们的例子中,color这个方法我们可想调用多少次就调用多少次,并且每次调用都会覆盖上一次调用所设置的值,这在应用程序的上下文中可能是合理的。

另一个重要的发现是,没有编译器检查来强制必需的属性值。一个可能的解决方案是,如果任何对象创建和/或配置规则没有得到满足的话(比如,一个必需属性被遗忘),在运行时抛出异常。通过从链中方法返回中间对象有可能达到规则校验的目的。

1.2 返回中间对象

从连贯接口的方法中返回中间对象和返回this的方式相比,有这样一些优点:

  • 我们可以使用编译器来强制业务规则(比如:必需属性)
  • 我们可以通过限制链中下一个元素的可用选项,通过一个特殊途径引导我们的连贯接口用户
  • 在用户可以(或必须)调用哪些方法、调用顺序、用户可以调用多少次等方面,给了API创建者更大的控制力

下面的例子表示的是通过带参数的构建函数来创建一个vacation对象的实例:

Vacation vacation = new Vacation("10/09/2007", "10/17/2007",
                                  "Paris", "Hilton",
                                  "United", "UA-6886"); 

这个方法的好处在于它可以迫使我们的用户申明所有必需的参数。不幸的是,这儿有太多的参数,而且没有表达出他们的目的。“Paris”和“Hilton”所指的分别是目的地的城市和酒店?还是我们同事的名字?:)

第二个方法是将setter方法对每个参数进行建档:

Vacation vacation = new Vacation();
 vacation.setStart("10/09/2007");
 vacation.setEnd("10/17/2007");
vacation.setCity("Paris");
vacation.setHotel("Hilton");
vacation.setAirline("United");
vacation.setFlight("UA-6886"); 

现在我们的代码更易读,但仍然很冗长。第三个方案则是创建一个连贯接口来构建vacation对象的实例,如同在前一章节提供的例子一样:

Vacation vacation = vacation().starting("10/09/2007")
                               .ending("10/17/2007")
                               .city("Paris")
                               .hotel("Hilton")
                               .airline("United")
                               .flight("UA-6886"); 

这个版本的简明和可读性又进了一步,但我们丢失了在第一个版本(使用构建函数的那个版本)中所拥有的关于遗忘属性的校验。换句话说,我们并没有使用编译器来校验可能存在的错误。这时,对这个方法我们所能做的最好的改进是,如果某个必需属性没有设置的话,在运行时抛出异常。

以下是第四个版本,连贯接口更完善的版本。这次,方法返回的是中间对象,而不是this:

Period vacation = from("10/09/2007").to("10/17/2007");
Booking booking = vacation.book(city("Paris").hotel("Hilton"));
booking.add(airline("united").flight("UA-6886"); 

这里,我们引进了PeriodBookingLocationBookableItemHotelFlight)、以及 Airline的概念。在这里的上下文中,airline作为Flight对象的一个工厂;LocationHotel的工厂,等等。我们所想要的booking的文法隐含了所有这些对象,几乎可以肯定的是,这些对象在系统中会有许多其他重要的行为。采用中间对象,使得我们可以对用户行为可否的限制进行编译器校验。例如,如果一个API的用户试图只通过提供一个开始日期而没有明确结束日期来预定假期的话,代码则不会被编译。正如我们之前提到,我们可以创建一种使用文法来增强语义的语言。

我们在上面的例子中还引入了静态工厂方法的应用。静态工厂方法在与静态import同时使用的时候,可以帮助我们创建更简洁的连贯接口。若没有静态import,上面的例子则需要这样的代码:

Period vacation = Period.from("10/09/2007").to("10/17/2007");
Booking booking = vacation.book(Location.city("Paris").hotel("Hilton"));
booking.add(Flight.airline("united").flight("UA-6886"); 

上面的例子不及采用了静态import的代码那么易读。在下面的章节中,我们将对静态工厂方法和import做更详细的讲解。

这是关于使用Java编写DSL的第二个例子。这次,我们将Java reflection的使用进行简化:

Person person = constructor().withParameterTypes(String.class)
                              .in(Person.class)
                              .newInstance("Yoda");

 method("setName").withParameterTypes(String.class)
                  .in(person)
                  .invoke("Luke");

 field("name").ofType(String.class)
              .in(person)
              .set("Anakin"); 

在使用方法链接的时候,我们必须倍加注意。方法链接很容易会被烂用,它会导致许多调用被一起链接在单一行中的“火车残骸”现象。这会引发很多问题,包括可读性的急剧下滑以及异常发生时栈轨迹(stack trace)的含义模糊。

2. 静态工厂方法和Imports

静态工厂方法和imports可以使得API更加简洁易读。我们发现,静态工厂方法是在Java中模拟命名参数的一个非常方便的方法,是许多程序员希望开发语言中所能够包含的特性。比如,对于这样一段代码,它的目的在于通过模拟一个用户在一个JTable中选择一行来测试GUI:

dialog.table("results").selectCell(6, 8); // row 6, column 8 

没有注释“// row 6, column 8”,这段代码想要实现的目的很容易被误解(或者说根本没有办法理解)。我们则需要花一些额外的时间来检查文档或者阅读更多行代码才能理解“6”和“8”分别代表什么。我们也可以将行和列的下标作为变量来声明,而非像上面这段代码那样使用常量:

int row = 6;
int column = 8;
dialog.table("results").selectCell(row, column); 

我们已经改进了这段代码的可读性,但却付出了增加需要维护的代码的代价。为了将代码尽量简化,理想的解决方案是像这样编写代码:

dialog.table("results").selectCell(row: 6, column: 8); 

不幸的是,我们不能这样做,因为Java不支持命名参数。好的一面的是,我们可以通过使用静态工厂方法和静态imports来模拟他们,从而可以得到这样的代码:

dialog.table("results").selectCell(row(6).column(8)); 

我们可以从改变方法的签名(signature)开始,通过包含所有参数的对象来替代所有这些参数。在我们的例子中,我们可以将方法selectCell(int, int)修改为:

selectCell(TableCell); 

TableCell will contain the values for the row and column indices:

TableCell将包含行和列的下标值:

public final class TableCell {

   public final int row;
   public final int column;

   public TableCell(int row, int column) {
     this.row = row;
     this.column = column;
   }
} 

这时,我们只是将问题转移到了别处:TableCell的构造函数仍然需要两个int值。下一步则是将引入一个TableCell的工厂,这个工厂将对初始版本中selectCell的每个参数设置一个对应的方法。另外,为了迫使用户使用工厂,我们需要将TableCell的构建函数修改为private

public final class TableCell {

   public static class TableCellBuilder {
     private final int row;

     public TableCellBuilder(int row) {
       this.row = row;
     }

     public TableCell column(int column) {
       return new TableCell(row, column);
     }
   }

   public final int row;
   public final int column;

   private TableCell(int row, int column) {
     this.row = row;
     this.column = column;
   }
} 

通过TableCellBuilder工厂,我们可以创建对每个参数都有一个调用方法的TableCell。工厂中的每个方法都表达了其参数的目的:

selectCell(new TableCellBuilder(6).column(8)); 

最后一步是引进静态工厂方法来替代TableCellBuilder构造函数的使用,该构造函数没有表达出6代表的是什么。如我们在之前所实现的那样,我们需要将构造函数设置为private来迫使用户使用工厂方法:

public final class TableCell {

   public static class TableCellBuilder {
     public static TableCellBuilder row(int row) {
       return new TableCellBuilder(row);
     }

     private final int row;

     private TableCellBuilder(int row) {
       this.row = row;
     }

     private TableCell column(int column) {
       return new TableCell(row, column);
     }
   }

   public final int row;
   public final int column;

   private TableCell(int row, int column) {
     this.row = row;
     this.column = column;
   }
 } 

现在我们只需要selectCell的调用代码中增加内容,包含对TableCellBuilderrow方法的静态import。为了刷新一下我们的记忆,这是如何实现调用selectCell的代码:

dialog.table("results").selectCell(row(6).column(8)); 

我们的例子说明,一点点额外的工作可以帮助我们克服主机编程语言中的一些限制。正如之前提到的,这只是我们通过使用静态工厂方法和imports来改善代码可读性的很多方法中的一个。下列代码段是以另一种不同的方法利用静态工厂方法和imports来解决相同的table坐标问题:

/**
  * @author Mark Alexandre
  */
public final class TableCellIndex {

   public static final class RowIndex {
     final int row;
     RowIndex(int row) {
       this.row = row;
     }
   }

   public static final class ColumnIndex {
     final int column;
     ColumnIndex(int column) {
       this.column = column;
     }
   }

   public final int row;
   public final int column;
   private TableCellIndex(RowIndex rowIndex, ColumnIndex columnIndex) {
     this.row = rowIndex.row;
     this.column = columnIndex.column;
   }

   public static TableCellIndex cellAt(RowIndex row, ColumnIndex column) {
     return new TableCellIndex(row, column);
   }

   public static TableCellIndex cellAt(ColumnIndex column, RowIndex row) {
     return new TableCellIndex(row, column);
   }

   public static RowIndex row(int index) {
     return new RowIndex(index);
   }

   public static ColumnIndex column(int index) {
     return new ColumnIndex(index);
   }
} 

这个方案的第二个版本比第一个版本更具灵活性,因为这个版本允许我们通过两种途径来声明行和列的坐标:

dialog.table("results").select(cellAt(row(6), column(8));
dialog.table("results").select(cellAt(column(3), row(5)); 

组织代码

相比返回中间对象的的方式来说,返回this的方式更加容易组织连贯接口的代码。前面的案例中,我们的最后结果是使用更少的类来封装连贯接口的逻辑,并且使得我们可以在组织非DSL代码的时候使用同样的规则或约定。

采用中间对象作为返回类型来组织连贯接口的代码更具技巧性,因为我们将连贯接口的逻辑遍布在一些小的类上。由于这些类结合在一起作为整体而形成我们的连贯接口,这使得将他们作为整体对待更为合理,我们可能不想将他们和DSL外的其他一些类混淆一起,那么我们有两个选择:

  • 将中间对象作为内嵌类创建
  • 将中间对象至于他们自己的顶级类中,将所有这些中间对象类放入同一个包中

分解我们的系统所采用的方式取决于我们想要实现的文法的几个因素:DSL的目的,中间对象(如果有的话)的数量和大小(以代码的行数来计),以及DSL如何来与其它的代码库及其它的DSL相协调。

对代码建档

在组织代码一章节中提到,对方法返回this的连贯接口建档比对返回中间对象的连贯接口建档来的简单的多,尤其是在使用Javadoc来建档的情况下。

Javadoc每次显示一个类的文档,这对于使用中间对象的DSL来说可能不是最好的方式:因为这样的DSL包含一组类,而不是单个的类。由于我们不能改变Javadoc显示我们的API文档的方式,我们发现在package.html文件中,加入一个使用连贯接口(包含所有相关类)、且对链中每个方法提供链接的例子,可以将Javadoc的限制的影响降到最低。

我们需要注意不要创建重复文档,因为那样会增加API创建者的维护代价。最好的方法是尽可能依赖于像可执行文档那样的测试。

结论

Java适用于创建开发人员易读易写的、并且对于商业用户用样易读的内部领域特定语言。用Java创建的DSL可能比那些由动态语言创建的DSL来的冗长。但好的一面是,通过使用Java,我们可以利用编译器来增强DSL的语义。另外,我们依赖于成熟且强大的Java集成开发环境,从而使DSL的创建、使用和维护更加简单。

使用Java创建DSL需要API设计者做更多的工作,有更多的代码和文档需要创建和维护。但是,付出总有回报。使用我们API的用户在他们的代码库中会看到更多的优化。他们的代码将会更加简洁,更易于维护,这些将使得他们的生活更加轻松。

使用Java创建DSL有很多种不同的方式,这取决于我们试图达到的目的是什么。尽管没有什么通用的方法,我们还是发现结合方法链接和静态工厂方法与imports的方式可以得到干净、简洁、易读易写的API。

总而言之,在使用Java来创建DSL的时候有利有弊。这都由我们——开发人员根据项目需求去决定它是否是正确的选择。

另外一点题外话,Java 7可能会包含帮助我们创建不那么冗长的DSL的新语言特性(比如闭包)。如果想得到更多关于建议中所提特性的全面的列表,请访问Alex Miller的blog

关于作者

Alex Ruiz是Oracle开发工具组织中的一名软件工程师。Alex喜欢阅读任何关于Java、测试、OOP 和AOP的信息,他最大的爱好就是编程。在加入Oracle之前,Alex曾是ThoughtWorks的咨询顾问。Alex的blog为 http://www.jroller.com/page/alexRuiz

Jeff Bay是纽约一家对冲基金的高级软件工程师。他曾多次建立高质量、迅速的XP团队工作于例如Onstar的计划注册系统、租赁软件、web服务器、建筑项目管理等各种系统。他对于消除重复和防止bug方面怀有极大的热情,以提高开发者的工作效率和减少在各种任务上所花费的时间。

相关资料

分享到:
评论

相关推荐

    【文献综述】基于JAVA的俄罗斯方块游戏设计与实现.pdf

    2.2 跨平台性 Java 语言的第二个特性就是跨平台性[4],也就是说使用 Java 语言编写的程序可以在编 2 译后不用经过任何更改,就能在任何硬件设备条件下运行。这个特性经常被称为"一次编 译,到处运行" 。执行 Java ...

    JAVA上百实例源码以及开源项目

     使用Java语言编写的一款用于反映颜色变化的面板,也就是大家熟悉的颜色调色板演示程序。原理是初始化颜色选择按钮,然后为颜色选择按钮增加事件处理事件,最后实例化颜色选择器。 Java二进制IO类与文件复制操作...

    JAVA上百实例源码以及开源项目源代码

     使用Java语言编写的一款用于反映颜色变化的面板,也就是大家熟悉的颜色调色板演示程序。原理是初始化颜色选择按钮,然后为颜色选择按钮增加事件处理事件,最后实例化颜色选择器。 Java二进制IO类与文件复制操作...

    java 面试题 总结

    以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题。 public class ThreadTest1{ private int j; public static void main(String args[]){ ThreadTest1 tt=new ThreadTest1(); Inc inc=tt.new Inc(); ...

    适用于Android触摸事件的领域特定语言:第2部分:结构和内部工作原理

    DSL的概念,用于在Android中创建触摸手势。

    超级有影响力霸气的Java面试题大全文档

    Stateless Session Bean 虽然也是逻辑组件,但是他却不负责记录使用者状态,也就是说当使用者呼叫 Stateless Session Bean 的时候,EJB Container 并不会找寻特定的 Stateless Session Bean 的实体来执行这个 method...

    java笔试题算法-dfalex:不用大惊小怪的Java扫描/词法分析

    它们让您使用特定于领域的语言编写规范,通常与代码混合,然后为扫描仪生成新的 Java 代码,您必须将其合并到您的构建中并以非常特定的方式使用。 DFALex 提供了强大的匹配功能,无需大惊小怪。 它将为您构建一个...

    基于Java的XML解析与反射设计模式.doc

    xstream可以完美转换xml,与以往的解析xml工具dom相比,xstream是一个易用的、开源 的库,使用起来简单、小巧,容易理解,可以轻易的将java对象和xml文档相互转换,而 且可以修改某个特定的属性和节点名称。...

    xmljava系统源码-GradleDemo:Gradle插件开发极简教程

    Gradle是一种构建工具,它抛弃了基于XML的构建脚本,取而代之的是采用一种基于Groovy的内部领域特定语言。Gradle 通过提供可以随意集成的声明式语言元素将声明性构建推到了一个新的高度。你可以添加自己的语言元素或...

    ssm项目weixin077停车场微信小程序的设计与实现+ssm.zip

    SSM项目weixin077停车场微信小程序的设计与实现Ssm.zip是一个使用Java语言开发的Web应用,它采用了SSM(Spring Spring MVC MyBatis)框架,并结合了微信小程序前端技术。该系统旨在为停车场提供一个智能化的管理和...

    groovy-git:https的只读镜像

    它可以与任何 Java 程序顺利集成,并立即为您的应用程序提供强大的功能,包括脚本功能、领域特定语言创作、运行时和编译时元编程以及函数式编程。下载Bintray 上提供了最新的 Groovy 版本 二进制分发链接位于包页面...

    Coherent Logic World Bank Client-开源

    所有进一步的更新都可以在以下地址找到:https://bitbucket.org/CoherentLogic/coherent-logic-world-bank-client/ *****警告*****世界银行客户端是Java-基于内部的领域特定语言(DSL),用于从世界银行的世界银行...

    UML类图怎么画学习示例

    UML类图(Class ...类图在UML中被视为最基本和最重要的构造块,因为问题域最终会被逐步转化为类来建模,并通过编程语言构建这些类来实现系统。类的命名应尽量使用领域中的术语,明确且无歧义,以便于交流和理解。

    Spring.3.x企业应用开发实战(完整版).part2

    经过历时一年的重大调整改版而成的,本书延续了上一版本追求深度,注重原理,不停留在技术表面的写作风格,力求使读者在熟练使用Spring的各项功能的同时,还能透彻理解Spring的内部实现,真正做到知其然知其所以然。...

    Spring3.x企业应用开发实战(完整版) part1

    经过历时一年的重大调整改版而成的,本书延续了上一版本追求深度,注重原理,不停留在技术表面的写作风格,力求使读者在熟练使用Spring的各项功能的同时,还能透彻理解Spring的内部实现,真正做到知其然知其所以然。...

    antlrworks-1.5.1.jar

    此功能提供语法元素的自然表示,并且是提供自定义功能以增强特定语言的特征的示例。另外,双击源代码图中的元素将立即跳转到相关的源代码。antlrworks:专门用于开发antlr的ide,(不同版本的antlrworks)其内部集成...

Global site tag (gtag.js) - Google Analytics