directory

  • preface
  • What is dynamic proxy, and what is the difference between static proxy and dynamic proxy
  • Simple use of Java dynamic proxies
  • Java dynamic proxy principle interpretation
  • Use of dynamic proxies in Android

I believe that the word dynamic proxy is both familiar and strange to many Android development partners. Familiar is because it may often listen to some groups, bloggers installed B expert hands hanging in the mouth, strange because in the daily Android development does not seem to have used this thing. Also did not go to learn this thing (especially the training class out of the partners, as far as I know most Android training classes will not say this thing, a small part of it is just a simple mention on the skip). And this kind of familiar and strange feeling makes some small partners feel that dynamic agent is very advanced, is the knowledge that those bosses only use. And I, today to help small partners to pull down the dynamic agent altar, next time have to install force small expert hand and you say dynamic agent when you knock his head said L (old) Z (son) also can 😁.

What is dynamic proxy, and what is the difference between static proxy and dynamic proxy

Both dynamic and static proxies can be considered as a use of the proxy pattern. What is the proxy pattern?

An object is provided with a proxy object that provides and controls access to the original object. Agent mode is actually a very simple design mode, the specific details of partners can be baidu, here is not much to introduce.

In a static proxy, you need to manually write a proxy class for the proxied class (the delegate class) and a new method for each method that needs to be proxied, which is done before compilation. Dynamic proxies can generate a proxy class dynamically at run time without having to manually implement each proxied method. Simply delegate class has 1000 methods need to be in the agent (such as agent is everybody is often used to for the purpose of the additional print before, during, and after each method performs a output), using static agent you need to manually write the proxy class and implement the 1000 method, which use static agent requires simple few lines of code you can realize this problem.

Simple use of Java dynamic proxies

Related classes for dynamic proxies

Dynamic proxy has two main related classes:

  • Proxy (under the java.lang.Reflect package) is responsible for managing and creating Proxy classes.
  • The InvocationHandler interface, which has only one Invoke method and is responsible for the method invocation part, is the method we need to implement in the dynamic proxy
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
// The three parameters are the proxy class proxy method executed by method args method passed in
// The return value is the return value of the method execution
Copy the code

As an example, suppose we have the following interface and two classes that operate on orders

public interface OrderHandler {
    void handler(String orderId);
}

public class NormalOrderHandler implements OrderHandler {
    @Override
    public void handler(String orderId) {
        System.out.println("NormalOrderHandler.handler():orderId:"+orderId); }}public class MaxOrderHandler implements OrderHandler {
    @Override
    public void handler(String orderId) {
        System.out.println("MaxOrderHandler.handler():orderId:"+orderId); }}Copy the code

This requires us to print a string of information before and after each handler call, and to intercept the first 10 bits if the length of the orderId is greater than 10 (don’t ask me which RZ raised this requirement, it’s not a blogger, you know 😁). At this point we can do the following code:

// Create a handler class that implements the InvocationHandler interface and implements the invoke method
public class OrderHandlerProxy implements InvocationHandler {

    // The delegate class in this case is equivalent to the object of the class that implements OrderHandler
    Object target;

    public Object bind(Object target){
        this.target=target;

        // Create a Proxy class using the static method of Proxy.
        // The second argument is the set of interfaces implemented by the delegate class, and the third argument is the processor class itself
        return Proxy.newProxyInstance(this.target.getClass().getClassLoader(),
                this.target.getClass().getInterfaces(),this);

    }
    
    // Second point
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        // Determine if the method executed is the handler method we need to delegate
        if (method.getName().equalsIgnoreCase("handler")){
            System.out.println("OrderHandlerProxy.invoke.before");
            String orderId= (String) args[0];

            // Limit the length of orderId
            if (orderId.length()>=10){
                orderId=orderId.substring(0.10);
            }
            
            Third, this is where the delegate class's methods are called through reflection
            Object invoke = method.invoke(target, orderId);
            
            System.out.println("OrderHandlerProxy.invoke.after");
            return invoke;
        }else {
            // The method currently executed is not the corresponding method to execute the delegate without doing anything when we need the delegate method
            System.out.println("Method.name:"+method.getName());
            returnmethod.invoke(target,args); }}}Copy the code

Next comes the code that uses the dynamic proxy

public class NormalApplicationClass {
    public void handlerOrder(OrderHandler orderHandler,String orderId){
        // Create a processor object
        OrderHandlerProxy proxy=new OrderHandlerProxy();
        // Create a proxy class for the passed object that implements the OrderHandler interface and instantiate the object
        OrderHandler handler = (OrderHandler) proxy.bind(orderHandler);
        
        handler.handler(orderId);
        System.out.println(handler.toString());
    }
    public static void main(String[] args) {
        NormalApplicationClass app=new NormalApplicationClass();
        app.handlerOrder(new MaxOrderHandler(),"012345678999"); }}Copy the code

The return values are as follows

