Annotations we use them a lot, and many frameworks provide a lot of annotations for us to use, For example, @route (path = “/test/activity”) for ARouter, @bindView (R.i.User) for Butterknife EditText username; But, have you customized annotations and written your own annotation handler? Reflection sounds fancy, but when you really get to know it, it’s just some API calls; Dynamic proxy is just a reflection technique based on static proxy (proxy mode); This article will give you a better understanding of annotations, reflection, and dynamic proxies.


The sample code for this article is posted on Github.


annotations

Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the actions of the code they annotate.

Annotations can be used in many ways, such as:

  • Provide information to the compiler: The compiler can use annotations to check for errors or suppress warnings
  • Compile or deploy-time processing: you can generate code, XML, files, and so on
  • Runtime processing: Annotations can be checked at runtime

Format of annotations

The format of the notes is as follows:

@Persilee

class MyClass {... }

Copy the code

Annotations begin with @ followed by content. Annotations can contain elements such as:

@Persilee(id=Awesome!, value = "lsy")

class MyClass {... }

Copy the code

If there is only one value element, the name can be omitted. If there are no elements, the parentheses can be omitted. For example

@Persilee("lsy"// There is only one value element

class MyClass {... }



@Persilee // No element

class MyClass {... }

Copy the code

If the annotation has the same type, it is a duplicate annotation, such as

@Persilee("lsy")

@Persilee("zimu")

class MyClass {... }

Copy the code

Annotation statement

The definition of an annotation is similar to the definition of an interface, with the @ prefix in front of the keyword interface, as in:

@interface Persilee {

    int id(a);

    String value(a);

}

Copy the code

Annotation type

Int ID () and String value() are annotation types. They can also define optional defaults, such as:

@interface Persilee {

    int id(a);

    String value(a) default "lsy";

}

Copy the code

When using annotations, if the annotation type of the defined annotation does not have a default value, it must be assigned, as in:

@Persilee(id = Awesome!// id must be assigned. For example, @persilee will prompt you that ID must be assigned

class MyClass {... }

Copy the code

Yuan notes

Annotations above annotations are called meta-annotations, such as annotations

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.SOURCE)

@interface Persilee {

    int id(a);

    String value(a) default "lsy";

}

Copy the code

There are several meta-annotation types defined in java.lang.annotation (@retention, @target are commonly used), such as

@retention specifies how annotations are stored, as we know from retentionPolicy.java (which is an enumeration), such as:

public enum RetentionPolicy {

    SOURCE, // The annotations of the tag remain only at the source level and are ignored by the compiler.

    CLASS, // Annotations of the tag are retained by the compiler at compile time, but ignored by the Java Virtual Machine (JVM).

    RUNTIME // The annotation of the tag is reserved by the JVM so that the runtime environment can use it.

}

Copy the code

@target specifies the scope in which the annotation can be used. We know the scope from elementType. Java (which is an enumeration) as follows:

public enum ElementType {

    TYPE, / / class

    FIELD, // Field or attribute

    METHOD, / / method

    PARAMETER, / / parameters

    CONSTRUCTOR, // constructor

    LOCAL_VARIABLE, // Local variables

    ANNOTATION_TYPE, // Can also be used for annotations

    PACKAGE, / / package

    TYPE_PARAMETER, // Type parameter

    TYPE_USE // Any type

}

Copy the code

TYPE_PARAMETER (type parameter), TYPE_USE (any type name) may not be well understood. If Target is set to @target ({elementtype.type_parameter}), Representations can be used on type parameters of generics (which were introduced in the previous article), such as:

public class TypeParameterClassThe < @Persilee T{

    public <@Persilee T> foo(T t) {

        return null;

    }

}

Copy the code

If Target is set to @target ({elementtype.type_use}), it can be used on any type, such as:

TypeParameterClass<@Persilee String> typeParameterClass = new TypeParameterClass<>();

@Persilee String text = (@Persilee String)new Object();

Copy the code

The @documented annotation indicates that a specified annotation is used, and these elements will be Documented using the Javadoc tool.

The @Inherited annotation indicates that annotation types can be Inherited from superclasses.

The @REPEATable annotation indicates that the tagged annotation can be applied to the same statement or type multiple times.

Annotation Application Scenario

Depending on how the @Retention meta-annotation is defined, annotations can be used in one of the following three scenarios:

level technology instructions
The source code APT Classes and all member information in which annotations and annotation declarations can be obtained at compile time are typically used to generate additional helper classes.
The bytecode Bytecode enhancement After the Class is compiled, you can modify the Class data to modify the code logic. You can use annotations to distinguish whether changes are needed or to determine whether changes are made to different logic.
The runtime reflection While the program is running, the annotation and its elements are dynamically retrieved by reflection technology to make different logical judgments.

Small case study (syntax checking with annotations)

We define a weekDay field that is of type weekDay enumeration type so that we can set the values specified in the enumeration, such as:

class WeekDayDemo {



    private static WeekDay weekDay;



    enum WeekDay {

        SATURDAY,SUNDAY

    }



    public static WeekDay getWeekDay(a) {

        return weekDay;

    }



    public static void setWeekDay(WeekDay weekDay) {

        WeekDayDemo.weekDay = weekDay;

    }



    public static void main(String[] args) {

        setWeekDay(WeekDay.SATURDAY);

        System.out.println(getWeekDay());

    }

}

Copy the code

As we all know, enumerations in Java are essentially special static member variables. At runtime, all enumerations are loaded into memory as singletons, which consume a lot of memory. So, is there an optimized solution?

Instead of enumerations, we use constants and @intdef meta-annotations, such as:

class IntdefDemo {



    private static final int SATURDAY = 0;

    private static final int SUNDAY = 1;



    private static int weekDay;



    @IntDef({SATURDAY, SUNDAY})

    @Target({ElementType.FIELD, ElementType.PARAMETER})

    @Retention(RetentionPolicy.SOURCE)

    @interface WeekDay { // Create a custom WeekDay annotation



    }



    public static void setWeekDay(@WeekDay int weekDay) // Use WeekDay annotations to restrict parameter types

        IntdefDemo.weekDay = weekDay;

    }



    public static void main(String[] args) {

        setWeekDay(SATURDAY); // Only SATURDAY, SUNDAY can be passed

    }

}

