用Clojure生成Java Beans

8
有没有一种简单的方法在Clojure中给定一个向量来生成Java Beans?例如,给定以下向量:
[
    String :key1
    Integer :key2
]

我希望它能生成像这样的代码:
```html

我要求它生成这样的代码:

```
public class NotSureWhatTheTypeWouldBeHere {
    private String key1;
    private Integer key2;

    public NotSureWhatTheTypeWouldBeHere() {}
    public NotSureWhatTheTypeWouldBeHere(String key1, Integer key2) {
        this.key1 = key1;
        this.key2 = key2;
    }

    public void setKey1(String key1) {
        this.key1 = key1;
    }
    public String getKey1() {
        return this.key1;
    }
    public void setKey2(Integer key2) {
        this.key2 = key2;
    }
    public String getKey2() {
        return this.key2;
    }

    // and equals,hashCode, toString, etc.
}

为了情境的描述,我想编写一个用Java编写但调用Clojure库的应用程序。这意味着返回值应该是Java bean(我知道它们不一定要是,但我希望它们是)。一种方法是在Java中定义模型,然后使用Clojure的普通Java互操作来填充Clojure代码中的模型,但我喜欢将简洁的Clojure向量(或映射)扩展成(冗长的)Java bean的想法。

3个回答

4

我认为你的Java代码可能无法与自动生成的Java bean兼容的类很好地协作。你必须至少在Java端有一个接口,才能理解Clojure将返回什么。如果没有这个,你将不得不回退到:

Object result = callClojureLib(params);

然后,无论实际结果是否实现了Java bean协议,你的Java代码都必须进行各种反射操作以便能够调用setter方法,因为缺少类规范。

解决这个问题的另一种方法是使用java.util.Map接口作为Java和Clojure之间的协议。这样,你可以只使用普通的Clojure映射作为传输对象,因为它们可以分配给java.util.Map

user=> (isa? (class {}) java.util.Map)
true

建议使用java.util.Map作为接口类,并将字符串用作键:这些字符串在Clojure方面的工作与关键字一样好,但对于Java API用户来说更容易理解。+1 - mikera
这可能是正确的答案,但它忽略了我特别不想在Java端使用map的重点。我有一大堆Java代码,包括服务、DAO和许多模型对象。我希望能够将其重写为Clojure,以便可以动态生成模型对象(从而大大减少代码量)。从客户端的角度来看,代码与原始的Java代码没有任何区别。 - Kevin
我建议您从客户端Java代码的样本开始,就像您希望它一样,然后尝试找到一个通用接口,可以描述您需要的所有情况,并且您可以从Clojure中实现。问题实际上是在尝试在没有与Java端具有共同契约的情况下动态生成Java bean。 - skuro

2

远非完美,当你尝试使用它时可能会遇到很多未预料的问题,但我认为你可以从以下内容开始:

  (ns genbean)

  (defn -init []
     [[] (atom {})])

  (defn -toString
    [this]
     (str @(.state this)))

  (defn -equals
    [this other]
    (= @(.state this) @(.state other)))

  (defn -hashCode
    [this]
    (hash @(.state this)))

  (defn set-field
     [this key value]
     (swap! (.state this) into {key value}))

  (defn get-field
     [this key]
     (@(.state this) key))

  (defn gen-method-defs [fields]
     (mapcat (fn [[name type]] [[(str "set" name) [type] 'void]
                          [(str "get" name) [] type]]) fields))

  (defn def-access-methods [fields]
     (mapcat (fn [field] [`(defgetter ~field) `(defsetter ~field)]) fields))

  (defmacro defsetter [field]
     `(defn ~(symbol (str "-set" field)) [this# value#]
         (set-field this# ~(keyword field) value#)))

  (defmacro defgetter [field]
     `(defn ~(symbol (str "-get" field))
        [this#]
        (get-field this# ~(keyword field))))

  (defmacro defbean [bean fields]
     `(do
         (gen-class
            :main false
            :state ~'state
            :init ~'init
            :name ~bean
            :methods ~(gen-method-defs fields))
         ~@(def-access-methods (keys fields))
         ))

  (defbean com.test.Foo {Bar Integer Baz String What int})

从Java端使用:

  Foo f = new Foo();
  f.setBaz("hithere");
  f.setBar(12);
  System.out.println("f = " + f);
  Foo f2 = new Foo();
  System.out.println("f2.equals(f) = " + f2.equals(f));
  f2.setBaz("hithere");
  f2.setBar(12);
  System.out.println("f2.equals(f) = " + f2.equals(f));
  System.out.println("(f2.hashCode() == f.hashCode()) = " + (f2.hashCode() == f.hashCode()));

生成:

f = {:Baz "hithere", :Bar 12}
f2.equals(f) = false
f2.equals(f) = true
(f2.hashCode() == f.hashCode()) = true

请注意,您需要编译geanbean命名空间。实现使用原子来存储所有属性,因此请确保您理解权衡。此外,在使用Clojure时,您可能不想使用javabeans,但是您可以创建一些方法来获取和设置保存状态的原子。

0

我认为这应该是可能的,但我不确定你是否完全理解Clojure中的键(可能只是我误读了你的示例代码)。

例如:name之类的键属于clojure.lang.Keyword类型,而不是StringInteger等类型(同时在Clojure中通常不声明类型)。它们经常用于映射(使用{}语法),以检索值,例如以下代码从映射{:key1“hello”,:key2 4}中检索与:key2相关联的值。

(get {:key1 "hello", :key2 4} :key2)
4

我不确定你的示例是想表达你有一个与:key1关联的String和一个与:key2关联的Integer,还是认为:key1String类型。如果是前者,可能需要使用映射而不是向量。

恐怕我对Java bean或你特定的用例了解不够,无法提供更多帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接