4
头图

Hello, I am crooked.

While I was poking around on Spring's github this week, an issue piqued my interest.

This article, which I wrote down this issues, starts with it, but it doesn't stop there:

https://github.com/spring-projects/spring-framework/pull/27818

The title of this issues is translated, which means that I hope the @Async annotation can support placeholders or SpEL expressions.

And the reason why I paid attention to this issue is entirely because I have written @Async related articles before.

In this issue, an issue numbered 27775 is mentioned:

https://github.com/spring-projects/spring-framework/issues/27775

What is this about?

It is estimated that you can see that when you take a look at the place marked in my screenshot, he wants to put the name of the thread pool in the configuration file. I don't think this requirement is surprising. Based on the Spring framework, it is a very reasonable requirement.

Make a Demo

I'll give you a demo first, and check what it wants to do.

First, a thread pool named why is injected.

Then there is a method decorated with the @Async annotation, and this annotation specifies a value of why, indicating that this thread pool named why is to be used:

Then we need a Controller to trigger it:

Finally, add the @EnableAsync annotation to the startup class to start the project.

Call the link below to initiate the call:

http://127.0.0.1:8085/insertUser?age=18

The output is as follows:

Indicates that the configuration has taken effect.

Then, the buddy who raised issues, he wants such a feature:

That is, let the @Async annotation and the configuration file be linked.

The current version of Spring does not support this thing. For example, when I start the project, I trigger it once:

NoSuchBeanDefinitionException is thrown directly, indicating that the value annotation of @Async does not have the function of parsing expressions.

wave of support

Ok, now the requirement is clear: it is not currently supported, someone has put forward this requirement in the community and wants Spring to support this feature.

Then this dude called sbrannen came out:

He said two words:

  • 1. If the provided BeanFactory is ConfigurableBeanFactory, we seem to be able to support it by modifying the code of org.springframework.aop.interceptor.AsyncExecutionAspectSupport.findQualifiedExecutor(BeanFactory,String) and using EmbeddedValueResolver.
  • Take a look at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.setBeanFactory(BeanFactory) for a corresponding example.

In the first sentence, the findQualifiedExecutor method he mentioned, that is, the code that needs to be modified, is like this in my 5.3.16 version:

You just need to remember that there is a beanFactory in the parameter.

The setBeanFactory method mentioned in the second sentence is like this:

What he said "for an example" is what I framed.

There are two key points here:

  • ConfigurableBeanFactory
  • EmbeddedValueResolver

First of all, ConfigurableBeanFactory is a very important class in Spring, but it is not the focus of this article. It is mentioned in one sentence: you can understand it as a huge, fully functional factory interface.

The point is the EmbeddedValueResolver thing:

It can be known from the annotation that this class is used to parse placeholders and expressions. It is equivalent to a tool class that Spring encapsulates for you.

There is this method in EmbeddedValueResolver:

And this method calls a resolveEmbeddedValue method:

org.springframework.beans.factory.support.AbstractBeanFactory#resolveEmbeddedValue

The method is the core code for parsing expressions in Spring.

I'll show you.

First we add a little code:

This code does not need to be explained, it is already very clear.

I just need to put a breakpoint on the code we analyzed earlier and run the program:

Is it very clear.

The input parameter is the ${user.age} expression, and the output parameter is the corresponding 18 in the configuration file.

All the secrets about how to parse are hidden in this line of code:

Do you think I'm going to explain it to you in detail?

Impossible, just point the way, see for yourself.

Now I'm going to start turning, turning back to this old man's reply:

Now let me take you for a stroke.

First of all, an old iron said: Can your Spring's @Async annotation support expressions, such as @Async("${thread-pool.name}")

Then the official came out and replied: No problem, we can modify findQualifiedExecutor method and use the EmbeddedValueResolver tool class to support it. For example, it is like the setBeanFactory method in the following class:

Then I take you to see this method, and then know the usage of EmbeddedValueResolver.

Ok, so now the question comes: in the findQualifiedExecutor method, how do we use it?

Going around in circles, now back to the original issues:

This old man said that he was based on sbrannen, which is the tip of the official staff. Submitted this revision.

How to modify it?

See his Files changed:

Three files were modified, one of which was a test class.

There are two remaining, one is the @Async annotation:

Here, only the Javadoc is modified, indicating that this annotation supports the configuration of expressions.

The other is the AsyncExecutionAspectSupport class:

Add five lines of code to the findQualifiedExecutor method to complete this function.

Finally, when the official reviewed the code, another line of code was deleted:

That is 4 lines of code, in fact, it should be 2 lines of core code, which completes the requirement for @Async to support expressions.

And the official is to tell you what the solution is first, as long as you follow up a little and start your small brain to think, I think it is not difficult for you to write these 4 lines of code.

