LoginSignup
3
4

More than 5 years have passed since last update.

実行時DI

Posted at

Interceptor の挙動をプログラム実行中に変えられないものかと思ったのですが、
プログラム実行時 にDIするようなパターンはよくみますが、
プログラム実行中 にDIするようなものが軽くググっても見つけられなかったので作ってみました(そもそもそんなことするなって事かな・・・)。

実現したのは、Seasar2 のAopProxyを使って、プログラム実行中に動的に Interceptor を挟むというものです。
サンプルでやっている事自体には特に意味はないです

ソース

DynamicAopProxy.java

    public class DynamicAopProxy {

        public Map<String, AopProxy> aopProxys = new ConcurrentHashMap<String, AopProxy>();


        @SuppressWarnings("unchecked")
        public <T> T get(Class<T> clazz, String targetMethod, String dynamicParam) {
            AopProxy aopProxy = findAopProxy(clazz, targetMethod, dynamicParam);
            return (T)aopProxy.create();
        }


        private <T> AopProxy findAopProxy(Class<T> clazz, String targetMethod, String dynamicParam) {
            // key が 一意になってくれれば生成方法は何でも良い
            final String key = clazz.getName() + targetMethod + dynamicParam;

            if (!aopProxys.containsKey(key)) {
                HogeInterceptor interceptor = new HogeInterceptor();
                interceptor.setDynamicParam(dynamicParam);

                Pointcut pointcut = new PointcutImpl(new String[] {targetMethod});
                Aspect aspect = new AspectImpl(interceptor, pointcut);
                AopProxy aopProxy = new AopProxy(clazz, new Aspect[]{aspect});
                aopProxys.put(key, aopProxy);
            }
            return aopProxys.get(key);
        }
    }

HogeInterceptor.java

    public class HogeInterceptor implements MethodInterceptor {

        private String dynamicParam;

        public void setDynamicParam(String dynamicParam) {
            this.dynamicParam = dynamicParam;
        }

        public HogeInterceptor() {

        }

        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println(dynamicParam);

            return invocation.proceed();
        }
    }

HogeHoge.java

    public class HogeHoge {
        public void hoge(String str) {
            System.out.println(str);

            System.out.println("Class Name: " + this.getClass().getName() + "\n");
        }
    }

テストコード

main.java

    public static void main() {

        DynamicAopProxy daop = new DynamicAopProxy();

        final String targetMethod = "hoge";

        HogeHoge hoge = new HogeHoge();
        hoge.hoge("Intercepter を挟まない時.");

        hoge = daop.get(HogeHoge.class, targetMethod, "Intercepter を挟むと");
        hoge.hoge("dynamicParam が先に出力されていますね.");
        hoge.hoge("クラス名も変わっています.");

        hoge = daop.get(HogeHoge.class, targetMethod, "Intercepter を挟むと");
        hoge.hoge("同じパラメータなら同じクラスが使用されている事がクラス名からわかりますね.");

        hoge = daop.get(HogeHoge.class, targetMethod, "パラメータを変えると");
        hoge.hoge("クラス名が変わってるいる事から、別のクラスとして登録されているんですね.");
    }

結果

実行結果

Intercepter を挟まない時.
Class Name: Nijuya.DynamicAopProxyTest$HogeHoge

Intercepter を挟むと
dynamicParam が先に出力されていますね.
Class Name: Nijuya.DynamicAopProxyTest$HogeHoge$$EnhancedByS2AOP$$6df52ff5

Intercepter を挟むと
クラス名も変わっています.
Class Name: Nijuya.DynamicAopProxyTest$HogeHoge$$EnhancedByS2AOP$$6df52ff5

Intercepter を挟むと
同じパラメータなら同じクラスが使用されている事がクラス名からわかりますね.
Class Name: Nijuya.DynamicAopProxyTest$HogeHoge$$EnhancedByS2AOP$$6df52ff5

パラメータが変わると
クラス名が変わってるいる事から、別のクラスとして登録されているんですね.
Class Name: Nijuya.DynamicAopProxyTest$HogeHoge$$EnhancedByS2AOP$$53d67244

AopProxy クラスを Map に格納していますが、これが結構重要だったりします。
DIする内容が同じになるような場合、取得する HogeHogeクラスは毎回同じになってほしいのですが、
new AopProxy() の度に毎回新しい HogeHoge クラスをClassLoaderに登録しているようで、
Permanent領域 に新しい HogeHoge クラスをどんどんスタックしていってしまい、Permnent領域がいづれ枯渇してしまいます。
以下は、AopProxy を Map に格納しなかった場合にどうなるかを実験したソースです。

ソース

LeakDynamicAopProxy.java

    public class LeakDynamicAopProxy {

        @SuppressWarnings("unchecked")
        public <T> T get(Class<T> clazz, String targetMethod, String dynamicParam) {
            HogeInterceptor interceptor = new HogeInterceptor();
            interceptor.setDynamicParam(dynamicParam);

            Pointcut pointcut = new PointcutImpl(new String[] {targetMethod});
            Aspect aspect = new AspectImpl(interceptor, pointcut);
            AopProxy aopProxy = new AopProxy(clazz, new Aspect[]{aspect});
            return (T)aopProxy.create();
        }
    }

テストコード

main.java

    public void main2() {
        LeakDynamicAopProxy daop = new LeakDynamicAopProxy();

        final String targetMethod = "hoge";

        HogeHoge hoge;
        hoge = daop.get(HogeHoge.class, targetMethod, "あ");
        hoge.hoge("クラス名に注目!!");
        hoge = daop.get(HogeHoge.class, targetMethod, "あ");
        hoge.hoge("同じ引数ですが、クラス名が違います。");
        hoge = daop.get(HogeHoge.class, targetMethod, "あ");
        hoge.hoge("new AopProxy で毎回新しいクラスそのものを作っているっぽいです。");
        hoge = daop.get(HogeHoge.class, targetMethod, "あ");
        hoge.hoge("その為、呼び出す度に Permanent領域にクラスをスタックしていくリークが発生します。");
    }

結果

実行結果
あ
クラス名に注目!!
Class Name: Nijuya.DynamicAopProxyTest$HogeHoge$$EnhancedByS2AOP$$3f8eae0f

あ
同じ引数ですが、クラス名が違います。
Class Name: Nijuya.DynamicAopProxyTest$HogeHoge$$EnhancedByS2AOP$$7c3fd10d

あ
new AopProxy で毎回新しいクラスそのものを作っているっぽいです。
Class Name: Nijuya.DynamicAopProxyTest$HogeHoge$$EnhancedByS2AOP$$38a5143a

あ
その為、呼び出す度に Permanent領域にクラスをスタックしていくリークが発生します。
Class Name: Nijuya.DynamicAopProxyTest$HogeHoge$$EnhancedByS2AOP$$3bd660e5

あとがき

Mapにつめたとしても、パターンが膨大になれば、HeapもPermanentも枯渇するので、
DI するパターンがある程度限られるような場所でしか使わない方がよいですね。。。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4