Copy the code

APT annotation processor

Annotation Processor APT(Annotation Processor Tools) is used to process annotations. Compiled Java files need to be compiled by Javac into bytecode (Class) files that can be loaded by VMS. Annotation Processor is a tool provided by Javac. Used to process annotation information at compile time.

Now that we have defined @persilee annotations, we can write a simple annotation processor to handle @persilee annotations. We can create a new Java Module and create a PersileeProcessor class, such as:

@SupportedAnnotationTypes("net.lishaoy.anreprdemo.Persilee")  // Specify the annotation to work with

public class PersileeProcessor extends AbstractProcessor {



    @Override

    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        Messager messager = processingEnv.getMessager(); //

        messager.printMessage(Diagnostic.Kind.NOTE, "APT working ...");

        for (TypeElement typeElement: set) {

            messager.printMessage(Diagnostic.Kind.NOTE,"= = = >" + typeElement.getQualifiedName());

            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(typeElement);

            for (Element element: elements) {

                messager.printMessage(Diagnostic.Kind.NOTE,"= = = >" + element.getSimpleName());

            }

        }



        return false;

    }

}

Copy the code

Then, create a new resources directory under the main directory, as shown:

annotation

This directory structure is dead, we must write, then in the javax.mail. Annotation. Processing. The Processor file annotation Processor register need to be addressed, such as

net.lishaoy.aptlib.PersileeProcessor

Copy the code

Finally, introduce modules in your app’s build.gradle file, such as

dependencies {

.



  annotationProcessor project(':aptlib')

}

Copy the code

When you Build the project, the Task: : app compileDebugJavaWithJavac Task print we log information in the annotation processing program, such as:

Note: APT working...

Note: = = = >.net. Lishaoy. Anreprdemo. Persilee

Note: = = = > MainActivity

Copy the code

Because we only use the @persilee annotation in MainActivity, as follows:

@Persilee(id = Awesome!, value = "lsy")

public class MainActivity extends AppCompatActivity {



    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);



        setContentView(R.layout.activity_main);

    }

}

Copy the code

reflection

In general, when we use a class, we must know what it is and what it does. We instantiate the class directly and then operate with the class object.

Cook cook = new Cook(); // Instantiate an object

cook.cookService("🍅");

Copy the code

Reflection does not know what the initialized class object is at the beginning, nor can it use the new keyword to create the object. Reflection does not know what the class to operate on is at run time, and can obtain the complete construction of the class at run time, and call the corresponding method.

The Java reflection mechanism provides the following functions:

  • Constructs an object of any class at run time
  • Gets or modifies the member variables and methods that any class has at run time
  • Call methods (properties) of any object at run time

Class Class

Class is a Class that encapsulates the information about the corresponding Class of the current object. Each Class we write can be regarded as an object, which is an object of the Java.lang. Class Class, which describes the Class.

Get the Class object

Class objects can be obtained in three ways:

  • Gets the class name by class name. Class
  • Get the object name from the object.getClass ()
  • Get class.forname (Class name) by Class name
Cook cook = new Cook();

Class cookClass = Cook.class;

Class cookClass1 = cook.getClass();

Class cookClass2 = Class.forName("net.lishaoy.reflectdemo.Cook");

Copy the code

Create an instance

We can use reflection to generate instances of objects, such as:

Class cookClass = Cook.class;

Cook cook1 = (Cook) cookClass.newInstance();

Copy the code

Get constructor

The constructor can be obtained as follows:

  • Constructor getConstructor(Class[] params) : getConstructor(Class[] params) : getConstructor(Class[] params) : getConstructor(Class[] params)
  • Constructor[] getConstructors() : Get all the public constructors of the class
  • Constructor getDeclaredConstructor(Class[] params) : getDeclaredConstructor(private included)
  • Constructor[] getDeclaredConstructors() : Get all class constructors (regardless of access level)

Let’s create a new Person for our demo, such as:

public class Person {



    public String name;

    private int age;



    public Person(String name, int age) {

        this.name = name;

        this.age = age;

    }



    public Person(a) {

        super(a);

    }



    public String getName(a) {

        System.out.println("get name: " + name);

        return name;

    }



    public void setName(String name) {

        this.name = name;

        System.out.println("set name: " + this.name);

    }



    public int getAge(a) {

        System.out.println("get age: " + age);

        return age;

    }



    public void setAge(int age) {

        this.age = age;

        System.out.println("set age: " + this.age);

    }



    private void privateMethod(a){

        System.out.println("the private method!");

    }

}

Copy the code

A generic class with private properties and methods.

Let’s create a new class GetConstructor to demonstrate how to use the GetConstructor method, as in:

class GetConstructor {



    public static void main(String[] args) throws

            ClassNotFoundException,

            NoSuchMethodException,

            IllegalAccessException,

            InvocationTargetException,

            InstantiationException 
{



        String className = "net.lishaoy.reflectdemo.entity.Person";

        Class<Person> personClass = (Class<Person>) Class.forName(className);



        // Get all the constructor objects

Constructor<? >[] constructors = personClass.getConstructors();

        for(Constructor<? > constructor: constructors) {

            System.out.println("Get all constructor objects:" + constructor);

        }



        // Get a constructor object

        Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);

        System.out.println(Get a constructor object: + constructor);



        // Call the constructor's newInstance() method to create the object

        Person person = constructor.newInstance("lsy".66);

        System.out.println(person.getName() + "," + person.getAge() );

    }



}

Copy the code

The output is as follows:

For all the constructor object: public net. Lishaoy. Reflectdemo. Entity. The Person (Java. Lang. String, int)

For all the constructor object: public net. Lishaoy. Reflectdemo. Entity. The Person ()

To obtain a constructor object: public net. Lishaoy. Reflectdemo. Entity. The Person (Java. Lang. String, int)

lsy, 66

Copy the code

Access method

The methods to obtain the method are as follows:

  • Method getMethod(String name, Class[] params) : Gets a named public Method using a specific parameter type
  • Method[] getMethods() : Gets all public methods of the class
  • Method getDeclaredMethod(String name, Class[] params) : use the close-up parameter type to get the named Method of the Class declaration
  • Method[] getDeclaredMethods() : Get all methods declared by a class

Let’s create a new GetMethod to show how to get and call a method, like this:

class GetMethod {



    public static void main(String[] args) throws

            ClassNotFoundException,

            NoSuchMethodException,

            IllegalAccessException,

            InstantiationException,

            InvocationTargetException 
{



Class<? > aClass = Class.forName("net.lishaoy.reflectdemo.entity.Person");



        // Get all public methods (including methods inherited from their parent)

        Method[] methods = aClass.getMethods();

        for (Method method: methods) {

            System.out.println("Get all public methods:" + method.getName() + "()");

        }



        System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = =");



        // Get all methods (excluding superclass methods)

        methods = aClass.getDeclaredMethods();

        for (Method method: methods) {

            System.out.println("Get all methods:" + method.getName() + "()");

        }



        System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = =");



        // Get the specified method

        Method method = aClass.getDeclaredMethod("setAge".int.class);

        System.out.println("Get the specified method :" + method);



        // Call the method

        Object instance = aClass.newInstance();

        method.invoke(instance, 66);



        // Call private methods

        method = aClass.getDeclaredMethod("privateMethod");

        method.setAccessible(true); // This method needs to be called and set to true

        method.invoke(instance);



    }



}

Copy the code

The result is as follows:

Get all public methods: getName()

Get all public methods: setName()

Get all public methods: setAge()

Get all public methods: getAge()

Get all public methods:wait(a)

Get all public methods:wait(a)

Get all public methods:wait(a)

Get all public methods: equals()

Get all public methods: toString()

Get all public methods: hashCode()

Get all public methods: getClass()

Get all public methods: notify()

Get all public methods: notifyAll()

= = = = = = = = = = = = = = = = = = = = = = = = = = =

Get all methods: getName()

Get all methods: setName()

Get all methods: setAge()

Get all methods: privateMethod()

Get all methods: getAge()

= = = = = = = = = = = = = = = = = = = = = = = = = = =

Access to the specified methods: public void net. Lishaoy. Reflectdemo. Entity. Person. SetAge (int)

set age: 66

the private method!



BUILD SUCCESSFUL in 395ms

Copy the code

Get member variables

The methods for obtaining member variables are as follows:

  • Field getField(String name) : Gets the named public Field
  • Field[] getFields() : Gets all the public fields of the class
  • Field getDeclaredField(String name) : Gets the named Field declared by the class
  • Field[] getDeclaredFields() : Gets all fields declared by the class

Let’s create a new GetField class to show how to get a member variable, as follows:

class GetField {



    public static void main(String[] args) throws

            ClassNotFoundException,

            NoSuchFieldException,

            IllegalAccessException,

            InstantiationException 
{



Class<? > aClass = Class.forName("net.lishaoy.reflectdemo.entity.Person");



        // Get all fields (excluding superclass fields)

        Field[] fields = aClass.getDeclaredFields();

        for (Field field: fields) {

            System.out.println("Get all fields:" + field.getName());

        }



        System.out.println("= = = = = = = = = = = = = = = =");



        // Get the specified field

        Field name = aClass.getDeclaredField("name");

        System.out.println("Get specified field:" + name.getName());



        // Sets the value of the specified field

        Object instance = aClass.newInstance();

        name.set(instance, "per");



        // Gets the value of the specified field

        Object o = name.get(instance);

        System.out.println("Get the value of the specified field:" + o);



        // Set and get the values of private fields

        Field age = aClass.getDeclaredField("age");

        age.setAccessible(true); // This method needs to be called and set to true

        age.set(instance, 66);

        System.out.println("Get private field value:" + age.get(instance));



    }



}

Copy the code

The result is as follows:

Get all fields: name

Get all fields: age

= = = = = = = = = = = = = = = =

Gets the specified field: name

Gets the value of the specified field: per

Get the value of the private field: 66



BUILD SUCCESSFUL in 395ms

Copy the code

Automatic findViewById with annotations and reflection (Case)

Now that we have a better understanding of annotations and reflection, let’s reinforce our learning with a quick example: Using annotations and reflection to do something similar to Butterknife’s automatic findViewById.

Create an empty Android project, create the Inject directory under the project directory, and in this directory create an InjectView class and BindView custom annotations, such as:

Create InjectView

InjectView does findViewById by reflection:

public class InjectView {



    public static void init(Activity activity) {

        // Get the activity's class object

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

        // Get all the member variables of the activity

        Field[] declaredFields = aClass.getDeclaredFields();

        // Variables so member variables

        for (Field field: declaredFields) {

            // Check whether the attribute is annotated with @bindView

            if(field.isAnnotationPresent(BindView.class)){

                // Get the annotation BindView object

                BindView bindView = field.getAnnotation(BindView.class);

                // Get the id of the annotation type element

                int id = bindView.value();

                // Find the view based on the resource ID

                View view = activity.findViewById(id);

                // Set access to private fields

                field.setAccessible(true);

                try {

                    // Assign a value to the field

                    field.set(activity,view);

                } catch (IllegalAccessException e) {

                    e.printStackTrace();

                }

            }

        }

    }

}

Copy the code

Create the @bindView annotation

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.FIELD)

public @interface BindView {

    @IdRes int value(a)// @idres can only transmit id resources

}

Copy the code

Use the @bindView annotation

Use @bindView annotations in MainActivity, such as:

public class MainActivity extends AppCompatActivity {



    // Use annotations

    @BindView(R.id.text_view)

    private TextView textView;



    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);



        setContentView(R.layout.activity_main);



        // Initialize InjectView to complete automatic findViewById

        InjectView.init(this);

        // Test whether R.i.d.ext_view is automatically assigned to textView

        textView.setText("Autocomplete findViewById with @BindView annotation.");

    }

}

Copy the code

Running results, as shown in figure:


Isn’t it easy? A single class does the automatic findViewById.

A dynamic proxy

Before we look at dynamic proxies, let’s review static proxies.

Static agent

The proxy pattern provides a proxy object to an object and controls references to the original object, such as mediations, which are common in our daily life.

The proxy mode typically has three roles, as shown in the figure below:


  • Abstract role: Refers to the public methods provided by the proxy role and the real role, usually as an interface
  • Real roles: You need to implement an abstract role interface that defines the business logic that real roles implement for invocation by proxy roles
  • Proxy roles: Need to implement abstract role interfaces, are proxies for real roles, implement abstract methods through the business logic methods of real roles, and can attach their own operations

Why use proxy mode

  • Objects can be accessed indirectly, avoiding the unnecessary complexity of accessing them directly
  • Access is controlled through proxy objects

Static proxy case

The scenario is as follows:

Xiao Ming can buy domestic things on a website, but he can’t buy overseas things, so he finds an overseas agent to help him buy things.

How do you describe it in code? According to the three roles of the proxy pattern, we define 1 interface and 2 classes respectively, for example: OrderService interface (Abstract role), ImplJapanOrderService class (real role), ProxyJapanOrder class (proxy role)

OrderService interface (Abstract role), code as follows:

public interface OrderService {

    int saveOrder(a);

}

Copy the code

The ImplJapanOrderService class (real role) looks like this:

// Implement the abstract role interface

public class ImplJapanOrderService implements OrderService {

    @Override