This is to contribute source code to Spring, and it is a more valuable contribution. If you seize this opportunity, you can write a sentence on your resume: have contributed source code to Spring, and let @Async annotation support the configuration of expressions.

Generally speaking, friends who don't know much about Spring will only feel very arrogant when they see this sentence, thinking that they should be a big guy.

But in fact, 2 lines of core code will do it.

So you said that it is difficult to contribute source code to Spring?

Opportunities are always there, it just depends on whether you care about it or not.

What, you ask if I have contributed source code to Spring?

I don't, I just don't care, what's the matter.

This is the first point I want to express in writing this article:

Contributing source code to an open source project is actually not a particularly difficult thing. Don't think about submitting an entire function at a time. A little improvement is good.

Debugging Tips

For the code improvement mentioned above, Spring has not released an official package yet, but I want to test it myself, what should I do?

Of course, you can pull down the source code of Spring, then compile a wave yourself, and finally change the source code locally and try it.

But this process is too complicated, and it can basically be said to be a process of persuasion.

For such a small verification, it is totally worthless.

So I teach you a "saucy" operation that I researched myself.

First of all, my local Spring version is 5.3.16, and the source code corresponding to this part is as follows:

Or modify the program first:

Then run the program, trigger a call, and stop at the breakpoint:

At this time, we can see that qualifier is still in the form of an expression.

Then came the bullshit.

When you click on this icon, the corresponding shortcut key is Alt+F8:

This is the Evaluate Expression function provided by ide, in which code can be written.

For example:

It can also steal the beam and change the column. I modified the qualifier to the "yyds" string here:

Then run over the breakpoint, and you can see from the exception message that it was actually modified:

So, if I use the Evaluate Expression function to execute the 4 lines of code submitted this time, is it even simulating the corresponding modified function?

Let me ask you: this method is "saucy" or not.

Next, we get to practice.

Fill these lines of code into Evaluate:

if (beanFactory instanceof ConfigurableBeanFactory) {
    EmbeddedValueResolver embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory)beanFactory);
    qualifier = embeddedValueResolver.resolveStringValue(qualifier);
}

Enter the code snippet, remember to click this icon:

After clicking execute, it looks like this:

Then look at the output log, you can see a line like this:

It shows that my "stealing beam and replacing column" Dafa has been successful.

Isn't that much more convenient than compiling a copy of the Spring source code?

And this debugging method is equivalent to you can execute some additional code when debugging, so sometimes it can really work wonders sometimes.

This is the second purpose of my writing this article, I want to share this debugging method with you.

the difference

Careful readers must have discovered that the official code is a bit strange:

First of all, instanceof is a reserved keyword in Java. Its function is to test whether the object on its left is an instance of the class on its right, and return the data type of boolean.

But I remember that instanceof is not used like this? What kind of shit is this?

Don't panic, stick it out first, put it in the ide and see what's going on:

Our commonly used writing methods are labeled as ①. When I wrote the code labeled as ② in my environment, ide gave me a prompt:

Patterns in 'instanceof' are not supported at language level '8'

Probably means that this usage of instanceof is not supported in JDK 8.

The moment I saw this prompt, I suddenly remembered that this writing seems to be supported by an advanced version of JDK, and I glanced at it somewhere a long time ago.

Then I checked it with the keyword "Patterns instanceof" and found that it is a new feature supported after the JDK 14 version.

https://www.baeldung.com/java-pattern-matching-instanceof

I will directly take out the example in the article and tell you about it.

When we use instanceof, we basically need to check the type of the object, and different types correspond to different logics.

Well, I ask you, you use instanceof, after the type is matched, what is your next step?

Is it a cast to the object?

For example this:

In the above code screenshot, we need to judge the specific type of animal through instanceof in each case, and then force the type conversion to declare it as a local variable, and then execute the specified function according to the specific type.

This way of writing has many disadvantages:

  • It's very tedious to write like this, you need to detect the type and then cast the type.
  • The type name must appear three times for each if.
  • Type conversions and variable declarations are poorly readable
  • Repeated declaration of type names means that it is error-prone , which can lead to unanticipated runtime errors.
  • Each time an animal type is added, the function here is modified.

Pay attention to what I have bolded, which is the same as the original text. This wave of emphasis and details is full:

To address some of the shortcomings mentioned above, Java 14 provides an instanceof operation that combines parameter type checking and binding local variable types.

something like this:

First match the type of animal with Cat in the if block. First see if the animal variable is an instance of Cat type, if so, cast it to Cat type and assign it to cat.

It should be noted that the variable name cat is not a real variable, but a declaration of a pattern variable. You can understand it as a fixed syntax.

The variables cat and dog are only valid and assigned when the result of the pattern matching expression is true. So if you accidentally use the variable elsewhere, it will directly alert you to a compilation error.

