Java Core Technology Interview Essentials (Lecture 6) | What is the principle of dynamic agent?

Programming languages ​​usually have different classification angles. Dynamic type and static type are one of the classification angles. The simple distinction is whether the language type information is checked at runtime or at compile time.

There is also a comparison similar to it, that is, the so-called strong type and weak type, which is whether it is necessary to explicitly (mandatory) type conversion when assigning values ​​to variables of different types.

So, how to classify the Java language? It is generally believed that Java is a statically strongly typed language, but because it provides mechanisms similar to reflection, it also has the capabilities of part of a dynamically typed language.

Closer to home, the question I want to ask you today is, talk about the Java reflection mechanism, what principle is dynamic proxy based on?


Typical answer

The reflection mechanism is a basic function provided by the Java language, which gives the program the ability to introspect (introspect, the official term) at runtime. Through reflection, we can directly manipulate classes or objects, such as obtaining the class definition of an object, obtaining the properties and methods of the class declaration, calling methods or constructing objects, and even modifying the class definition at runtime.

Dynamic proxy is a mechanism that facilitates dynamic construction of proxy at runtime and dynamic processing of proxy method calls. Many scenarios are implemented using similar mechanisms, such as packaging RPC calls and aspect-oriented programming (AOP). (Netizen Mengqi Junjie’s note: This explanation is so detour...Dynamic agency, in simple terms, is an existence in which requests are processed through an intermediary of the same nature, and at the same time, the proxy object is given operation authority.)

There are many ways to implement dynamic proxy. For example, the dynamic proxy provided by JDK itself mainly uses the reflection mechanism mentioned above. There are other implementations, such as using the legendary higher-performance bytecode operation mechanism, similar to ASM, cglib (based on ASM), Javassist, etc.

Test site analysis

My first impression of this topic is a little bit of suspicion. I may subconsciously think that the dynamic agent is realized by the reflection mechanism. This is not wrong but it is a little bit incomplete. Function is the goal, and there are many ways to achieve it. In general, this topic examines another basic mechanism of the Java language: reflection, which is like a kind of magic. The introduction of runtime introspection capabilities gives the Java language unexpected vitality through runtime operations. Metadata or objects, Java can flexibly manipulate information that can only be determined at runtime. The dynamic agent is an extended technology widely used in product development. Many tedious and repetitive programming can be elegantly solved by the dynamic agent mechanism.

From the perspective of investigating knowledge points, the knowledge points involved in this question are relatively complex, so the interviewer can expand or dig a lot of content, such as:

  • Check your understanding and mastery of the reflection mechanism.
  • What problem does the dynamic agent solve, and what is the application scenario in your business system?
  • What is the difference between the design and implementation of JDK dynamic proxy and cglib and other methods, and how to choose?

These test points do not seem to be covered in a short article. I will try my best to sort them out in the knowledge expansion part.

Knowledge expansion

1. Reflection mechanism and its evolution

For the reflection mechanism of the Java language itself, if you look at the relevant abstractions under the java.lang or java.lang.reflect packages, you will have a very intuitive impression. Class, Field, Method, Constructor, etc., these are the metadata correspondences we use to manipulate classes and objects. Reflect the programming of various typical use cases. I believe there are too many articles or books that have been introduced in detail. I will not repeat them. At least you need to master the basic scene programming. Here is the official reference document: https://docs. oracle.com/javase/tutorial/reflect/index.html.

Regarding reflection, one thing I need to mention specifically is AccessibleObject.setAccessible​(boolean flag) provided by reflection. Most of its subclasses also rewrite this method. The so-called accessible here can be understood as modifying the public, protected, and private of members, which means that we can modify member access restrictions at runtime!

The application scenarios of setAccessible are very common, all over our daily development, testing, dependency injection and other frameworks. For example, in the O/R Mapping framework, we automatically generate setter and getter logic for a Java entity object at runtime. This is necessary to load or persist data. The framework can usually use reflection to do this without the need Developers manually write similar repetitive code.

