slim3のtester周りのコードを少し眺めてたら

本日も自宅で陽気にプログラミングの時間です。体調悪いですが、目はさえています。
以下はAppEngineTesterのprepareLocalServices()メソッドですが

    @SuppressWarnings("unchecked")
    protected static void prepareLocalServices(ClassLoader loader) {
        try {
            Class<?> apiProxyLocalImplClass =
                loader.loadClass(API_PROXY_LOCAL_IMPL_CLASS_NAME);
            Class<?> localServerEnvironmentClass =
                loader.loadClass(LOCAL_SERVER_ENVIRONMENT_CLASS_NAME);
            Constructor<?> con =
                apiProxyLocalImplClass
                    .getDeclaredConstructor(localServerEnvironmentClass);
            con.setAccessible(true);
            InvocationHandler ih = new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    if (method.getName().equals("getAppDir")) {
                        return new File("build/test-classes");
                    }
                    if (method.getName().equals("getPort")) {
                        return 0;
                    }
                    return null;
                }
   };
            Object localServerEnvironment =
                Proxy.newProxyInstance(
                    loader,
                    new Class<?>[] { localServerEnvironmentClass },
                    ih);
            apiProxyLocalImpl =
                (Delegate<Environment>) con.newInstance(localServerEnvironment);
        } catch (Throwable cause) {
            ThrowableUtil.wrapAndThrow(cause);
        }
    }

気になっちゃったものなー

con.setAccessible(true);

↑ここ。


ならばこのコードに沿って、前のApiProxyLocalImplをFactoryから生成してたやつを勉強がてら書き直してみましょうか。意味はないです。勉強です。Proxyクラスとかの使い方の勉強になりました。
前のコードは、

	public void setUp() {

		ApiProxy.setEnvironmentForCurrentThread(new TestEnviroment());

		ApiProxyLocal proxy = new ApiProxyLocalFactory()
			.create(new LocalServerEnvironment() {
				@Override
				public void waitForServerToStart()
						throws InterruptedException {
				}
				@Override
				public int getPort() {
					return 0;
				}

				@Override
				public File getAppDir() {
					return new File("nekonekoponpon");
				}

				@Override
				public String getAddress() {
					return null;
				}
			});

		proxy.setProperty(LocalDatastoreService.NO_STORAGE_PROPERTY , Boolean.TRUE.toString());
		ApiProxy.setDelegate(proxy);
	}

であって、このApiProxyLocalFactory().createの引数に渡しているLocalServerEnvironmentは、LocalServerEnvironmentのインスタンスそのものなのであろう。

で、↓slim3コード参考にさせてもらいこのようにしてもできるのですが、

public void setUp() throws Exception {

	ApiProxy.setEnvironmentForCurrentThread(new TestEnviroment());
		
	ClassLoader loader = LocalBaseTestCase.class.getClassLoader();
	Class<?> localServerEnvironmentClass = loader.loadClass("com.google.appengine.tools.development.LocalServerEnvironment");
	Class<?> apiProxyLocalImplClass = loader.loadClass("com.google.appengine.tools.development.ApiProxyLocalImpl");
        
	Constructor<?> con = apiProxyLocalImplClass.getDeclaredConstructor(localServerEnvironmentClass);
        
	con.setAccessible(true);
        
	InvocationHandler ih = new InvocationHandler() {
             public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                  if (method.getName().equals("getAppDir")) {
                       return new File("nekoneko");
                  }
                  if (method.getName().equals("getPort")) {
                      return 0;
                  }
                  return null;
              }
    };
        
	Object localServerEnvironment = Proxy.newProxyInstance(loader,new Class[] { localServerEnvironmentClass },ih);
	ApiProxyLocal proxy = (ApiProxyLocal) con.newInstance(localServerEnvironment);

	proxy.setProperty(LocalDatastoreService.NO_STORAGE_PROPERTY,Boolean.TRUE.toString());
	ApiProxy.setDelegate(proxy);
}

前回との違いはcon.newInstance(localServerEnvironment)の引数のlocalServerEnvironmentは、Proxyクラスから作られたProxyオブジェクトであって、Proxy生成対象となるクラスのメソッドが呼ばれた場合には、Proxy.newProxyInstance()の第3引数のInvocationHandlerのinvoke()を呼びますということらしい。それがProxyというものらしい。

イメージ的には、LocalServerEnvironmentのメソッドを全てオーバライドして、全てのメソッドでinvoke()に処理を委譲し、とかいうイメージになるのでしょうか? それでinvoke()の引数として実際のメソッドをMethodクラスのオブジェクトとして与えている。AOPの感じですかね。

今回の場合は、

package com.google.appengine.tools.development;
import java.io.File;
public interface LocalServerEnvironment
{
    public abstract File getAppDir();
    public abstract String getAddress();
    public abstract int getPort();
    public abstract void waitForServerToStart()
        throws InterruptedException;
}

のgetAppDir()とgetPort()が呼ばれたときに「new File("nekoneko")」と「0」をそれぞれreturnしていて、それ以外はnullを返しているのであるな。

con.setAccessible(true);

↑これやらないと、

ApiProxyLocal proxy = (ApiProxyLocal) con.newInstance(localServerEnvironment);

↑ここでアクセスできないって怒られるね。

あーメモった。メモッた。間違えていたら、すいません。いろいろ知らないこと多すぎ。slim3のコードを読むのは勉強になるので、頑張って少しずつ読んでいきましょう。

con.setAccessible(true);

こんなのぜんぜん知らなかったもの。だめだだめだ。俺はだめだ。

あとslim3だと、そもそもtest用にこのjarやあのjarをパスに追加しろとかもないっぽいですね。依存してない。よいですねー。