So if you compare the code of the above two versions, it must be that the code of the Java 14 version is more concise and easier to understand. A lot of type conversions are reduced, and readability is greatly improved.

Back to Spring

You see, I originally looked at Spring, why did I suddenly write about the new features of JDK?

That must be my foreshadowing.

I show you something:

https://spring.io/blog/2021/09/02/a-java-17-and-jakarta-ee-9-baseline-for-spring-framework-6

Officially announced at last year's SpringOne conference: the JDK baseline version of the two major frameworks Spring 6.0 and Spring Boot 3 is 17.

That said: it's quite possible that after JDK 8, the next version to embrace is JDK 17.

And I, as a technology enthusiast: this is a good thing, and it needs to be supported and strongly supported.

However, as a Java practitioner who writes about CRUD: It's a headache to think about various compatibility issues after upgrading, so I hope this embrace doesn't happen in my short career. Go and let the young and strong guys who have just entered the industry toss it.

And when I limited my perspective to the angle of this article, between the light and the flint, I thought of a "saucy" operation to contribute source code to Spring.

There are so many places where instanceof is used in the historical code. I only need to replace these places with new features in the 6.0 branch. Isn't that a simpler way to contribute source code?

However, before submitting issues, the general process is to check if there are similar submissions.

So, before doing this, I'll do some research on it calmly.

When I checked, I laughed...

I can think of it, and certainly other people can think of it, and sure enough, someone has already taken the lead.

For example here:

https://github.com/spring-projects/spring-framework/issues?q=instanceof

The code submitted this time is as follows:

Then, the official also made a small wave of complaints in it:

Simply put: Brother, for such a small improvement, don't mention the issue. You have to make the whole big one, don't just change one class.

I think it is the same, you can change a module if you change it. For example, this old man changed 8 files under the Spring-beans module:

This is the correct posture for this type of change.

Anyway, I have pointed the way here. If you are interested, you can go and see if there are still some places in the code of Spring 6.0 that have not been changed, and you can try to submit it.

This topic goes back to the first point I made at the beginning:

Contributing source code to an open source project is actually not a particularly difficult thing. Don't think about submitting an entire function at a time. A little improvement is good.

The submitted things are indeed not related to the Spring framework, but you can at least experience the process and feeling of contributing to open source projects, and the larger the project, the more refined the process, and you can definitely learn something.

And what you learn in this process is definitely much better than submitting an instanceof, so you can still say that such a submission is not nutritious?

For example, in an article I wrote last year, I mentioned that Dubbo has an unnecessary repetitive operation when decoding the response message, which can delete a line of verification-related code.

I didn't mention the corresponding pr, but I wrote it in the article.

After a reader saw it, he went to submit it at noon that day, and the official store was soon put into storage.

At the end of last year, the Dubbo community held a feedback activity and gave him a coffee cup:

Surprise, a line of code, not only can you learn a little knowledge, you can also get a coffee cup for free, just ask if it's good.

sublime

Well, review this article.

I started with @Async's support for expressions, to the new features of instanceof, and then to Spring 6 with JDK 17 as the baseline version.

In fact, when I wrote this article, there was a sentence lingering in my mind: the wind starts at the end of Qingping.

instanceof, is the end of Qingping.

Gale is JDK 17 as a baseline release.

Regarding why JDK 17 is used as the baseline version, this is actually a calamity for Java in its prime. Whether the tribulation is successful or not depends on each of our practitioners.

Under the "noisy" of cloud native, the people walking in front have already felt: the wind has been blowing.

For example, Dr. Zhou Zhiming said in a passage called "Cloud Native Era, Java's Crisis and Opportunity":

https://icyfenix.cn/tricks/2020/java-crisis/qcon.html

The next period of time will be an important transition period for Java. If Java 17, the next LTS version, can successfully integrate the new capabilities and new features of Amber, Portola, Valhalla, Loom and Panama, GraalVM can also provide enough strong support If so, Java 17 LTS will most likely be a milestone version, leading the entire Java ecosystem to transform from large-scale server-side applications to a new cloud-native era software system.

It could be a milestone comparable to that year's transition from Java 1 for embedded devices and browser Web Applets to Java 2, which established the rudiments of the modern Java language direction (Java SE/EE/ME and JavaCard).

However, if Java cannot accelerate its pace of development, the moat built by a strong ecosystem will eventually be exhausted, and a large part of the market will be eroded by new languages such as Golang and Rust, as well as old rivals such as C, C++, C#, and Python. share, and even forced to abdicate from the throne of the "world's first" programming language.

Whether the future of Java will continue to move forward and climb to a new peak, or will it change from prosperity to decline, and its edge will shrink, you and I will wait and see.

And I have only seen the end of Qingping.

Finally, the article was first published on the public account [why technology]. Welcome to pay attention and receive the latest articles as soon as possible.


why技术
2.2k 声望6.8k 粉丝