OrderHandlerProxy.invoke.before
MaxOrderHandler.handler():orderId:0123456789
OrderHandlerProxy.invoke.after
Method.name:toString
com.against.javase.rtti.use.MaxOrderHandler@d716361
Copy the code

As we can see above, we have successfully printed a list of things before and after executing the handler method, and have limited the length of the orderId without affecting other method calls like toString on the object itself.

Interpretation of dynamic proxy principle

NewProxyInstance () : static Proxy: static Proxy: static Proxy: static Proxy: static Proxy: static Proxy: static Proxy: static Proxy

public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
            throws IllegalArgumentException
    {
        // Omit some code, mainly some Jvm security verification and permission verification

        /* * Gets or generates a proxy Class object. Why get or build? * This is due to the presence of a caching mechanism, * when the newProxyInstance method is called the second time and the cache of the last generated proxy class has not expiredClass<? > cl = getProxyClass0(loader, intfs);/* * Get the constructor of the proxy class through reflection and generate the proxy class object */
        try {
            / /...

            // Get the constructor of the proxy class by reflection,
            finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
            if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run(a) {
                        cons.setAccessible(true);
                        return null; }}); }// Use the constructor to generate the proxy object, which returns the object for our proxy class
            return cons.newInstance(new Object[]{h});
        } / /... Omit some extraneous code
    }
Copy the code

The Proxy takes the Class object of the Proxy Class, gets the constructor through reflection, injects an instance of InvocationHandler from the constructor (our own implementation of the handler Class) and creates an instance of the Proxy Class. The Class object of the proxy Class is obtained in Class<? > cl = getProxyClass0(loader, intfs); Here, so let’s go ahead and see that it’s pretty simple, just doing some normal checks and calling proxyClassCache. Get (loader, interfaces); . We looked and found

    /** * a cache of proxy classes */
    private static finalWeakCache<ClassLoader, Class<? >[], Class<? >> proxyClassCache =new WeakCache<>(new KeyFactory(), new     ProxyClassFactory());
// When seeing WeakCache, we can guess what WeakCache does. The name of the ProxyClassFactory Class is used to create the Class object of the proxy Class
Copy the code

The main method in this class is the Apply method.

@Override
publicClass<? > apply(ClassLoader loader, Class<? >[] interfaces) {/ /... Generate proxyName, the name of the proxy class
// The specific generation operation is in this method
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
try {
    // This method is a native method that loads Class objects through the Class loader and byte stream of Class names and Class files.
    return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        throw newIllegalArgumentException(e.toString()); }}Copy the code

Already can locate classes next generation operation in ProxyGenerator. GenerateProxyClass (proxyName, interfaces, accessFlags); This part of the code is a bit cumbersome, we just briefly look at one place.

        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
// Invoke (toString); // Invoke (toString); // Invoke (toString); // Invoke (toString);

// Generate a constructor with the InvocationHandler argument
private ProxyGenerator.MethodInfo generateConstructor(a) throws IOException {
        ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>"."(Ljava/lang/reflect/InvocationHandler;) V".1);
        DataOutputStream var2 = new DataOutputStream(var1.code);
        this.code_aload(0, var2);
        this.code_aload(1, var2);
        var2.writeByte(183);
        var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy"."<init>"."(Ljava/lang/reflect/InvocationHandler;) V"));
        var2.writeByte(177);
        var1.maxStack = 10;
        var1.maxLocals = 2;
        var1.declaredExceptions = new short[0];
        return var1;
    }
Copy the code

The next step is to generate the corresponding proxy method based on the interface implemented by the delegate class, and generate a constructor that needs to pass the InvocationHandler object, only instead of the.java file that we wrote by hand, this generates a.class file. .class files, on the other hand, are already compiled and can be loaded and executed by the JVM, eliminating the need for compilation. Let’s look at what the Proxy class looks like for us:

public final class $Proxy0 extends Proxy implements OrderHandler {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final String toString(a) throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final void handler(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final int hashCode(a) throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.against.javase.rtti.use.OrderHandler").getMethod("handler", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

We can see that the proxy class essentially implements the interface of the delegate class and passes every call to the invoke method of the delegate class to the invoke method of the processor class we wrote. The dynamic proxy is not a mystery haha 😁.

Use of dynamic proxies in Android

The paper come zhongjue shallow, must know this to practice this sentence is very reasonable, the blogger in the process of learning to feel deeply see one thousand times as their own to write again, some knowledge points looked at the feeling understand, do not write themselves can not find some problems, do not write it is difficult to have a deep image. Now, most blogs that talk about dynamic proxies do what I did above and just give you a simple example of how to do it and then you end up feeling like you understand everything and you don’t understand anything.

A simple imitation of a butterknife

When choosing specific examples, I thought of something like Hook SystemManager, but this part is more about Android system level than dynamic proxy. It’s tedious to write and it’s hard to do anything interesting with very little code. Finally, I decided to imitate the simple function of the butter knife. Speaking of the butter knife, everyone should be familiar with it, and the following two annotations must be used:

@BindView(a)@OnClick(a)Copy the code

Today we will implement the functions of these two annotations. Let’s go directly to the code to define the two annotations

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
    int value(a);
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    int[] value();
}
Copy the code

Then use these two annotations in the Activity

public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.tv)
    private TextView tv;

    @InjectView(R.id.iv)
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewInject.inject(this);
        tv.setText("inject success!");

        iv.setImageDrawable(getResources().getDrawable(R.mipmap.ic_launcher));
    }

    @OnClick({R.id.tv,R.id.iv})
    public void onClick(View view){
        switch (view.getId()){
            case R.id.tv:
                Toast.makeText(this."onClick,TV",Toast.LENGTH_SHORT).show();
                break;
            case R.id.iv:
                Toast.makeText(this."onClick,IV",Toast.LENGTH_SHORT).show();
                break; }}}Copy the code