    public int saveOrder(a) {

        System.out.println("Order successfully, order number: 888888");

        return 888888;

    }

}

Copy the code

ProxyJapanOrder class (proxy role), code as follows:

// Implement the abstract role interface

public class ProxyJapanOrder implements OrderService {



    private OrderService orderService; // Hold real characters



    public OrderService getOrderService(a) {

        return orderService;

    }



    public void setOrderService(OrderService orderService) {

        this.orderService = orderService;

    }



    @Override

    public int saveOrder(a) {

        System.out.print("Japan order,");

        return orderService.saveOrder(); // Call the behavior method of the real role

    }

}

Copy the code

Create a Client class to test our code as follows:

public class Client {



    public static void main(String[] args) {

        // Order in Japan

        OrderService orderJapan = new ImplJapanOrderService();

        ProxyJapanOrder proxyJapanOrder = new ProxyJapanOrder();

        proxyJapanOrder.setOrderService(orderJapan);

        proxyJapanOrder.saveOrder();

    }

}

Copy the code

The result is as follows:

The order from Japan has been placed successfully. The order number is 888888



BUILD SUCCESSFUL in 1s

Copy the code

If you need to buy something from Korea, you need to add a new class called ImplKoreaOrderService (Korean service provider) and a new class called ProxyKoreaOrder (Korean agent). If you need to buy something from other countries, you need to add different classes, then you may have a lot of static proxy objects and code. This leads to complex code and poor maintainability. If so, we need to use dynamic proxy.

A dynamic proxy

Dynamic proxy classes and instances are created at run time. Therefore, we can pass different real actors and implement a single proxy class to perform the behavior of multiple real actors. Of course, it is less efficient than static proxy. How do we implement dynamic proxies? The JDK already provides a Proxy class and an InvocationHandler interface to do this.

Let’s create a ProxyDynamicOrder class with the following code:

public class ProxyDynamicOrder implements InvocationHandler {



    private Object orderService; // Hold real characters



    public Object getOrderService(a) {

        return orderService;

    }



    public void setOrderService(Object orderService) {

        this.orderService = orderService;

    }

    // Use Proxy to dynamically create real roles

    public Object getProxyInstance(a){

        return Proxy.newProxyInstance(

                orderService.getClass().getClassLoader(),

                orderService.getClass().getInterfaces(),

                this

                );

    }



    @Override

    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

        return method.invoke(orderService, objects); // Perform real character behavior through reflection

    }

}

Copy the code

Let’s see how the Client class is called. The code is as follows:

public class Client {



    public static void main(String[] args) {



        Static proxy mode

        // Domestic order

        OrderService order = new ImplOrderService();

        order.saveOrder();

        // Order in Japan

        OrderService orderJapan = new ImplJapanOrderService();

        ProxyJapanOrder proxyJapanOrder = new ProxyJapanOrder();

        proxyJapanOrder.setOrderService(orderJapan);

        proxyJapanOrder.saveOrder();

        // Order from Korea

        OrderService orderKorea = new ImplKoreaOrderService();

        ProxyKoreaOrder proxyKoreaOrder = new ProxyKoreaOrder();

        proxyKoreaOrder.setOrderService(orderKorea);

        proxyKoreaOrder.saveOrder();



        // Dynamic proxy mode

        // Domestic order

        ProxyDynamicOrder proxyDynamicOrder = new ProxyDynamicOrder();

        OrderService orderService = new ImplOrderService();

        proxyDynamicOrder.setOrderService(orderService);

        OrderService orderService1 = (OrderService) proxyDynamicOrder.getProxyInstance();

        orderService1.saveOrder();



        // Order in Japan

        OrderService japanOrderService = new ImplJapanOrderService();

        proxyDynamicOrder.setOrderService(japanOrderService);

        OrderService japanOrderService1 = (OrderService) proxyDynamicOrder.getProxyInstance();

        japanOrderService1.saveOrder();



        // Order from Korea

        OrderService koreaOrderService = new ImplKoreaOrderService();

        proxyDynamicOrder.setOrderService(koreaOrderService);

        OrderService koreaOrderService1 = (OrderService) proxyDynamicOrder.getProxyInstance();

        koreaOrderService1.saveOrder();



        // Generate the class file generated by the dynamic proxy

        //ProxyUtil.generateClassFile(koreaOrderService.getClass(), koreaOrderService1.getClass().getSimpleName());



    }

}

