About the static and dynamic agents, has always been more troubling many new development, but actually we development, small enough to write a utility class, big to often use the Retrofit of its internal use dynamic proxy, so this article from the basic to the source code parsing, for you to understand more easily Static agent and the Jdk dynamic proxy.

Static proxy: a proxy class that is manually created by developers or exists before the program runs. Static proxy usually represents only one class. Dynamic proxy represents multiple implementation classes under one interface.

Dynamic proxy: Static proxies know what to delegate in advance. Dynamic proxies don’t know what to delegate until runtime. Usually dynamic proxies are implemented by implementing the INVOKE method of the JDK’s InvocationHandler interface

Code practice

The interface for the proxy is required

interface IBook {
    fun toBookName(name: String)
}
Copy the code

Static agent

class BookImpl : IBook {
    override fun toBookName(name: String) {
        println(The title "|$name")}}// TODO:The most important feature of static proxies is that specific proxies are declared in advance
class BookProxy(
    private val iBook: IBook,
    private var objStart: () -> Unit = { println("Pre-start operations.")},private var objStop: () -> Unit = { println("Operation at the end") }
) : IBook {
    override fun toBookName(name: String) {
        // We can do some processing before the implementation
        objStart.invoke()
        iBook.toBookName(name)
        // Do some operations after specific processing
        objStop.invoke()
    }
}

fun main(a) {
    val bookProxy = BookProxy(BookImpl())
    bookProxy.toBookName("Android&Petterp")}Copy the code

Static proxies are relatively simple and have limitations. We need to declare proxy classes in advance, and each proxy needs to be created in advance.

A dynamic proxy

class BookImpl : IBook {
    override fun toBookName(name: String) {
        println("Test the output text$name")}}BookImplHandler looks like an odd proxy, but it's really just a proxy class for help, and we'll end up producing a proxy class that will send calls to it for processing. * The Proxy class itself is dynamically created at run time with the proxy.newProxyInstance () method
class BookImplHandler(
    private val book: IBook,
    private var objStart: () -> Unit = { println("Pre-start operations.")},private var objStop: () -> Unit = { println("Operation at the end") }
) :
    InvocationHandler {
    // TODO: 2020/11/25
    (1) The invoke method accepts variable length arguments. In Kotlin, the array is array,The variable length argument type isvarargThe type does not match.// (2)Kotlin arrays are converted to variable-length arguments, preceded by the * symbol.
    If the method has no arguments,args will be null, and propagating it to Kotlin will result in a NullPointerException.
    As a workaround, use *(args? : arrayOfNulls < Any > (0)),Select the correct part in the extreme case described and extend it to zero parameters.override fun invoke(proxy: Any, method: Method? , args:Array<out Any>?: Any? {
        objStart.invoke()
        valinvoke = method? .invoke(book, *(args ? : emptyArray())) objStop.invoke()return invoke
    }
}

fun main(a) {
    val bookImplHandler = BookImplHandler(BookImplDynamic())
    val iBook = Proxy.newProxyInstance(
        BookImpl::class.java.classLoader,
        BookImpl::class.java.interfaces, bookImplHandler
    ) as IBook
    iBook.toBookName("Test text")}Copy the code

Common dynamic proxy scenarios

Dynamic agents in Retrofit

With Retrofit, when we use Retrofit.Builder().create(), we pass in our interface class, and its create method uses a dynamic proxy inside to generate the corresponding proxy class.

public <T> T create(final Class<T> service) {
  // Check whether it is an interface
  validateServiceInterface(service);
  return (T)
    	// Proxy implementation
      Proxy.newProxyInstance(
          service.getClassLoader(),
          newClass<? >[] {service},new InvocationHandler() {
        		...

            @Override
            public @Nullable Object invoke(Object proxy, 
            Method method, @Nullable Object[] args)
                throws Throwable {
              // If it is an object method, it is fired directly
              if (method.getDeclaringClass() == Object.class) {
                return method.invoke(this, args); }... }}); }Copy the code

Use dynamic proxies to implement onClick injection

As shown below, we declare an annotation that applies to the method and add an Activity extension function that uses the annotation to inject onclick into the Activity of all methods that use the annotation, using reflection + dynamic proxy inside the method.

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class InjectClick(@IdRes val ids: IntArray)

fun Activity.injectClicks(a) {
    javaClass.methods.asSequence().filter {
        it.isAnnotationPresent(InjectClick::class.java)
    }.forEach {
        it.isAccessible = true
        it.getAnnotation(InjectClick::class.java).ids.forEach { id ->
            findViewById<View>(id).apply {
                val clickProxy = Proxy.newProxyInstance(
                    javaClass.classLoader, arrayOf(View.OnClickListener::class.java)
                ) { _, _, _ ->
                    it.invoke(this@injectClicks)}as View.OnClickListener
                setOnClickListener(clickProxy)
            }
        }
    }
}
Copy the code

Source code explore dynamic proxies in the Jdk

Dynamic Proxy source code implementation is relatively simple, we first enter Proxy. NewProxyInstance method, a look.

Proxy.newProxyInstance

public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
            throws IllegalArgumentException {
        Objects.requireNonNull(h);
        // Clone a passed proxy object
        finalClass<? >[] intfs = interfaces.clone();// Get the generated proxy class and maintain a map internallyClass<? > cl = getProxyClass0(loader, intfs);// Get the proxy class constructor
        finalConstructor<? > cons = cl.getConstructor(constructorParams);// Grant permissions
        if(! Modifier.isPublic(cl.getModifiers())) { cons.setAccessible(true);
        }
        Reflection creates the proxy class and passes in the user-implemented mid-tier interface
        return cons.newInstance(new Object[]{h});
}
Copy the code

As described above, we then generate our own proxy class to see the internal call:

Generating proxy classes

fun writeFileProxy(a) {
  	//IApple is the interface I want to delegate
    val name = IApple::class.java.name + "Proxy()"
    val bytes = ProxyGenerator.generateProxyClass(
    name, 
    arrayOf(IApple::class.java))
    val fos = FileOutputStream("$name.class")
    fos.write(bytes)
    fos.close()
}
Copy the code
The sample code
public final class IAppleProxy(a) extends Proxy implements IApple {
   
    private static Method m3;

    public IAppleProxy__/* $FF was: IAppleProxy()*/(InvocationHandler var1)
    throws  {
        super(var1);
    }

    public final void count(a) throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}static {
            m3 = Class.forName("com.android.readbook.proxy.IApple").getMethod("count")}}Copy the code

The generated proxy class is shown above, which implements our interface and proxies all corresponding methods, with some cuts here.

When looking at a proxy class method, its implementation is assisted by the InvocationHandler object, which is our own implementation, and calls its invoke method to call back to our implementation as an interface callback. The whole process is easy to understand and not too difficult.

conclusion

For dynamic proxies in the JDK, when we call proxy.newProxyinstance, we pass in a classLoader for the current class, an array of interfaces to Proxy, and a helper class object that implements the InvocationHandler interface. This generates an in-memory proxy class at runtime that implements our interface and receives an external InvocationHandler helper class object, and implements our interface method proxy by calling the invoke method of the helper class at the specific method implementation location.