Создать прокси для конкретного экземпляра объекта в ближайшем будущем - PullRequest
4 голосов
/ 01 февраля 2012

Я пытаюсь создать прокси-объект, который добавляет некоторые функции к некоторым методам объекта, используя замыкание (let / proxy). Я могу это сделать, к сожалению, я хочу переписать ВСЕ методы из Исходный объект o Я получаю исключение UnsupportedOpretationException, вот пример: ;; реальный объект

(def realcon (java.sql.DriverManager/getConnection "jdbc:h2:tcp://localhost:9092/test"))


(def con 
    (let [msg "FG>"
          xcon rcon]
        (proxy [java.sql.Connection] []
            (createStatement []
                (println msg) ;; access to closure context !
                (.createStatement xcon)))))

(def stmt (.createStatement con))
;;output FG>

(def rs (.executeQuery stmt "select count(*) from serie_sat"))

Если я вызываю любой другой метод из java.sql.Connection, я получаю исключение UnsupportedOperationException, которое я могу сделать вручную путем проксирования ВСЕХ методов, но, возможно, существует лучший способ!.

Спасибо

Ответы [ 3 ]

4 голосов
/ 01 ноября 2015

Вот альтернативный вариант использования reify вместо proxy, поскольку, согласно документам , это «предпочтительно во всех случаях, когда его ограничения не являются запретительными».