Copy the code

The result is as follows:

The order number is 666666

The order from Japan has been placed successfully. The order number is 888888

Korea agent order, place the order successfully, the order number is 666888

The order number is 666666

The order number is 888888

The order number is 666888



BUILD SUCCESSFUL in 1s

Copy the code

Only a ProxyDynamicOrder proxy class is required to complete the services provided by the ImplOrderService, ImplJapanOrderService, and ImplKoreaOrderService real roles.

Principle of dynamic proxy

We’re proxyDynamicOrder. GetProxyInstance hit a breakpoint () code, found in a debug mode, as shown in figure:


The name of the proxy class is $Proxy0@507. We can not find the $Proxy0@507 class file in the compiled directory, as shown in the following figure:


Proxy. NewProxyInstance ();

@CallerSensitive

public static Object newProxyInstance(ClassLoader var0, Class [] var1, InvocationHandler var2) throws IllegalArgumentException {

    Objects.requireNonNull(var2);

    Class[] var3 = (Class[])var1.clone();

    SecurityManager var4 = System.getSecurityManager();

    if(var4 ! =null) {

        checkProxyAccess(Reflection.getCallerClass(), var0, var3);

    }

    // Get the class object of the proxy class

    Class var5 = getProxyClass0(var0, var3);



    try {

        if(var4 ! =null) {

            checkNewProxyPermission(Reflection.getCallerClass(), var5);

        }

        // Get the constructor for the proxy class

        final Constructor var6 = var5.getConstructor(constructorParams);

        if(! Modifier.isPublic(var5.getModifiers())) {

            AccessController.doPrivileged(new PrivilegedAction<Void>() {

                public Void run(a) {

                    var6.setAccessible(true);

                    return null;

                }

            });

        }

        // An example of creating a proxy class

        return var6.newInstance(var2);

    } catch (InstantiationException | IllegalAccessException var8) {

        throw new InternalError(var8.toString(), var8);

    } catch (InvocationTargetException var9) {

        Throwable var7 = var9.getCause();

        if (var7 instanceof RuntimeException) {

            throw (RuntimeException)var7;

        } else {

            throw new InternalError(var7.toString(), var7);

        }

    } catch (NoSuchMethodException var10) {

        throw new InternalError(var10.toString(), var10);

    }

}

Copy the code

Then, go to getProxyClass0(var0, var3) to see how to get the class object of the proxy class.

private staticClass<? > getProxyClass0(ClassLoader var0, Class<? >... var1) {

    if (var1.length > 65535) {

        throw new IllegalArgumentException("interface limit exceeded");

    } else {

        // The proxy class object is cached

        return (Class)proxyClassCache.get(var0, var1);

    }

}

Copy the code

Then, let’s see what var1 is. We looked up and found, sure enough, the following:

// Var1 is the InvocationHandler interface we implemented

protected Proxy(InvocationHandler var1) {

    Objects.requireNonNull(var1);

    this.h = var1;

}

Copy the code

Then, we click proxyClassCache. Get (var0, var1), as shown:


Apply (var1, var2) to get the class object of our proxy class, we enter the Apply implementation class ProxyClassFactory, as follows:

publicClass<? > apply(ClassLoader var1, Class<? >[] var2) {

    IdentityHashMap var3 = new IdentityHashMap(var2.length);

    Class[] var4 = var2;

    int var5 = var2.length;



.



    if (var16 == null) {

        var16 = "com.sun.proxy.";

    }



    long var19 = nextUniqueNumber.getAndIncrement();

    // Generate the class name of the proxy class

    String var23 = var16 + "$Proxy" + var19;

    // Generate the bytecode of the proxy class

    byte[] var22 = ProxyGenerator.generateProxyClass(var23, var2, var17);



    try {

        // Generate a class object for the proxy class

        return Proxy.defineClass0(var1, var23, var22, 0, var22.length);

    } catch (ClassFormatError var14) {

        throw new IllegalArgumentException(var14.toString());

    }

}

