On the underlying principle and implementation of dynamic agent and static agent

What is reflection and what is the function of reflection

Java's reflection mechanism refers to that in the running state of the program, you can construct the object of any class, understand the class to which any object belongs, understand the member variables and methods of any class, and call the properties and methods of any object. This function of dynamically obtaining program information and dynamically calling objects is called the reflection mechanism of Java language. Reflection is regarded as the key to dynamic language

java class loading mechanism

To understand the reflection principle of java, we first need to know the class loading mechanism of java

  • *The. java file is compiled into * class file
  • *The. Class file is loaded into the memory of the jvm virtual machine through the class loader
  • At this time, the binary data in the class file is stored in the method area, and the corresponding class object is generated in the heap memory at the same time. The class object can be said to be the access entry of class data in the method area
  • Class object can obtain the constructor, member variables, methods and other information of the class, and the process of obtaining all attributes and methods of any object of the class is reflection

Class class

There are three ways to obtain Class

// Get class name directly
Class class1 = Student.class;

// The of the object through new getClass() method
Class class2 = new Student().getClass();

// Pass Get the full path of java file
Class class3 = Class.forName("path");

We can use an example to see what the Class object can do

When we get the Class object of the Student Class, we can obtain the construction Method of the Student Class through this object, obtain the attribute with the specified name, modify the access permission of the attribute, and obtain the Method object Method with the specified name

And call the invoke() Method of the Method object to execute the previously obtained Method.

class Student{
    public String name;
    public String age;

    public void setName(String name) {
        this.name = name;
    }
    public Student(String name, String age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}
public class ReflectionDemo {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        
        Class class1 = Class.forName("com.javase.Student");
        //Gets the specified constructor
        Constructor constructor = class1.getConstructor(String.class, String.class);
        //Get an instance of the object
        Student student = (Student) constructor.newInstance("ZhangSan","12");
        System.out.println(student);
        //Get the specified attribute
        Field name = class1.getField("name");
        //Gets the specified method
        Method setName = class1.getDeclaredMethod("setName", String.class);
        //Execution method
        setName.invoke(student,"LiSi");
        System.out.println(student);
    }
}
#Console output results
Student{name='ZhangSan', age='12'}
Student{name='LiSi', age='12'}

Reflection class

So how does the Class get this information? Let's take getDeclaredMethod as an example

The Reflection Class is the core of the Reflection mechanism. This Class obtains the Class object of the caller through a native method getCallerClass(). This method also has an overloaded method, and the input parameter is the level of the caller

0 and less than 0 - return Reflection class

1 - return your own class

2 - return the caller's class

3. 4. Pass and return the caller's class layer by layer

However, this method is outdated. The official explanation is to prevent malicious calls to obtain classes without permission

This annotation is used to plug the loophole. Once, hackers used to construct double reflection to improve permissions. The principle is that reflection only checks the class of the caller with a fixed depth to see if it has privileges, such as the caller with two layers (getCallerClass(2)). If my class doesn't have enough permissions for the group to access some information, I can achieve my goal through double reflection: the classes related to reflection have high permissions, while on the call chain of I - > reflection 1 - > reflection 2, reflection 2 sees the class of reflection 1 when checking permissions, which is deceived and leads to security vulnerabilities. After using CallerSensitive, getCallerClass no longer uses a fixed depth to find the actual caller ("I"), but marks all interface methods related to reflection with CallerSensitive. When searching, anyone who sees the annotation will skip it directly, which effectively solves the problem of malicious calling

@CallerSensitive
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        //Check whether the current class has access to member variables and methods
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        //Query the specified method through the searchMethods method
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }

@CallerSensitive
public static native Class<?> getCallerClass();

//getCallerClass overloaded method
@Deprecated
public static native Class<?> getCallerClass(int var0);

Now let's continue to look at the getDeclaredMethod() method. Now that we have obtained the Class class of this Class, the next step is to judge whether there are enough permissions to access the member variables in the Class. This step is implemented in the checkMemberAccess() method

// getDeclaredMethod() method
private void checkMemberAccess(int which, Class<?> caller, boolean checkProxyInterfaces) {
        final SecurityManager s = System.getSecurityManager();
        if (s != null) {
            /* Default policy allows access to all {@link Member#PUBLIC} members,
             * as well as access to classes that have the same class loader as the caller.
             * In all other cases, it requires RuntimePermission("accessDeclaredMembers")
             * permission.
             */
            final ClassLoader ccl = ClassLoader.getClassLoader(caller);
            final ClassLoader cl = getClassLoader0();
            if (which != Member.PUBLIC) {
                if (ccl != cl) {
                    s.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
                }
            }
            this.checkPackageAccess(ccl, checkProxyInterfaces);
        }
    }

// searchMethods method
private static Method searchMethods(Method[] methods,String name,Class<?>[] parameterTypes) {
        Method res = null;
        String internedName = name.intern();
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName() == internedName
                && arrayContentsEq(parameterTypes, m.getParameterTypes())
                && (res == null
                    || res.getReturnType().isAssignableFrom(m.getReturnType())))
                res = m;
        }

        return (res == null ? res : getReflectionFactory().copyMethod(res));
    }

First, int which determines the access level. Here, the member is passed in Defined, that is, public. Here, the access rights of class members are checked. If the access rights are insufficient, a SecurityException will be thrown. When the access rights of class members pass the check, execute the checkPackageAccess() method to check whether the program can normally access the class member resources under the current access policy. If not, a SecurityException will still be thrown, Next, search the specified method through the searchMethods() method in getDeclaredMethod method. If the queried method is equal to null, NoSuchMethodException will be thrown. So far, the method has been successfully obtained.

