如何在Clojure中实现面向方面编程?我们需要在Clojure中使用AOP吗?
假设我们想要使用纯粹的Clojure解决方案(不使用AspectJ)。
面向方面编程通常用于为代码添加横切功能,否则业务逻辑将变得混乱。一个很好的例子是日志记录-您真的不想在整个代码库中到处散布日志记录代码。
在Clojure中,您实际上不需要AOP,因为可以使用其他技术轻松实现这一点。
例如,您可以使用高阶函数来“包装”其他函数以实现横切功能:
; 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
面向切面编程是在Java中实现关注点分离的好方法。Clojure的可组合抽象非常擅长实现此目标。也可以参考这个问题。有关该主题的详细内容可以在《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")
,所有内容都会被包装在日志记录中(和其他一些内容)。topic
,即“一些别名/函数名称”
PS2:也包装了try/catch。
PS3:不太喜欢这样做。恢复为显式记录日志。
around
操作符来包围函数调用。这正是这里所展示的内容。 - denis631