How does the creation of a proxy work under the table?

Asked

Viewed 64 times

6

I’ve had a lot of fun creating interface proxies in Java. For example, the following proxy was required on Sqldroid to run version 5.2.4 of Flyway (and also to catch the path of the file created by the driver):

// interface para pegar o path do arquivo

public interface DataSourceHasFilename extends DataSource {

    String getDbFilename();
}

// classe que tem o código do proxy


public class ProxyDataSource {

    private final DroidDataSource ds;
    private final DataSourceHasFilename dshf;

    public ProxyDataSource(DroidDataSource ds) {
        this.ds = ds;
        String dbFilename = "/data/data/" + ds.getPackageName() + "/" + ds.getDatabaseName() + ".db";

        dshf = (DataSourceHasFilename) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {DataSourceHasFilename.class }, (proxy, method, args) -> {
            switch (method.getName()) {
            case "getDbFilename":
                return dbFilename;
            case "getConnection":
                return connWrapper((Connection) method.invoke(ds, args));
            }
            return method.invoke(ds, args);
        });
    }

    public DataSourceHasFilename asDataSourceHasFilename() {
        return dshf;
    }

    private Connection connWrapper(Connection c) {
        return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { Connection.class }, (proxy, method, args) -> {
            switch (method.getName()) {
            case "getMetaData":
            return metadataWrapper((DatabaseMetaData) method.invoke(c, args));
            }
            return method.invoke(c, args);
        });
    }

    public DatabaseMetaData metadataWrapper(DatabaseMetaData dmd) {
        return (DatabaseMetaData) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { DatabaseMetaData.class }, (proxy, method, args) -> {
            switch (method.getName()) {
            case "getDatabaseMajorVersion":
            return 3;
            case "getDatabaseMinorVersion":
            return 7;
            }
            return method.invoke(dmd, args);
        });
    }
}

The creation of proxy calls to java.lang.reflect.Proxy.newProxyInstance. It creates an object with the interfaces passed in the method call newProxyInstance and that, somehow, all calls made to the methods of that interface are delegated to the InvocationHandler.

So, like, underneath the covers, Java creates this object?

It needs some modifier to indicate that it is proxy?

Why doesn’t he create methods bridge when implementing an interface extending a generic interface, but rather 2 distinct concrete methods*?

*: I got this by taking the interface B of Victor Stafusa’s reply and instantiating her as proxy:

Proxy.newProxyInstance(Main.class.getClassLoader(),
    new Class[] { B.class },
    (Object proxy, Method method, Object[] argsM) -> {
            switch (method.getName()) {
            case "getBar1":
                return 1;
            case "isOk":
                return true;
            }
            return null;
        });

1 answer

4


So, like, underneath the covers, Java creates this object?

Note that the newProxyInstance receives as first parameter a ClassLoader and as a second, an array of interfaces. What it will do is create a new class that implements all given interfaces. The class is created by building its bytecodes and then using the ClassLoader given to load it into memory.

However, what about the implementation of the methods of this interface? This is due to the InvocationHandler, which is the lambda you pass as the third parameter. All methods simply invoke it and there you decide what to do. No bridge method is required in this case.

Finally, after this class has been created, it is instantiated and an object of it is returned.

I mean, let’s assume you do this:

Object x = Proxy.newProxyInstance(
        algumClassLoader,
        new Class<?>[] {Runnable.class, Readable.clas},
        algumLambdaAqui)

It will create a runtime class that looks something like this:

public class Proxy$1 extends Proxy implements Runnable, Readable {
    public Proxy$1(InvocationHandler h) {
        super(h);
    }

    @Override
    public void run() {
        h.invoke(this, getClass().getMethod("run"), new Object[] {});
    }

    @Override
    public int read(CharBuffer cb) {
        return (int) h.invoke(this, getClass().getMethod("read", CharBuffer.class), new Object[] { cb });
    }
}

And then return you an instance of that object. The h is InvocationHandler which is stored in a field protected with that name in class Proxy.

It needs some modifier to indicate that it is proxy?

No. A method called newProxyInstance in a class called Proxy it would make no sense to need something to indicate that the returned object is a Proxy, it’s not even?

If you need to know if any object is a proxy, you can use the method isProxyClass(Class<?>) thus:

Object foo = ...;
boolean isProxy = Proxy.isProxyClass(foo.getClass());

Why does it not create bridge methods when implementing an interface that extends a generic interface, but rather 2 distinct concrete methods*?

The mechanism of Proxy was designed in Java 1.3, and is therefore older than the bridge methods of Java 5. It can be argued that all methods are implemented as something similar to what bridge methods do, just by delegating the call to something else.

In order for bridge methods to make sense, it would be necessary to have some kind of covariance between different implementations. However, you cannot make your proxy implement something like Comparator<Integer> because there is no Comparator<Integer> as a class, there is only Comparator. However, you can get around this by declaring interface MeuComparator extends Comparator<Integer>.

In addition, all interface methods (even those marked as default) will be overwritten in the proxy by the code that calls the InvocationHandler, including bridge methods and default interface methods.

Here is an example that shows that the bridge method is also overwritten by Proxy:

import java.util.Comparator;
import java.util.List;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Exemplo2 {

    interface MeuComparator extends Comparator<Integer> {
        @Override
        public int compare(Integer x, Integer y);
    }

    public static void main(String[] args) {
        Comparator a = new MeuComparator() {
            @Override
            public int compare(Integer x, Integer y) {
                return 42;
            }
        };
        System.out.println(a.compare((Object) 5, (Object) 7));

        System.out.println("Métodos de MeuComparator:");
        for (Method m : MeuComparator.class.getMethods()) {
            if ("compare".equals(m.getName())) System.out.println(m);
        }
        System.out.println();

        MeuComparator proxy = (MeuComparator) Proxy.newProxyInstance(
                Exemplo2.class.getClassLoader(),
                new Class<?>[] {MeuComparator.class},
                (o, m, p) -> {
                    System.out.println(m);
                    return 27;
                });

        proxy.compare(Integer.valueOf(3), Integer.valueOf(4));

        // Usa o tipo bruto para enganar o verificador de tipos do compilador.
        Comparator c = proxy;
        c.compare("bar", "foo");
    }
}

Here’s the way out:

42
Métodos de MeuComparator:
public abstract int Exemplo2$MeuComparator.compare(java.lang.Integer,java.lang.Integer)
public default int Exemplo2$MeuComparator.compare(java.lang.Object,java.lang.Object)

public abstract int Exemplo2$MeuComparator.compare(java.lang.Integer,java.lang.Integer)
public default int Exemplo2$MeuComparator.compare(java.lang.Object,java.lang.Object)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.