我正在为Braintree Java库编写一个Clojure包装器,以提供更简洁和惯用的界面。我想提供快速简洁地实例化Java对象的函数,如:
(transaction-request :amount 10.00 :order-id "user42")
我知道我可以明确地执行此操作,如this question:
所示(defn transaction-request [& {:keys [amount order-id]}]
(doto (TransactionRequest.)
(.amount amount)
(.orderId order-id)))
但是这对于许多类来说是重复的,并且当参数是可选的时变得更复杂。使用反射,可以更简洁地定义这些功能:
(defn set-obj-from-map [obj m]
(doseq [[k v] m]
(clojure.lang.Reflector/invokeInstanceMethod
obj (name k) (into-array Object [v])))
obj)
(defn transaction-request [& {:as m}]
(set-obj-from-map (TransactionRequest.) m))
(defn transaction-options-request [tr & {:as m}]
(set-obj-from-map (TransactionOptionsRequest. tr) m))
显然,如果可能的话,我想避免反思。我尝试定义set-obj-from-map
的宏版本,但我的宏功能不够强大。根据{{3}}的解释,可能需要eval
。
有没有办法调用在运行时指定的Java方法,而不使用反射?
提前致谢!
更新解决方案:
根据Joost的建议,我能够使用类似的技术解决问题。宏在编译时使用反射来识别类具有哪些setter方法,然后吐出表单以检查映射中的param并使用它的值调用该方法。
这是宏和使用示例:
; Find only setter methods that we care about
(defn find-methods [class-sym]
(let [cls (eval class-sym)
methods (.getMethods cls)
to-sym #(symbol (.getName %))
setter? #(and (= cls (.getReturnType %))
(= 1 (count (.getParameterTypes %))))]
(map to-sym (filter setter? methods))))
; Convert a Java camelCase method name into a Clojure :key-word
(defn meth-to-kw [method-sym]
(-> (str method-sym)
(str/replace #"([A-Z])"
#(str "-" (.toLowerCase (second %))))
(keyword)))
; Returns a function taking an instance of klass and a map of params
(defmacro builder [klass]
(let [obj (gensym "obj-")
m (gensym "map-")
methods (find-methods klass)]
`(fn [~obj ~m]
~@(map (fn [meth]
`(if-let [v# (get ~m ~(meth-to-kw meth))] (. ~obj ~meth v#)))
methods)
~obj)))
; Example usage
(defn transaction-request [& {:as params}]
(-> (TransactionRequest.)
((builder TransactionRequest) params)
; some further use of the object
))
答案 0 :(得分:8)
你可以在编译时使用反射〜只要你知道你正在处理的类〜找出字段名称,并从中生成“静态”设置器。我写了一些代码,这些代码在不久之前为getter做了很多,你可能会感兴趣。请参阅https://github.com/joodie/clj-java-fields(尤其是https://github.com/joodie/clj-java-fields/blob/master/src/nl/zeekat/java/fields.clj中的def-fields宏)。
答案 1 :(得分:1)
宏可以简单如下:
(defmacro set-obj-map [a & r] `(doto (~a) ~@(partition 2 r)))
但这会使你的代码看起来像:
(set-obj-map TransactionRequest. .amount 10.00 .orderId "user42")
我猜这不是你想要的:)