Another typical scenario is to bypass API access control. During our daily development, we may be forced to call internal APIs to do things. For example, a custom high-performance NIO framework needs to explicitly release DirectBuffer, and it is a common way to use reflection to circumvent restrictions.

However, after Java 9, the use of this method may be controversial, because the new modular system of the Jigsaw project restricts reflective access due to strong encapsulation considerations. Jigsaw introduces the concept of so-called Open. Only when the module being reflected and the specified package are open to the reflective caller module can setAccessible be used; otherwise, it is considered an illegal operation. If our entity class is defined in the module, we need to explicitly declare in the module descriptor:

module MyEntities {
    // Open for reflection
    opens com.mycorp to java.persistence;
}

Because the reflection mechanism is widely used, according to community discussions, at present, Java 9 still retains the behavior that is compatible with Java 8, but it is very likely that in future versions, the aforementioned setAccessible restriction will be fully enabled, that is, only when the module is operated by reflection SetAccessible can be used only when the specified package is Open to the reflection caller module. We can use the following parameters to set it explicitly.

--illegal-access={ permit | warn | deny }

2. Dynamic proxy

 The previous question asked about dynamic agents. Let's take a look at it. What problem does it solve?

First, it is a proxy mechanism. If you are familiar with the proxy mode in the design pattern, we will know that the proxy can be seen as a wrapper for the call target, so that our call to the target code does not happen directly, but is done through the proxy. In fact, many dynamic agent scenes can also be regarded as the application of the decorator pattern, which I will supplement in the topic of the design pattern of the column.

Decoupling between the caller and the implementer can be achieved through a proxy. For example, making RPC calls, addressing, serialization, and deserialization within the framework are often meaningless to the caller. Through the proxy, a more friendly interface can be provided.

The development of the agent has gone through a process from static to dynamic, which originated from the extra work introduced by static agents. Similar to the early antique technologies such as RMI, tools such as rmic are also needed to generate static stubs and other files, which adds a lot of tedious preparation work, which has nothing to do with our business logic. Using the dynamic proxy mechanism, the corresponding stub and other classes can be generated at runtime, and the corresponding call operation is also completed dynamically, which greatly improves our productivity. The improved RMI no longer needs to manually prepare these, although it is still a relatively old and backward technology, it may be gradually removed in the future.

This may not be intuitive enough to say, we can look at a simple example of JDK dynamic proxy. The following is just a sentence of print. In the production system, we can easily extend similar logic for diagnosis and current limiting.

public class MyDynamicProxy {
    public static  void main (String[] args) {
        HelloImpl hello = new HelloImpl();
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        // 构造代码实例
        Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
        // 调用代理方法
        proxyHello.sayHello();
    }
}
interface Hello {
    void sayHello();
}
class HelloImpl implements  Hello {
    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}
 class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("Invoking sayHello");
        Object result = method.invoke(target, args);
        return result;
    }
}

The above JDK Proxy example implements the dynamic proxy construction and proxy operation very simply. First, implement the corresponding InvocationHandler; then, use the interface Hello as a link to construct a proxy object for the called target, and then the application can use the proxy object to indirectly run the logic of the call target, and the proxy provides additional logic (here, println) for the application to provide A convenient entrance.

From the perspective of API design and implementation, this implementation still has limitations, because it is interface-centric, which is equivalent to adding a restriction that does not have much meaning for the callee. What we instantiate is the Proxy object, not the real called type, which may still bring various inconveniences and capability degradation in practice.

If the callee does not implement the interface, and we still want to use the dynamic proxy mechanism, then other methods can be considered. We know that Spring AOP supports two modes of dynamic proxy, JDK Proxy or cglib. If we choose the cglib method, you will find that the dependency on the interface is overcome.

The cglib dynamic proxy adopts the method of creating subclasses of the target class. Because it is subclassing, we can achieve the effect of using the callee itself. (Netizen Dazhi Ruoyu’s note: If the target class implements one or more interfaces, Spring will create a JDK dynamic proxy, which implements each interface. If the target class does not implement an interface, Spring will use CGLIB to quickly create a new class , The new class is a subclass ("extension") of the target class. This leads to an important difference: the JDK dynamic proxy cannot be converted to the original target class, because it is just a dynamic proxy, which happens to implement the same interface as the target. The interface used in your application model has the effect of "tempting" the interface you programmed, because the proxy is usually called through these interfaces. On the other hand, if there is no interface in the model at all, Spring will create a CGLIB proxy , CGLIB proxy can be treated like the target class itself.) In Spring programming, the framework usually handles this situation, of course we can also specify it explicitly. Regarding the implementation details of similar solutions, I will not discuss them in detail.

So how do we choose in development? Let me briefly compare the advantages of the two methods.

Advantages of JDK Proxy:

  • Minimizing dependencies and reducing dependencies means simplifying development and maintenance. The support of JDK itself may be more reliable than cglib.
  • Smoothly upgrade the JDK version, and the bytecode library usually needs to be updated to ensure that it can be used on the new version of Java.
  • The code is simple to implement.

Based on the advantages of a similar cglib framework:

  • Sometimes it may be inconvenient for the calling target to implement additional interfaces. From a certain perspective, restricting the caller to implement the interface is a somewhat intrusive practice. Similar to the cglib dynamic proxy, there is no such restriction.
  • Only operate the classes we care about, without having to increase the workload for other related classes.
  • high performance.

In addition, from a performance perspective, I would like to add a few words. I remember someone once concluded that JDK Proxy is dozens of times slower than cglib or Javassist. Frankly speaking, without arguing about specific benchmark details, in mainstream JDK versions, JDK Proxy can provide equivalent performance levels in typical scenarios, and the order of magnitude gap is basically not widespread. Moreover, the performance of the reflection mechanism has been greatly improved and optimized in the modern JDK. At the same time, many functions of the JDK are not completely reflection, and ASM is also used for bytecode operations.

In our selection, performance may not be the only consideration. Reliability, maintainability, programming workload, etc. are often the more important considerations. After all, the threshold for standard libraries and reflection programming is much lower, and the amount of code is more feasible. Controlled, if we compare the investment of different open source projects in dynamic agent development, we can also see this.

The application of dynamic proxy is very extensive. Although it was mostly due to the use of RPC and other applications that came into our sight at first, the usage scenarios of dynamic proxy are far more than that. It perfectly complies with Spring AOP and other aspect programming. I will further analyze the purpose and capabilities of AOP in a later column. Simply put, it can be seen as a supplement to OOP, because OOP is not expressive enough for the scattered and entangled logic across different objects or classes, such as doing things at specific stages of different modules, such as logs, user authentication, and global For sexual exception handling, performance monitoring, and even transaction processing, you can refer to the following picture.

AOP allows developers to escape from these tedious matters through a (dynamic) proxy mechanism, greatly improving the abstraction and reuse of the code. Logically speaking, our similar agents in software design and implementation, such as Facade, Observer, and many other design purposes, can be elegantly achieved through dynamic agents.

Today I briefly reviewed the reflection mechanism, talked about the changes that reflection is taking place in the evolution of the Java language, and further explored the dynamic proxy mechanism and related aspect programming, analyzed the problems it solves, and discussed the selection considerations in production practice. .

Practice one lesson

Do you know what we are discussing today? Leave a thought question for you. Which scenarios do you use dynamic agents in your work? What implementation technology was selected accordingly? What is the basis for selection?


Other classic answers

The following is the answer from the netizen's official account-Technology Sleeplessly:

Principles of reflection and dynamic proxy

1 About reflection
One of the biggest effects of reflection is that we can not know the type of an object at compile time, but at runtime by providing the complete "package name + class name.class". Note: Not at compile time, but at runtime.

Functions:
• It can judge the class to which any object belongs at runtime.
• Can construct an object of any class at runtime.
• Judge the member variables and methods of any class at runtime.
• Call any object's method at runtime.
In the vernacular, using the Java reflection mechanism, we can load a class whose name is known only at runtime, learn its construction method, and generate its object entity, which can set values ​​for its fields and evoke its methods.

Application scenario:
Reflection technology is often used in the development of various general frameworks. Because in order to ensure the versatility of the framework, it is necessary to load different objects or classes according to the configuration file and call different methods. At this time, reflection will be used-dynamically load the objects that need to be loaded at runtime.

Features:
Because reflection consumes a certain amount of system resources, if you don't need to dynamically create an object, then you don't need to use reflection. In addition, the permission check can be ignored when the method is called by reflection, which may destroy the encapsulation and cause security problems.

2 Dynamic proxy
Provides a proxy for other objects to control access to this object. In some cases, one object is not suitable or cannot directly refer to another object, and the proxy object can play an intermediary role between the two (similar to a house intermediary, the landlord entrusts an intermediary to sell houses, sign contracts, etc.).
The so-called dynamic proxy means that you don't need to care about who is the proxy in the implementation phase, but only specify which object to proxy (uncertainty) in the runtime phase. If you write the proxy class yourself, it is a static proxy (deterministic).

Components:
(Dynamic) Proxy mode mainly involves three elements:
First: Abstract class interface
Second: The proxy class (the class that implements the abstract interface)
Third: Dynamic proxy class: The class

implementation that actually calls the methods and attributes of the proxy class Way: There
are many ways to implement dynamic proxy. For example, the dynamic proxy provided by JDK itself mainly uses the reflection mechanism. There are other implementations, such as the use of bytecode manipulation mechanisms, similar to ASM, CGLIB (based on ASM), Javassist, etc.
For example, the dynamic proxy interface InvocationHandler provided by the JDK can often be used to implement dynamic proxy classes. The invoke method is mandatory for the interface definition, and it completes the call to the real method. Through the InvocationHandler interface, all methods are processed by the Handler, that is, all the proxied methods are taken over by the InvocationHandler for actual processing tasks. In addition, we can often add custom logic implementations in the invoke method implementation to achieve non-invasiveness to the business logic of the agent class.

The following is the answer from the netizen Joshua:

Answer the question first:
99% of Java programmers should use AOP indirectly. Directly written in my own project, such as call tracking, general log, automatic retry,
reflection and AOP are really double-edged technologies. Starting from MVC, the development concept of convention over configuration prevails. ORM automatic mapping, plugin mode, and now spring + boot + cloud declarative programming are largely based on this implementation. It can be said that without reflection and AOP, there would be no Java today. On the other hand, I want to customize the package and it is really hard to
ask another question:
1. I heard a saying that the reflection is slow because one is to get the Field, Method, etc. to search for metadata, here is a string matching operation . Second, when Invoke, some security checks must be carried out. Is this statement correct? Does the JVM not perform some checks on memory operations during interpretation and execution? If not, what is the reason? Is there anything else?
2. For those who wrote C# before, they can spell expression tree and generate a function at runtime (no object is needed). Theoretically, the performance is the same as handwritten code, but it can be cached. This solves the problem that handwriting intermediate code is too difficult. Does Java have this similar function?

Author's reply: 1. Basically; reflection is relatively guarantee of type safety, I think it is also compared with methodhandle and the like, which is closer to the black box inside jvm and has better performance
2. Do you mean lambda? It also requires jvm to generate a call site, and then invokedynamic and other calls, so the first call overhead is obvious, C# does not understand, but the feeling of dynamic generation is the same;
this thing currently does not have a cache, if you are talking about storing in the file system; in the future; , Hehe...

 

Guess you like

Origin blog.csdn.net/qq_39331713/article/details/114090116