This part of the code is very simple, there is nothing to say, focus on viewinject.inject (this); here

public class ViewInject {

    public static void inject(Activity activity) {

        Class<? extends Activity> activityKlazz = activity.getClass();

        try {

            injectView(activity,activityKlazz);
            proxyClick(activity,activityKlazz);

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch(InvocationTargetException e) { e.printStackTrace(); }}private static void proxyClick(Activity activity,Class<? extends Activity> activityKlazz) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        Method[] declaredMethods = activityKlazz.getDeclaredMethods();

        for (Method declaredMethod : declaredMethods) {

            // Get the method marked with the OnClick annotation
            if (declaredMethod.isAnnotationPresent(OnClick.class)){
                OnClick annotation = declaredMethod.getAnnotation(OnClick.class);

                int[] value = annotation.value();

                // Create the handler class and generate the proxy class, and bind the activity method we marked OnClick to the handler class
                OnClickListenerProxy proxy=new OnClickListenerProxy();
                Object listener=proxy.bind(activity);
                proxy.bindEvent(declaredMethod);


                for (int viewId : value) {
                    Method findViewByIdMethod =
                            activityKlazz.getMethod("findViewById".int.class);

                    findViewByIdMethod.setAccessible(true);
                    View view = (View) findViewByIdMethod.invoke(activity, viewId);

                    // Inject our proxy class into the corresponding View's onClickListener via reflection
                    Method setOnClickListener = view.getClass().getMethod("setOnClickListener", View.OnClickListener.class);

                    setOnClickListener.setAccessible(true); setOnClickListener.invoke(view,listener); }}}}private static void injectView(Activity activity,Class<? extends Activity> activityKlazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

        /** * InjectView is a simple way to inject a view. Use reflection to retrieve the activity field marked with the InjectView annotation. The findViewById method is then retrieved by reflection and executed to get an instance of the View and assign the instance to the field in the activity
        for (Field field : activityKlazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(InjectView.class)) {
                InjectView annotation = field.getAnnotation(InjectView.class);

                int viewId = annotation.value();

                    Method findViewByIdMethod =
                            activityKlazz.getMethod("findViewById".int.class);

                    findViewByIdMethod.setAccessible(true);
                    View view = (View) findViewByIdMethod.invoke(activity, viewId);
                    field.setAccessible(true); field.set(activity, view); }}}}Copy the code

The code above is commented, so let’s take a look at OnClickListenerProxy:

public class OnClickListenerProxy implements InvocationHandler {

    static final String TAG="OnClickListenerProxy";

    // This is actually our related activity
    Object delegate;

    // This is the activity whose OnClick method is marked. The final action is to replace the OnClick call in the OnClickListener with a call to the Event method
    Method event;


    public Object bind(Object delegate){
        this.delegate=delegate;
        // Generate a proxy class
        return Proxy.newProxyInstance(this.delegate.getClass().getClassLoader(),
                new Class[]{View.OnClickListener.class},this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Log.e(TAG,"invoke");
        Log.e(TAG,"method.name:"+method.getName()+" args:"+args);

        // Call the onClick method instead of our event method.
        if ("onClick".equals(method.getName())){
            for (Object arg : args) {
                Log.e(TAG,"arg:"+arg);
            }
            View view= (View) args[0];
            return event.invoke(delegate,view);
        }
        return method.invoke(delegate,args);
    }
    public void bindEvent(Method declaredMethod) { event=declaredMethod; }}Copy the code

With the code implementation above, we were able to get the project up and running and successfully implement the functionality we needed. Of course there are a lot of details to be worked out, but that’s enough for demonstration learning, and isn’t it Cool that we’ve implemented some of the functionality of a well-known open source library like Butter Knife in our own way?

But bloggers do not advise to their projects using these, we still use the butter knife, because the reflection and dynamic proxy brings with it a part of the performance of the loss, and butter knife is using a compile-time annotation can realize the function of the above, the form of at runtime will not bring the loss of performance, interested friends can go to Google related articles.