Copy the code

Then, we click on the proxy.defineclass0 method as follows:

private static nativeClass<? > defineClass0(ClassLoader var0, String var1,byte[] var2, int var3, int var4);

Copy the code

Is a native method, so we won’t follow it further when it comes to C or C++.

So, where does the proxy Class file actually exist, given the life cycle of a Class, as shown in figure:


The Class file of the agent is reflected in memory, so we can write to the file by byte[]. We create a utility Class to write the Class bytecode in memory to the file, such as:

public class ProxyUtil {



    public static void generateClassFile(Class aClass, String proxyName) {



        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(

                proxyName,

                new Class[]{aClass}

        );

        String path = aClass.getResource(".").getPath();

        System.out.println(path);

        FileOutputStream outputStream = null;



        try {

            outputStream = new FileOutputStream(path + proxyName + ".class");

            outputStream.write(proxyClassFile);

            outputStream.flush();

        } catch (IOException e) {

            e.printStackTrace();

        } finally {

            try {

                outputStream.close();

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }

}

Copy the code

Find the file using the output path, for example:

/Users/lishaoying/Documents/APP/Android/practice/annotation_reflect/anRePrDemo/proxyDemo/build/classes/java/main/net/lis haoy/proxydemo/service/impl/

Copy the code

The file code is as follows:

// Implements the ImplKoreaOrderService interface

public final class $Proxy0 extends Proxy implements ImplKoreaOrderService {



    // generate various methods

    private static Method m1;

    private static Method m8;

    private static Method m3;

    private static Method m2;

    private static Method m5;

    private static Method m4;

    private static Method m7;

    private static Method m9;

    private static Method m0;

    private static Method m6;



    public $Proxy0(InvocationHandler var1) throws  {

        super(var1);

    }



.



    // Generates the saveOrder method for the real character

    public final int saveOrder(a) throws  {

        try {

            // what is h? And click on it to find the InvocationHandler interface that we passed in

            // what is m3? The following static code block is our saveOrder method

            return (Integer)super.h.invoke(this, m3, (Object[])null);

        } catch (RuntimeException | Error var2) {

            throw var2;

        } catch (Throwable var3) {

            throw new UndeclaredThrowableException(var3);

        }

    }



.



    public final Class getClass(a) throws  {

        try {

            return (Class)super.h.invoke(this, m7, (Object[])null);

        } catch (RuntimeException | Error var2) {

            throw var2;

        } catch (Throwable var3) {

            throw new UndeclaredThrowableException(var3);

        }

    }



.



    static {

        try {

            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));

            m8 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("notify");

            m3 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("saveOrder");

            m2 = Class.forName("java.lang.Object").getMethod("toString");

            m5 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("wait", Long.TYPE);

            m4 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("wait", Long.TYPE, Integer.TYPE);

            m7 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("getClass");

            m9 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("notifyAll");

            m0 = Class.forName("java.lang.Object").getMethod("hashCode");

            m6 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("wait");

        } catch (NoSuchMethodException var2) {

            throw new NoSuchMethodError(var2.getMessage());

        } catch (ClassNotFoundException var3) {

            throw new NoClassDefFoundError(var3.getMessage());

        }

    }

}

Copy the code

Complete a simple Retrofit using annotations, reflection, and dynamic proxies

Due to the length of the article, and the amount of code used to complete a simple Retrofit using annotations, reflection, and dynamic proxies, I will not show it here. If you are interested, you can check the source code on GitHub.

Finally, the blog and GitHub address are attached as follows:

Blog address: h.lishaoy.net GitHub address: github.com/persilee