4 minute read

From Reflection to Proxy

I’ve been recapping the Spring Framework recently and find Proxy plays a big role in Spring framework. So want to make use of this article to re-learn the basic of reflection and proxy

What is Reflection?

It allows an executing Java program to examine or “introspect” upon itself, and manipulate internal properties of the program. For example, it’s possible for a Java class to obtain the names of all its members and display them.

yeah I know it’s hard to understand, let’s create an example.

First I have two classes - Person.class & Student.class

public class Person {
    public int age;
    private String name;
    
    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }
}

public class Student extends Person {
    public int studentNo;
    private String school;
    
    public Student(String name, int age, int studentNo, String school) {
        super(age, name);
        this.studentNo = studentNo;
        this.school = school;
    }
    
    private void setStudentNo(int stuNo) {
        studentNo = stuNo;
    }
}

There are three ways to get the “Class” object of Student class:

public static void main(String[] args) {
    // way 1 (recommmended)
    Class clazz1 = Class.forName("Student");
    
    // way 2 (recommended)
    Class clazz2 = Student.class;
     
    // way3 (not recommmended)
    Class clazz3 = (new Student()).getClass();
}

After we get the “Class” Object, we can “introspect” and “manipulate” on a created object, including its fields, methods and constructors.

  1. Introspect the Fields value of existing object, there are two ways to get the fields of class
    • getFields(): can only access to public field of the Class, not include parent fields.
    • getDeclaredFields(): can access to all field of the Class, not include parent fields
public void introFields(Student student) {
    Student student = new Student("tom", 18, 123, "school-1");
    
    Class clazz = Class.forName("Student");
    Field[] fields = clazz.getFields(); // can only access to public fields of Class object: studentNo
    Field[] fields = clazz.getDeclaredFields();// can access to all fields of Class object: StudentNo, school
    for(Field field:fields) {
        System.out.println(field.getName());
        System.out.println(field.getType());
        System.out.println(field.getModifiers());
    }
    
    // can also modify the field value, even though it's private
    Field schoolField = clazz.getDeclaredField("school");
    schoolField.setAccessible(true);
    schoolField.set(student, "school-2");
}
  1. intro and call the method of Class even though it’s private
    • getMethod(): get all public methods of Class file, including parent class
    • getDeclaredMethod(): get all methods of Class files, including private & protected methods, while it doesn’t include methods from parent class
public void introMethod(Student student) {
    Class clazz = Student.class;
    Method[] methods = clazz.getMethods(); // get all public methods, including the methods from parent classes
    Method[] methods = clazz.getDeclaredMethods(); // get all methods from that class, including private & protected methods
    
    for(Method method: methods) {
        System.out.println(method.getName());
        System.out.println(method.getReturnType());
        System.out.println(method.getModifiers());
    }
    
    Method setStudentNoMethod = clazz.getDeclaredMethod("setStudentNo", int.class);
        setStudentNoMethod.setAccessible(true);
        setStudentNoMethod.invoke(student, 456);
        System.out.println(student);
}
  1. introspect and invoke constructor of Class file.
public void introConstructor(Student student) {
    private static void introConstructor(Student student) throws Exception {
        Class clazz = student.getClass();
        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class, int.class, String.class);
        Object o = declaredConstructor.newInstance("new tom", 34, 456, "school-5");
        System.out.println(o);
    }
}

What is Proxy?

Proxy is a structural design pattern that provides an object that acts as substitute for a real service for a real service object used by a client.

A proxy receives client requests, does some work and then passes the request to service object.

In Java, there are two ways to implement Proxy, depends on whether the real service object have implemented any interfaces

  • when Real service object implement any interface, we can use proxy from JDK
  • when Real service object doesn’t implement any interface, we use proxy from CGLib

Let take a calculator example to illustrate how to use these two Proxy ways

assume the real service is CalculatorImpl

Proxy from JDK

we have an interface called ICalculator and real service object will implement it

public interface ICalculator {
    int add(int i, int j);
}

public class CalculatorImpl implements ICalculator{
    @Override
    public int add(int i, int j) {
        return i + j;
    }
}

public class CalculatorJDKProxy {
    public static Object getProxy(CalculatorImpl calculator) {
        ClassLoader classLoader = calculator.getClass().getClassLoader();

        Class<?>[] interfaces = calculator.getClass().getInterfaces();

        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("pre processor");
                Object result = method.invoke(calculator, args);
                System.out.println("post processor");
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

public class Main {
    public static void main(String[] args) {
        // test code
        ICalcualtor object = (ICalculator) CalculatorJDKProxy.getProxy(new MyCalculatorImpl());
        object.add(1,3);
    }
}

Proxy from CGLib

assume the real service object doesn’t implement any interfaces,

first need to add CGLib Maven POM

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
public class CalculatorImpl {
    public int add(int i, int j) {
        return i + j;
    }
}

public class CalculatorCGLibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("pre processor");
        Object calc = methodProxy.invokeSuper(o, objects);
        System.out.println("post processor");

        return calc;
    }
}

public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CalculatorImpl.class);
        enhancer.setCallback(new CalculatorCGLibProxy());

        CalculatorImpl o = (CalculatorImpl) enhancer.create();
        System.out.println(o.add(1, 3));
    }
}

next step: looks into the source code of two ways