What is the agent model

The agent can be regarded as a wrapper for the calling target, so that our call to the target code does not occur directly, but is completed through the agent. The bottom layer of dynamic proxy provided by jdk is realized through reflection

Static proxy

The so-called static proxy can be understood as: the relationship between the proxy object and the target object has been determined before the program runs, the bytecode file of the proxy object already exists, and the conditions that the static proxy needs to meet: the proxy object and the target object need to inherit the same parent class or implement the same interface, as shown in the following figure

[the external chain picture transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-lm9disilw-1653113138756) (images / reflection, dynamic and static agent-2.jpg)]

Static proxy example

//Target interface
public interface ProxyInterface {
    public void sendMsg();
}

//Target object
public class SourceObject implements ProxyInterface{
    @Override
    public void sendMsg(){
        System.out.println("send message!!");
    }
}
//Proxy object
public class ProxyDemo implements ProxyInterface{

    private SourceObject target;

    public ProxyDemo(ProxyInterface target){
        this.target = (SourceObject) target;
    }
    //The proxy object enhances the sendMsg method of the target object
    @Override
    public void sendMsg() {
        System.out.println("get phone number!");
        target.sendMsg();
        System.out.println("send the email!");
    }
}
  • This is the implementation of a simple static proxy pattern. All roles in the proxy mode (proxy object, target object, interface of target object) are determined at compile time.

  • The purpose of static proxy is to control the access rights of real objects, and the use rights of real objects are controlled through proxy objects.

  • Avoid creating large objects. By using a proxy small object to represent a real large object, we can reduce the consumption of system resources, optimize the system and improve the running speed.

  • Enhancing the function of real objects is relatively simple. Through the proxy, you can add additional functions before and after calling the methods of real objects.

Dynamic agent

Previously introduced Static proxy , although the static proxy mode is easy to use, the static proxy still has some limitations. For example, using the static proxy mode requires programmers to write a lot of code, which is a waste of time and energy. Once there are many methods in the class that need to be represented, or when multiple objects need to be represented at the same time, this will undoubtedly increase the complexity.

The agent class in dynamic agent is not required to be determined at compile time, but can be generated dynamically at run time, so as to realize the agent function of the target object. Reflection is an implementation of dynamic agent

JDK dynamic agent

java. The Proxy class and InvocationHandler interface in lang.reflect package provide the ability to generate dynamic Proxy classes

Steps of JDK dynamic agent

  • Define a delegate class and a public interface.

  • Define a class by yourself (calling the processor class, i.e. implementing the InvocationHandler interface). The purpose of this class is to specify the specific tasks to be completed by the agent class to be generated at runtime (including Preprocess and Postprocess), that is, any method called by the agent class will pass through the calling processor class

  • To generate a proxy object (of course, a proxy class will also be generated), you need to specify (1) a delegate object (2) a series of interfaces implemented by it (3) an instance of the calling processor class. Therefore, it can be seen that a proxy object corresponds to a delegate object and a calling processor instance.

Classes involved in dynamic proxy

java.lang.reflect.Proxy: This is the main class for generating proxy classes. All proxy classes generated through proxy classes inherit the proxy class, that is, dynamicproxyclass extensions proxy

java.lang.reflect.InvocationHandler: it is called "call handler" here. It is an interface. The specific content of the dynamically generated proxy class needs to define a class, and this class must implement the InvocationHandler interface.

Dynamic proxy example

//Target interface
public interface ProxyInterface {
    public void sendMsg();
}

//Target object
public class SourceObject implements ProxyInterface{
    @Override
    public void sendMsg(){
        System.out.println("send message!!");
    }
}

//Dynamic proxy class
public class DynamicProxy implements InvocationHandler {
    private Object target;
    public DynamicProxy(Object target){
        super();
        this.target = target;
    }
    public DynamicProxy(){
        super();
    }
    /**
     * @param proxy Via proxy Proxy class object generated by newproxyinstance()
     * @param method Represents the function called by the proxy object
     * @param args Represents the parameters of the function called by the proxy class object
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("----------Start send Msg-----------------");
        Object invoke = method.invoke(target, args);
        System.out.println("----------End send Msg-------------------");
        return invoke;
    }
    //Create proxy object
    public Object getProxy(){
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
    }
}

//Calling sendMsg method using dynamic proxy
public class ValueRefer {
    public static void main(String[] args) {
        //Create target object
        ProxyInterface proxyInterface = new SourceObject();
        //Pass the target object into the dynamic proxy class
        DynamicProxy dynamicProxy = new DynamicProxy(proxyInterface);
        //Get proxy object
        ProxyInterface proxy = (ProxyInterface)dynamicProxy.getProxy();
        //Call the enhanced method through the proxy class
        proxy.sendMsg();
    }
}

/*console output 
----------Start send Msg-----------------
send message!!
----------End send Msg-------------------
*/

Cglib dynamic agent

Cglib (Code Generation Library) is a third-party code generation class library, which dynamically generates a subclass object in memory at runtime, so as to expand the function of the target object.

The biggest difference between cglib and dynamic proxy is that objects using dynamic proxy must implement one or more interfaces, while objects using cglib proxy do not need to implement interfaces to achieve no intrusion of proxy classes

There are many explanations about Cglib dynamic agent on the Internet, so I won't go into details here

Tags: Linux ssh server

Posted by mania on Sun, 22 May 2022 09:29:53 +0300