【发布时间】:2011-07-31 05:07:20
【问题描述】:
如何在 Clojure 中实现面向切面的编程?我们需要 Clojure 中的 AOP 吗?
假设我们想要普通的 Clojure 解决方案(没有 AspectJ)。
【问题讨论】:
如何在 Clojure 中实现面向切面的编程?我们需要 Clojure 中的 AOP 吗?
假设我们想要普通的 Clojure 解决方案(没有 AspectJ)。
【问题讨论】:
面向方面的编程通常用于向代码添加横切功能,否则这些功能会与业务逻辑毫无希望地交织在一起。一个很好的例子是日志记录——你真的不希望日志记录代码分散在你的代码库中的任何地方。
您在 Clojure 中并不真正需要 AOP,因为使用 Clojure 中的其他技术很容易实现这一点。
例如,您可以使用高阶函数来“包装”具有横切功能的其他函数:
; a simple function - the "business logic"
(defn my-calculation [a b]
(+ a b))
; higher order function that adds logging to any other function
(defn wrap-with-logging [func]
(fn [& args]
(let [result (apply func args)]
(println "Log result: " result)
result)))
; create a wrapped version of the original function with logging added
(def my-logged-calculation (wrap-with-logging my-calculation))
(my-logged-calculation 7 9)
=> Log result: 16
=> 16
【讨论】:
around 运算符。这正是这里展示的内容
恕我直言,AOP 只是某些类型的静态编程语言的产物。 AFAIKS 它通常只是一堆非标准的编译器扩展。我还没有看到任何不能用更动态的语言更好地解决的 AOP 应用程序。 Clojure 无疑是足够动态的,这甚至没有考虑宏。
我可能错了,但如果是这样,我需要查看一个无法在纯 clojure 中实现的实际 AOP 用例。
编辑:明确一点:我拒绝将 elisp 的建议之类的东西视为面向方面。在动态语言中,这些只是在您需要时使用的技术,除了重新绑定函数定义之外不需要语言支持 - 无论如何,所有 lisps 都支持。
没有必要将它们视为特殊 - 您可以在 clojure 中轻松定义自己的类似 defadvice 的函数。例如,请参阅 compojure's wrap! macro,它实际上已被弃用,因为您通常甚至不需要它。
【讨论】:
面向方面的编程是在 Java 中实现关注点分离的好方法。 Clojure 的可组合抽象很好地实现了这一点。另见this question。 Joy Of Clojure 很好地涵盖了该主题。
作为另一个名称的 Aspect Oriented Clojure 示例,请查看 Ring web 框架
【讨论】:
好吧,您可以轻松地使用 Clojure 进行 AOP。只需在函数中使用元数据来通知您何时需要日志:
(defn ^:log my-calculation
[a b]
(+ a b))
然后您可以重新定义所有函数,自动将它们包装成日志记录。此代码的一部分(连同下面的展开函数):
(defn logfn
[f topic severity error-severity]
(fn [& args]
(try
(if severity
(let [r (apply f args)]
(log* topic {:args args, :ret r} severity)
r)
(apply f args))
(catch Exception e
(if error-severity
(let [data {:args args, :error (treat-error e), :severity error-severity}]
(log* topic data error-severity)
(throw e))
(throw e))))))
(defn logfn-ns
"Wrap function calls for logging on call or on error.
By default, do nothing. When any :log or :log-error, enables logging. If ^:log,
only log on error (default severity error).
Can customize log severity w/ e.g. ^{:log info} or on error log severity likewise."
[ns alias]
(doseq [s (keys (ns-interns ns))
:let [v (ns-resolve ns s)
f @v
log (-> v meta :log)
log-error (-> v meta :log-error)]
:when (and (ifn? f)
(-> v meta :macro not)
(-> v meta :logged not) ;; make it idempotent
(or log log-error))]
(let [log (if (= log true) nil log)
log-error (or log-error "error")
f-with-log (logfn f
(str alias "/" s)
log
log-error)]
(alter-meta! (intern ns s f-with-log)
(fn [x]
(-> x
(assoc :logged true)
(assoc :unlogged @v)))))))
(defn unlogfn-ns
"Reverts logfn-ns."
[ns]
(doseq [s (keys (ns-interns ns))
:let [v (ns-resolve ns s)]
:when (-> v meta :logged)]
(let [f-without-log (-> v meta :unlogged)]
(alter-meta! (intern ns s f-without-log)
(fn [x]
(-> x
(dissoc :logged)
(dissoc :unlogged)))))))
您只需致电 (log/logfn-ns 'my.namespace "some alias"),所有内容都包含日志记录(以及一些)。
PS:我上面的自定义记录器有一个topic,它是“一些别名/函数名”
PS2:也用 try/catch 包装。
PS3:不太喜欢这个。恢复为具有显式日志记录。
【讨论】: