Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Redemption from parallel worlds

2025-03-26 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/03 Report--

Here comes the divine torture.

Why do you say redemption? Let's first discuss the "death problem" with you. If your girlfriend or your wife asks you, "I fell into the water with your mother, who will you save first?"

Haha, yes, it is the ancient voice from China, the difficult problem of the century that tortures your heart! Are you scared?

You can flip a coin or find a fishing net to pick it up at once, but wait, do you really have so much time in this emergency?

At this time, you must most want to become Superman, or learn the magic of "separation", so that you don't have to do this difficult multiple-choice question.

Parallel cosmology tells us that there are countless copy in this world, and you also have countless copy. As long as you borrow one from another world, your heart will be divine redemption.

How?

Well, assuming that there is really a parallel world, in order to ensure the feasibility of this method landing, we also need to ensure that

I'm really the one in the parallel universe.

I represent not only the name and appearance, but also the composition of my life is the complete me, what is the best result? The parallel world of me is separated from me from this moment, is my real copy, through a version of copy!

The other one I can come into this world to help me save a person.

This is the essence of our discussion of this problem. If we can't solve it, what can I do with another 100 parallel worlds? So he wants to be able to interfere in our world and act in this world! But since it is a parallel world, then it must be impossible to come over, ah, what should we do? As we all know, as gods at high latitudes can be projected into the world at low latitudes and act through "projection", maybe we can do so?

Sort out your ideas.

Two of the same I saved the two most important people in life at the same time, not stepping on the pit, not heart-breaking, and a few wives can save it, it's perfect!

Holy one (programmer) time

For those of us who solve philosophical problems and get the sublimation of our hearts, we return to our true self (reality) and use the only remaining saint model to think about the practical significance of this method.

In the course of our "high-end" career as an engineer, we will encounter holes left in the code intentionally or unintentionally by our predecessors, such as

Some singleton patterns that are unreasonably designed allow us to have only one instance in a JVM to store some data (such as status) in a static field, and if modification may lead to running errors or other pain regardless of the design of later generations.

But when we need to create a new instance of this kind of object to save the world, apart from being trampled by thousands of grass and mud horses, we can only feel the malice of the world.

No, definitely not!

We're in saint mode!

In the real world, we are just losers, but in the world of 0 and 1, we are gods!

The omnipotent God!

God loves the world, how can he let his lamb live in dire straits?

Just like saving your mother, your wife and your heart, we can create a parallel universe. Isn't it our instinct to create things from nothingness?

Design

Earlier, we have discussed the solution to the difficult problem of the century, and given the design drawings, it seems that all we have to do is to transform this thinking into another world composed of 0 and 1.

We have to manipulate a bunch of "objects to be saved" through the savior object, well, that's what the savior should do.

However, there is a disaster on the other side, and there are a lot of "objects to be saved" sitting in rows, waiting for the savior to save.

The savior said, shit, I am not good at TM. God did not give me this superpower, and I am also very helpless.

All right, this is the chance for the hero to make his debut.

Your parents do not give you the art of separation, we are not separated, let's just open a new world, pull one over, don't ask why, it is so capricious.

Well, the specific operation is like how to put an elephant in the refrigerator in three steps.

1. Open up a new world

2. Copy a savior's past

3. Project the Savior.

Now that there are steps, analyze how to implement them.

Open up a new world

We are pragmatic engineers, so we should not be called opening up a new world, but should be called creating a relatively isolated environment. What about the requirements?. This environment should be

The objects that work on the inside should be exactly the same as those on the outside, and the objects inside and outside the environment should be completely different.

Let's name this environment "Sandbox" for the time being.

Take the singleton design as a reference, the singleton design generally depends on the existence of the Class, in order to copy this object, what we need to do is to make a copy of the entire Class.

We know that Class in Java is loaded into memory by ClassLoader, while ClassLoader uses a parent delegation mechanism, and a unique business object in ClassLoader does not exist for other ClassLoader, which perfectly satisfies the three points mentioned above. Good, that's it!

Solution: using ClassLoader as sandbox environment isolation

Copy a savior past

After we have decided on the ClassLoader plan, our thinking is naturally clear, and now it is very easy to consider copying Class into the ClassLoader!

We know that when ClassLoader loads Class, it actually reads the .class file, and then defines a class through ClassLoader's defineClass. Well, we can also copy the class definition outside the sandbox, two steps.

Read the .class content first. Where is this file? When the jar package is loaded into memory by ClassLoader, the file data can be read through getResource, perfect!

Define classes in a sandbox. Simple, just one defineClass, finish work ~

Hey, don't worry, be careful of class redefinition, remember to record which classes have been defined.

Project the savior.

Yeah, that's a problem, too.

As we just said, the unique business objects of different ClassLoader do not exist for other ClassLoader! This causes a problem, and the object instances created inside cannot be used outside!

For instance

BizObject biz = new BizObject (); / / OKBizObject biz2 = Sandbox.createObject (BizObject.class); / / error

Why did it go wrong? Because the BizObject inside and outside the sandbox is different, positive and negative particles will annihilate together.

So we need projection.

Well, instead of projection, we need an agent to train a puppet outside the sandbox. Oh no, it's an agent. All operations on this agent can be fed back into the sandbox.

Well, so far, we have basically combed through the problem, so the next step.

And God said, Let there be light

Through the above analysis and carding, we have basically determined the direction and logic, now, everything is ready, without a magical east wind, we can enter the new world, then we begin to play the code!

Wait a minute, classmate, are we missing something?

We have to design before we code!

All right, let's discuss this demand.

First of all, we assume that a magic "sandbox" has been set up, and the inside and outside of the sandbox are isolated, so the communication between the inside and outside can only be carried out through a bridge that is also very magical, which is called "agent".

When an external student needs to create an object but is subject to various restrictions, he can create a split of the object in the sandbox, and then manipulate it through the separate agent, so as to achieve the goal.

Well, there are only so many requirements, so let's talk about design.

In the above discussion, we decided to use ClassLoader to isolate the inside and outside of the sandbox, but didn't we directly expose the ClassLoader interface to external use?

ClassLoader can operate on the underlying class, although the function is powerful, but the operation complexity is high, if not careful, it is prone to problems, so we should encapsulate it and provide only the interface we expect users to use, and we think it should have these characteristics

Functions that have nothing to do with the sandbox should not be exposed and can be used directly after creating an object.

This is a bit difficult for ClassLoader, so we need to hide it, create a sandbox to provide services, and hide ClassLoader inside the sandbox, assuming it is called "SandboxClassLoader".

So we have it.

Caller sandbox SandboxClassLoader external ClassLoader

Four dates.

One more thing, as mentioned above, our caller manipulates objects in the sandbox through proxies. Do you remember why you use proxies? The essential reason for using proxies is that the classes inside and outside the sandbox belong to different ClassLoader, even if the classes with the same name are different!

By the same token, when we call through a proxy object, the parameters are passed using objects outside the sandbox, and entering the sandbox cannot be used directly, so we also need to convert such objects.

Here we only consider the value object parameters. If you are concerned about passing parameters from other objects, you need to carry out similar proxy conversion, but for value objects, all we have to do is to copy the values, and there is no need for too complicated processing.

Let's use a picture to illustrate this relationship.

The picture is very intuitive, so I won't repeat the explanation.

Well, the basic carding should be very clear. Only the blue "object in the sandbox" in the picture belongs to working in the sandbox and dynamically created, and the rest is outside the sandbox.

The box draws the boundary of the sandbox component, and both the caller and the APPClassLoader belong to the existing instance, so the interior of the component belongs to the part that needs to be implemented.

List the key classes

As you can see, Sandbox's API has become very simple and simple.

In order to simplify the design, it is stipulated that the object to be created must have a parameter constructor. If students need to construct an object with a parameter constructor, they can expand it. Welcome to do a good job in this sandbox tool.

Why separate enumerated and non-enumerated objects here? Is any of you clear?

The concept of enumeration refers to things that can be enumerated limited. In java, enumeration objects are inherited from Enum and cannot be constructed by the new method, but can only be selected from the values of enumerations.

The object is inherited from Object, and everyone is very familiar with it.

Genesis

Finally, we have entered the most important part of the codes.

Pick out the key code, and let's do it.

Public class Sandbox {private SandboxClassLoader classLoader; private SandboxUtil util = new SandboxUtil (); private List redefinedPackages; public Sandbox (List packages) {redefinedPackages = packages; classLoader = new SandboxClassLoader (getContextClassLoader ()) } / * sandbox object construction method * @ param redefinedPackages needs to work in the sandbox package * all the classes under this package work in the sandbox * / public Sandbox (String... RedefinedPackages) {this (Lists.newArrayList (redefinedPackages));} /.}

Let's talk about the construction method first.

Since it is a sandbox object, why design a parametric construction method?

In practice, we will consider the cohesion between certain classes. When one class runs in a sandbox, others are also recommended to run in a sandbox. We have learned the "singularity principle" and know that a packet is generally cohesive, so the design here is to specify certain package paths, and the sandbox will take over the objects in these packets.

For classes that are not in these packages, what if we call the sandbox to construct it? The so-called "Talk is cheap, show me the code" ~

Please wait a moment, let's continue with the constructor, ~ this problem we marked as problem 1 will be discussed later.

Here comes SandboxClassLoader, using getContextClassLoader () as a parameter, so what do you do here? Let's first take a look at the construction of SandboxClassLoader

/ * * Sandboxed isolation Core * * Class-level runtime isolation is performed through ClassLoader * * this class essentially proxies currentContextClassLoader objects and adds ClassLoader for some classes that need to be run in the sandbox * / class SandboxClassLoader extends ClassLoader {/ / current context to find class instances and clone them into sandboxed private final ClassLoader contextClassLoader / / cache Class instances that have been created to avoid duplicate definitions of private final Map cache = Maps.newHashMap (); SandboxClassLoader (ClassLoader contextClassLoader) {this.contextClassLoader = contextClassLoader;} / /. }

The construction method of SandboxClassLoader is only to temporarily store the incoming contextClassLoader, so let's take a look at the getContextClassLoader method

/ * * the class loader that gets the current context * * this class loader needs to contain MQClient-related class definitions * PS: defined separately as a method, it is worried that when the context class loader does not meet the requirements, it can be quickly replaced * @ return current class loader * / private ClassLoader getContextClassLoader () {/ / from the point of view of the class loader mechanism The thread context class loader is the most suitable return Thread.currentThread (). GetContextClassLoader () }

It's so easy!

In fact, there is some design basis: we are going to create an object, then the class definition of the object must be accessible in the current code.

Based on this consideration, we can determine that when a user uses something like An a = Sandbox.createObject (A.class) to create objects in the sandbox, class A must be accessible in the context of this code execution. At this time, we can get the .class resource file corresponding to class A through the ClassLoader of this context, and then redefine the class.

Continue to look at the relevant code, for ease of reading, I reorganized the code structure

Public class Sandbox {private SandboxClassLoader classLoader; / /. / * create a class instance with the specified name in the sandbox * * if the named class does not belong to the package specified by redefinedPackages, directly return the external class instance * @ param clzName the class name of the instance to be created * @ return the specified class name instance object * / public T createObject (String clzName) throws ClassNotFoundException, SandboxCannotCreateObjectException {Class clz = Class.forName (clzName) Return (T) createObject (clz);} / * create an instance of the specified Class in the sandbox * @ param clz the Class * @ return of the instance to be created has the same function as clz and works in the sandbox * / public synchronized T createObject (Class clz) throws SandboxCannotCreateObjectException {try {final Class clzInSandbox = classLoader.loadClass (clz.getName ()) Final Object objectInSandbox = clzInSandbox.newInstance (); / / if the classloader of the object is consistent with the classloader of clz, it means that it is not an object that needs to work in the sandbox, it can be returned directly without the agent if (objectInSandbox.getClass (). GetClassLoader () = = clz.getClassLoader ()) {return (T) objectInSandbox. } / * create an agent for the producer: since the objects inside and outside the sandbox essentially belong to different classes, the capabilities of the two need to be bridged. Here, the proxy mode is adopted, by creating an object instance outside the sandbox. And forward all its method calls to the sandbox through the proxy to execute in addition, because all instances inside and outside the sandbox belong to different classes, object conversion is also required for parameters and return values. Peer-to-peer cloning of objects inside and outside the sandbox * / / create a subclass agent of the object through cglib Enhancer enhancer = new Enhancer () Enhancer.setSuperclass (clz); enhancer.setCallback ((MethodInterceptor) (o, method, args, methodProxy)-> {Method targetMethod = clzInSandbox.getMethod (method.getName (), method.getParameterTypes ()); / / Clone the parameter before calling and convert it to Object in the sandbox [] targetArgs = args = = null? Null: util.cloneTo (args, classLoader); Object result = targetMethod.invoke (objectInSandbox, targetArgs); / / call to clone the result and convert it to return util.cloneTo (result, getContextClassLoader ());}); return (T) enhancer.create () } catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {throw new SandboxCannotCreateObjectException ("cannot create an object in a sandbox", e);}} / /.}

The main way to create objects in Sandbox has emerged! To make it easier to read, I'll strip out the irrelevant code and leave only the createObject method.

The T createObject (String clzName) method has no concrete implementation, only the parameter clzName is checked, and then it is transferred to T createObject (Class clz), so this method is mainly analyzed.

In fact, there is not much code (only 19 lines and all kinds of curly braces), mainly comments, the context is as follows

First get the parameter clz in the sandbox to define the clzInSandbox for the class, and create a concrete instance of the class objectInSandbox; through the newInstance of clzInSandbox, so the clz is required to have a parameter constructor here.

Determine whether clzInSandbox is running in a sandbox, and if not, there is no need to create an agent to return the object objectInSandbox directly

Why make that judgment, huh? Here you can answer the previous question 1 by the way, from the code

/ / if the object's classloader is the same as clz's classloader, you can return the object directly if it is not an object that needs to work in the sandbox. There is no need to agent if (objectInSandbox.getClass (). GetClassLoader () = > clz.getClassLoader ()) {return (T) objectInSandbox;}

We can see that when the created objectInSandbox is also running on an external ClassLoader, we do not actually create a proxy, because it is an object outside the sandbox, so why bother to create a proxy?

But when we call classLoader.loadClass (clz.getName ()) to get the class definition inside the sandbox, why do we get the class definition outside the sandbox? Does this contradict our design of the class SandboxClassLoader?

OK, take a look at the corresponding code, show me the code

Class SandboxClassLoader extends ClassLoader {/ / ClassLoader of the current context, which is used to find class instances and clone them into sandbox private final ClassLoader contextClassLoader; / /. / * override the parent class's reprint class into memory method * @ param name specify the class name * @ return the Class instance that has been reprinted into memory * @ throws ClassNotFoundException * / @ Override public Class loadClass (String name) throws ClassNotFoundException {return findClass (name) } / * redefine class reprint logic * * 1. For classes that need to run in the sandbox (declared in redefinedPackages), run directly under this ClassLoader by copying the contextClassLoader class definition * 2. For classes that do not need to run in the sandbox, return to the context class definition directly To reduce resource consumption * @ param name class name (full path) * @ return class definition * / @ Override protected Class findClass (String name) throws ClassNotFoundException {if (isRedefinedClass (name)) {return getSandboxClass (name) } else {return contextClassLoader.loadClass (name);}} /.}

We can see that the code that actually implements the logic is the findClass method, just a few sentences, which translates as "We get the classes that need to be redefined from the sandbox, and the classes that we don't need to take directly from the outside." so there will be objects whose ClassLoader is external.

So what is a "class that needs to be redefined"?

/ * whether the class running in the sandbox * @ param name class name * / boolean isRedefinedClass (String name) {/ / verify whether the sandboxed for (String redefinedPackage: redefinedPackages) {if (name.startsWith (redefinedPackage)) {return true;}} return false;} is required.

As long as the classes under the package specified when the Sandbox class is constructed, they all belong to the classes that need to be redefined in SandboxClassLoader.

Create an objectInSandbox proxy object using the cglib library, intercept all method execution of the proxy object, and transfer it to the actual object objectInSandbox for execution.

The code for cglib to create an object is not analyzed, but it is essentially the process of intercepting a method by creating a subclass of a specified class.

What we should care about is what did the interceptor do?

Enhancer.setCallback ((MethodInterceptor) (o, method, args, methodProxy)-> {Method targetMethod = clzInSandbox.getMethod (method.getName (), method.getParameterTypes ()); / / Clone the parameter before calling and convert it to an object in the sandbox Object [] targetArgs = args = null? Null: util.cloneTo (args, classLoader); Object result = targetMethod.invoke (objectInSandbox, targetArgs); / / Clone the result later and convert it to return util.cloneTo (result, getContextClassLoader ()) outside the sandbox;})

We will get the method with the same name and parameter from the object in the sandbox, then convert the parameter to the sandbox, execute the object method in the sandbox and get the result, and finally convert the result to the object outside the sandbox before returning

The logic is very clear, but how to convert objects inside and outside the sandbox?

If the code here is long and boring, it will not be posted separately. Students who are interested can download it on github. The general logic is as follows.

Determine whether the object needs to be converted to inside / out of the sandbox, return the object if not, and turn to 2 if necessary; create the corresponding object instance inside / outside the sandbox; traverse each field of the object instance, perform step 1 on the field, and assign the copied value to the corresponding field in the new object

Yeah, that's it.

As we mentioned earlier, we assume that all parameter passing objects are value objects, so the design here is relatively simple. If any student needs to pass non-value objects, then he or she needs to act as a proxy for external objects.

Return the proxy object

Some students are concerned about how classes are redefined from outside the sandbox to inside the sandbox, right? This is the core part of SandboxClassLoader, showing the code logic.

Class SandboxClassLoader extends ClassLoader {/ /. / / cache Class instances that have been created to avoid duplicate definitions of private final Map cache = Maps.newHashMap () / * Internal method: get the Class instance * @ param name class name * @ return class instance * @ throws ClassNotFoundException * / private synchronized Class getSandboxClass (String name) class {/ / 1 in the sandbox to be run in the sandbox. Check the cache to see whether the class has been reloaded. Return if (cache.containsKey (name)) {return cache.get (name) directly if any } / / 2. If this class does not exist in the cache, copy a copy from currentContextClassLoader to the current cache Class clz = copyClass (name); cache.put (name, clz); return clz } / * copy a class from currentContextClassLoader to this ClassLoader * * this copy defines the bytecode copy to the current ClassLoader, so it is completely different from the Class outside sandbox. Cannot directly assign * @ param name the class name to be copied * @ return works in the current ClassLoader Class * @ throws ClassNotFoundException * / private synchronized Class copyClass (String name) throws ClassNotFoundException {/ / get the path where the .class file is located String path = class ('.','/') + ".class" / / get the resource handle try (InputStream stream = contextClassLoader.getResourceAsStream (path)) {if (stream = = null) throw new ClassNotFoundException (String.format ("class% s not found", name) through the context class loader; / / read all byte content byte [] content = readFromStream (stream); return defineClass (name, content, 0, content.length) } catch (IOException e) {throw new ClassNotFoundException ("specified class not found", e);}} / /.}

There are two main methods involved. The getSandboxClass method is mainly responsible for checking at the cache level when getting objects. The purpose of caching is to accelerate the performance of obtaining class definitions, and to avoid errors caused by repeated execution of the same class definition.

As the name implies, copyClass is the process of copying the class definition, copying the .class file corresponding to the class from contextClassLoader, and defineClass it in SandboxClassLoader. Please read the code for details.

We also have a getEnumValue method in Sandbox. If the process is similar, we will not repeat it. Please download the code and read it.

At this point, we have finished writing the code.

At this point, we have completed the construction of a new world!

So far, we have finished all the work!??

It's too early to be happy.

The coming redemption

Testing is the guarantee of code quality, design and operation. The guarantee, in short, is the guarantee.

So, we also have to pass tests to verify our "world" to see if it is in line with our expectations.

This can be done simply by using unit tests. Code

Public class SandboxTest {@ Test public void getEnumValue () throws SandboxCannotCreateObjectException {/ / set the redefined package Sandbox sandbox = new Sandbox ("com.google.common.collect"); / / get the objects in the sandbox, although they have the same name and value, but because they belong to inside and outside the sandbox, it should not be expected to Enum type = sandbox.getEnumValue (com.google.common.collect.BoundType.CLOSED). AssertNotEquals (type, com.google.common.collect.BoundType.CLOSED); / / get the objects in the package that are not set and need to be redefined through the sandbox. It is expected to be equivalent Enum property = sandbox.getEnumValue (com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH); assertEquals (property, com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH) } @ Test public void createObject () throws SandboxCannotCreateObjectException, ClassNotFoundException {/ / set the redefined package Sandbox sandbox = new Sandbox ("com.google.common.eventbus"); / / get the objects in the sandbox. The expected class definition should be different from the class definition outside the sandbox com.google.common.eventbus.EventBus bus = sandbox.createObject (com.google.common.eventbus.EventBus.class) AssertNotEquals (bus.getClass (), com.google.common.eventbus.EventBus.class); / / obtained by name, such as bus = sandbox.createObject ("com.google.common.eventbus.EventBus"); assertNotEquals (bus.getClass (), com.google.common.eventbus.EventBus.class) / / get classes that do not need to be redefined through the sandbox, which is expected to be equivalent to the sandbox exterior List list = sandbox.createObject (ArrayList.class); assertEquals (list.getClass (), ArrayList.class);}}

Running result

OK, the test passed ~ ~

Coordinates of the world-> github- > Code Cloud gitee

Landing case: how to connect multiple RocketMQ servers in the same Java process

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report