(defmacro override-delegate
  [type delegate & body]
  (let [d (gensym)
        overrides (group-by first body)
        methods (for [m (.getMethods (resolve type))
                      :let [f (-> (.getName m)
                                symbol
                                (with-meta {:tag (-> m .getReturnType .getName)}))]
                      :when (not (overrides f))
                      :let [args (for [t (.getParameterTypes m)]
                                   (with-meta (gensym) {:tag (.getName t)}))]]
                  (list f (vec (conj args 'this))
                    `(. ~d ~f ~@(map #(with-meta % nil) args))))]
    `(let [~d ~delegate]
       (reify ~type ~@body ~@methods))))


;; Modifying your example slightly...
(def realcon (java.sql.DriverManager/getConnection "jdbc:h2:tcp://localhost:9092/test"))
(def con 
  (let [msg "FG>"]
    (override-delegate java.sql.Connection realcon
      (createStatement [this]
        (println msg)
        (.createStatement realcon)))))
* 1007Макрос override-delegate ожидает, что тело будет содержать спецификации reify методов, которые вы хотите переопределить.Любое, что вы не переопределите, будет вызвано делегатом.Все reify спецификации, сгенерированные макросом, будут включать подсказки типа для аргументов и возвращаемого значения каждого метода.

В моей реализации есть предостережение: он проверяет только метод names в body, игнорируя аргумент арти / тип для перегруженных методов.Таким образом, в приведенном выше примере, где интерфейс java.sql.Connection обеспечивает несколько перегрузок createStatement, ни один из тех, которые принимают аргументы, не будет определен для con.Не было бы слишком сложно расширить макрос для учета перегрузок, но когда мне нужно такое поведение, мне все равно обычно приходится переопределять их все.

2 голосов
/ 01 февраля 2012

Я только что написал самый нелепый макрос в моей жизни для поддержки этой функциональности. Возможно, есть более простой способ - если я смогу подумать об одном, я обязательно опубликую его, - но это дало мне прохладное, странное чувство и на самом деле, похоже, работает, так что ... вот так.

Редактировать: вот более простой способ; определите функцию, возвращающую обычный proxy, который делегирует все методы (напишите это вручную или создайте его автоматически - код delegating-proxy содержит способ сделать это), использование update-proxy в отдельных случаях для замены только методы, которые нуждаются в замене. Это явно менее круто, чем сумасшедший макрос, поэтому последний должен остаться ниже.

Вот новый упрощенный подход (все еще не очень понятный из-за некоторых проблем с ограничением числа параметров позиции и переменными):

;;; delegates all methods
(defmacro delegating-proxy [o class-and-ifaces ctor-args]
  (let [oname (gensym)
        impls (->> class-and-ifaces
                   (map resolve)
                   (mapcat #(.getDeclaredMethods ^Class %))
                   (group-by #(.getName ^java.lang.reflect.Method %))
                   (vals)
                   (map (fn delegating-impls [^java.lang.reflect.Method ms]
                          (let [mname (symbol (.getName ^java.lang.reflect.Method (first ms)))
                                arity-groups (partition-by #(count (.getParameterTypes ^java.lang.reflect.Method %)) ms)
                                max-arity (max-key #(count (.getParameterTypes ^java.lang.reflect.Method %)) ms)]
                            `(~mname
                              ~@(remove
                                 nil?
                                 (map (fn [agroup]
                                        (let [param-types (.getParameterTypes ^java.lang.reflect.Method (first agroup))
                                              vararg? (and (seq param-types) (or (.isArray ^Class (last param-types)) (<= 20 (count param-types))))
                                              arity  ((if vararg? dec identity) (count param-types))
                                              params (vec (repeatedly arity gensym))
                                              params (if vararg? (conj params '& (gensym)) params)]
                                          (when-not (and vararg? (not= arity max-arity))
                                            (list params `(. ~oname (~mname ~@params))))))
                                      arity-groups)))))))]
    `(let [~oname ~o]
       (proxy ~class-and-ifaces ~ctor-args ~@impls))))

Демонстрация:

user> (def p (delegating-proxy (fn [& args] :foo) [clojure.lang.IFn] []))
#'user/p
user> (update-proxy p {"applyTo" (fn [& args] :bar)})
#<Object$IFn$4c646ebb user.proxy$java.lang.Object$IFn$4c646ebb@1c445f88>
user> (p 1)
:foo
user> (apply p (seq [1]))
:bar

Редактировать: следует оригинальный макрос.

Сначала демоверсия:

user> (.invoke (delegating-proxy (fn [x y] (prn x y))
                 [clojure.lang.IFn] []
                 (invoke [x] :foo))
               :bar)
:foo
user> (.invoke (delegating-proxy (fn [x y] (prn x y))
                 [clojure.lang.IFn] []
                 (invoke [x] :foo))
               :bar :quux)
:bar :quux
nil

delegating-proxy принимает объект, которому он делегируется при вызове, чтобы выполнить метод, не реализованный явно, за которым следуют обычные proxy аргументы.

Во-вторых, код. Я думаю, можно с уверенностью предположить, что там скрываются различные недостатки. На самом деле общая форма этого прямо там; не скрывается Если он достаточно полезен для кого-то, он может быть протестирован и улучшен до некоторой степени гарантированной надежности.

Суть несколько легче читать.

(defmacro delegating-proxy [o class-and-ifaces ctor-args & impls]
  (let [oname (gensym)]
    (letfn [(delegating-impls [^java.lang.reflect.Method ms]
              (let [mname (symbol (.getName ^java.lang.reflect.Method (first ms)))
                    arity-groups (partition-by #(count (.getParameterTypes ^java.lang.reflect.Method %)) ms)
                    max-arity (max-key #(count (.getParameterTypes ^java.lang.reflect.Method %)) ms)]
                `(~mname
                  ~@(remove
                     nil?
                     (map (fn [agroup]
                            (let [param-types (.getParameterTypes ^java.lang.reflect.Method (first agroup))
                                  vararg? (and (seq param-types) (or (.isArray ^Class (last param-types)) (<= 20 (count param-types))))
                                  arity  ((if vararg? dec identity) (count param-types))
                                  params (vec (repeatedly arity gensym))
                                  params (if vararg? (conj params '& (gensym)) params)]
                              (when-not (and vararg? (not= arity max-arity))
                                (list params `(. ~oname (~mname ~@params))))))
                          arity-groups)))))
            (combine-impls [eimpls dimpls]
              (map (fn [e d]
                     (let [e (if (vector? (second e))
                               (list (first e) (next e))
                               e)]
                       (list* (first e) (concat (next e) (next d)))))
                   eimpls
                   dimpls))]
      (let [klass   (resolve (first class-and-ifaces))
            methods (->> class-and-ifaces
                         (map resolve)
                         (mapcat #(.getDeclaredMethods ^Class %)))
            eimpl-specs (set (map (juxt first (comp count second)) impls))
            rm-fn   (fn rm-fn [^java.lang.reflect.Method m]
                      (contains? eimpl-specs [(symbol (.getName m)) (count (.getParameterTypes m))]))
            dimpls  (->> methods
                         (remove rm-fn)
                         (remove #(let [mods (.getModifiers ^java.lang.reflect.Method %)]
                                    (or (java.lang.reflect.Modifier/isPrivate mods)
                                        (java.lang.reflect.Modifier/isProtected mods))))
                         (sort-by #(.getName ^java.lang.reflect.Method %))
                         (partition-by #(.getName ^java.lang.reflect.Method %))
                         (map delegating-impls))
            dimpl-names (set (map first dimpls))
            eimpl-names (set (map first eimpl-specs))
            {eonly false eboth true} (group-by (comp boolean dimpl-names first) impls)
            {donly false dboth true} (group-by (comp boolean eimpl-names first) dimpls)
            all-impls (concat eonly donly (combine-impls eboth dboth))]
        `(let [~oname ~o]
           (proxy ~class-and-ifaces ~ctor-args
             ~@all-impls))))))
0 голосов
/ 24 февраля 2012

Спасибо вам большое, я много выучил, увидев ваш ответ, затем обнаружил некоторые незначительные ошибки.

  1. В функции Delegating-Impls, параметр представляет собой массив объектов Method, которые делают типактерский состав неправильный.Это означает, что max-arity не является числом и не содержит наибольшую арность.

Мне потребовалось понять код, связанный с de varargs.и понял, что конструктор var arg в java (...) предварительно устанавливает параметр las в виде массива, проблема в том, что у объекта есть один метод, например, с 2 параметрами, а другой метод с одним параметром, за которым следует vararg (...) в результате мы получаем 2 метода одной и той же арности, код макроса делегирующего-прокси никогда не вводится в: (когда-нет (и vararg? (не = арность, макс-арность)), потому что макс-арность не являетсяномер! так что прокси-объект пропускает любой метод с массивом в качестве последнего параметра.

Это заняло у меня переписывание делегирующего прокси, и я получил следующий код, который отлично работает, если нетПараметры vararg (...), в противном случае эти методы не будут охвачены реализацией прокси.

Вот код:

</p>

<code>(defmacro instance-proxy [obj mtd-re-filter pre-func post-func]
    (let [cls (class (eval obj))
          interfaces (.getInterfaces cls)
          ifaces (into [] (map #(symbol (.getName %)) interfaces))
          oname (gensym)
          info (gensym)
          impls (->> ifaces
                     (map resolve)
                     (mapcat #(.getDeclaredMethods ^Class %))
                     (group-by #(.getName ^java.lang.reflect.Method %))
                     (vals)
                     (map (fn delegating-impls [ms] ;; ms is an array of "Method" objects
                              (let [mname (symbol (.getName ^java.lang.reflect.Method (first ms)))
                                    arity-groups (partition-by #(count (.getParameterTypes ^java.lang.reflect.Method %)) ms)]
                                  `(~mname
                                    ~@(remove
                                       nil?
                                       (map (fn [agroup]
                                                (let [param-types (.getParameterTypes ^java.lang.reflect.Method (first agroup))
                                                      arity  (count param-types)
                                                      vararg? (and
                                                                  (seq param-types)
                                                                  (.isArray ^Class (last param-types)))
                                                      params (vec (repeatedly arity gensym))]
                                                    (when-not vararg?
                                                        (if (re-matches mtd-re-filter (name mname))
                                                            (list params
                                                                `(swap! ~info ~pre-func)
                                                             `(let [result# (. ~oname (~mname ~@params))]
                                                                  (swap! ~info ~post-func)
                                                                  result#))
                                                            (list params `(. ~oname (~mname ~@params)))))))
                                            arity-groups)))))))]
        `(let [~oname ~obj
               ~info (atom {})]
             (proxy ~ifaces [] ~@impls))))

;;The code abobe is used like so:

(defn pre-test [m]
        (println "ejecutando pre")
        (assoc m :t0 (System/currentTimeMillis)))

(defn post-test [m]
        (println "ejecutando post " m)
        (let [now (System/currentTimeMillis)
              mm (assoc m :t1 now :delta (- now (:t0 m)))]
              (println mm)
              mm))

(def rcon (java.sql.DriverManager/getConnection "jdbc:h2:tcp://localhost:9092/test"))

(def pcon (instance-proxy rcon #"prepareStatement" pre-test post-test))

(def stmt (.prepareStatement pcon "select * from SERIE_SAT"))



;;ejecutando pre
;;ejecutando post  {:t0 1330046744318}
;;{:delta 3, :t1 1330046744321, :t0 1330046744318}
;;#'mx.interware.analyzer.driver/stmt

;;Here we obtain the statistics in a non-intrusive way wich was the objective of this code !
</code>

Это все на данный момент и еще раз спасибоза очень умный макрос!

Saludos

...