Help us understand the problem. What is going on with this article?

実行時DI

More than 3 years have passed since last update.

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 するパターンがある程度限られるような場所でしか使わない方がよいですね。。。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away