-
Notifications
You must be signed in to change notification settings - Fork 8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor SpiLoader and enhance its SPI functions #1383
Conversation
Could you please resolve conflicts? |
…nstanceOrDefault method, improve test cases
d958a5b
to
d3926c4
Compare
Rebase the branch and the conflicts have been resolved. |
@@ -0,0 +1,5 @@ | |||
# One | |||
com.alibaba.csp.sentinel.spi.TestOneProvider | |||
com.alibaba.csp.sentinel.spi.TestTwoProvider # Two |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest to beautify the comment. Move it to a separated line maybe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, normally we use a separated line to write the comment, which is more beautify and clear.
Here is intended to write the comment like this, to test the robustness of the program in test case.
|
||
/** | ||
* @author Eric Zhao | ||
* @since 1.6.1 | ||
*/ | ||
@SpiOrder(-4000) | ||
@Spi(order = -4000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use constant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And some slots are stateless while others are stateful. Maybe isSingleton
should be specified obviously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused here, not very sure and use -4000
temporarily.
There are some constants of slot order definitions In Constants
class of sentinel-core module:
/**
* Order of default processor slots
*/
public static final int ORDER_NODE_SELECTOR_SLOT = -10000;
public static final int ORDER_CLUSTER_BUILDER_SLOT = -9000;
public static final int ORDER_LOG_SLOT = -8000;
public static final int ORDER_STATISTIC_SLOT = -7000;
public static final int ORDER_AUTHORITY_SLOT = -6000;
public static final int ORDER_SYSTEM_SLOT = -5000;
// order of GatewayFlowSlot -4000
// order of ParamFlowSlot -3000
public static final int ORDER_FLOW_SLOT = -2000;
public static final int ORDER_DEGRADE_SLOT = -1000;
If use constant, which is better?
public static final int ORDER_GATEWAY_FLOW_SLOT = -4000;
public static final int ORDER_PARAM_FLOW_SLOT = -3000;
@Spi(order = Constants.ORDER_SYSTEM_SLOT)
public class GatewayFlowSlot {
No constant for ORDER_GATEWAY_FLOW_SLOT, ORDER_PARAM_FLOW_SLOT
@Spi(order = Constants.ORDER_SYSTEM_SLOT + 1000)
public class GatewayFlowSlot {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Constants.ORDER_SYSTEM_SLOT
Yeah whatever you want.
If you want to make it reference-able just replace it with a constant in gateway package;
If you want to express some kinds of relationship with existing slots just use relative calculation to existed constants;
If you insist on pure definition just leave it unchanged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These three ways speak my heart. I've been struggling with this for a long time.
Now I think the third way is not very good. Maybe one or two is better.
For a selection difficulty person, could you please help to make a good choice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These three ways speak my heart. I've been struggling with this for a long time.
Now I think the third way is not very good. Maybe one or two is better.
For a selection difficulty person, could you please help to make a good choice?
Go write a random test and make result confirmed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it! Using the first way, since it expresses the order of all default slot of sentinel clearly. Maybe the second way is a good choice for user custom slot.
* @param <T> Service type | ||
* @return SpiLoader instance | ||
*/ | ||
public static <T> SpiLoader<T> getSpiLoader(Class<T> service) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private
or package default
scope is suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is intend to provide another method to create SpiLoader
instance, and inside it just call of
method.
How about remove it? Maybe of
method is enough.
Just now, I found that in JDK's Optional
, apache commons's Pair
, they only have of
method to create its instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah i think this scaffold class should be shorter so just don't be limited by legacy design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This unused method has been removed, only of
method is remained.
* Reset all SpiLoader instances | ||
*/ | ||
public static void resetAll() { | ||
Set<Map.Entry<String, SpiLoader>> entries = SPI_LOADER_MAP.entrySet(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should also sychronize it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And it looks more like resetAndClear()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are reset*()
only used in unit tests?
If so i suggest to comment them obviously and make them package scope is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They have been improved, please check.
* @param closeables {@link Closeable} resources | ||
*/ | ||
private void closeResources(Closeable... closeables) { | ||
if (closeables == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe len=0
should also be added here for more obvious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, it has been added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has been improved.
try { | ||
closeable.close(); | ||
} catch (Exception e) { | ||
fail("error closing SPI file", e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SPI -> resource ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, SPI file may be confusing.
May I use error closing SPI resource file
or error closing SPI configuration file
?
This method is used for close InputStream
and BufferedReader
after reading and parsing configuration file.
SPI is used to emphasize the operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, SPI file may be confusing.
May I useerror closing SPI resource file
orerror closing SPI configuration file
?
This method is used for closeInputStream
andBufferedReader
after reading and parsing configuration file.
SPI is used to emphasize the operation.
File
is already a kind of resource
. Or do you mean you implemented a method which is more like closeFiles(...)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prompt message changed to error closing SPI configuration file
, and the exception in closeResources
method seems rarely happen.
* | ||
* @return Provider instance of highest order priority | ||
*/ | ||
public S loadHighestPriorityInstance() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe loadOne
for short. Is there any other way to get target instance? And surely i am curious that why we need loadLowestPriorityInstance
or loadFirstInstance
. And talk to isDefault
in SPI annotation we can make the default
service implementation as lowest
priority. Then we can use loadOne()
instead of something like loadFirstInstanceOrDefault
.
Thus loadInstance
loadDefault
also don't have any scenario to be applied.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loadHighestPriorityInstance
,loadFirstInstance
keeps same with previous version.
In SlotChainProvider
, we have DefaultSlotChainBuilder
has default implementation.
slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
In TokenClientProvider
, the difference is no default SPI implementation.
`ClusterTokenClient resolvedClient = SpiLoader.of(ClusterTokenClient.class).loadFirstInstance();`
Distinguish them may be more flexible.
com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot | ||
com.alibaba.csp.sentinel.slots.system.SystemSlot |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, now not changed in this PR.
…rove the log info,remove unused code in SpiLoader
public static final int ORDER_STATISTIC_SLOT = -7000; | ||
public static final int ORDER_AUTHORITY_SLOT = -6000; | ||
public static final int ORDER_SYSTEM_SLOT = -5000; | ||
public static final int ORDER_GATEWAY_FLOW_SLOT = -4000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a good practice setting unrelated constant here. You'd better put it in gateway adapter package. If you feel a little unsure to decide the value of it in separated package you can take it as a user-defined
implementation out of sentinel-core.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two constant ORDER_GATEWAY_FLOW_SLOT
,ORDER_PARAM_FLOW_SLOT
have been removed here.
Hi, @cdfive What's the plan on this PR? |
@jasonjoo2010 Sorry for late... I'm busy with my work now, maybe I can improve it this weekend. |
That's all right💪 |
…ment # Conflicts: # sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java # sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtensionProvider.java # sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java # sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java
Conflicts have been resolved, but the integration-test run failed, I tried several times and have not found the point.
Could you please give me some help? @sczyh30 @jasonjoo2010 |
Seems like an infinite slot chain was constructed?
|
…nning test cases.
I located the exception, which is caused by more than one SPI file, and the sortedClassList added more than once in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool
@nickChenyx I'm sorry, the double check when creating new instance is incorrect indeed. It has been fixed(after synchronized (this), check instance not null again, it's really necessary since the instance is a local variable). |
This is a good enhancement! @cdfive |
@canglang1973 Thanks! Come together to review the code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @return Provider class | ||
*/ | ||
private S createInstance(Class<? extends S> clazz) { | ||
Spi spi = clazz.getAnnotation(Spi.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will it remain for future improvement? About meta caching.
* @param clazzList class types of Providers | ||
* @return Provider instance list | ||
*/ | ||
private List<S> createInstanceList(List<Class<? extends S>> clazzList) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this one? Remain it this time?
是指要像Spring的 |
对类似,性能相关可以考虑分阶段改进 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Awesome work. Thanks for contributing! As this PR contains breaking changes, we may need to describe this in the release note of the next version. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
👍 🥂 |
* Add `@Spi` annotation as the general annotation for SPI definition. * Add isDefault in @SPI, add loadDefaultInstance and improve loadFirstInstanceOrDefault method, improve test cases * Add SpiLoaderException class for thrown when something goes wrong while loading Provider * Rearrange packages of base SPI mechanism NOTE: this PR contains breaking changes regarding API.
建议小版本号的更新保持下兼容性,因为与其他子模块之间不兼容。用户仅仅只是将 |
* Add `@Spi` annotation as the general annotation for SPI definition. * Add isDefault in @SPI, add loadDefaultInstance and improve loadFirstInstanceOrDefault method, improve test cases * Add SpiLoaderException class for thrown when something goes wrong while loading Provider * Rearrange packages of base SPI mechanism NOTE: this PR contains breaking changes regarding API.
* Add `@Spi` annotation as the general annotation for SPI definition. * Add isDefault in @SPI, add loadDefaultInstance and improve loadFirstInstanceOrDefault method, improve test cases * Add SpiLoaderException class for thrown when something goes wrong while loading Provider * Rearrange packages of base SPI mechanism NOTE: this PR contains breaking changes regarding API.
Refactor SpiLoader and enhance SPI mechanism (alibaba#1383)
* Add `@Spi` annotation as the general annotation for SPI definition. * Add isDefault in @SPI, add loadDefaultInstance and improve loadFirstInstanceOrDefault method, improve test cases * Add SpiLoaderException class for thrown when something goes wrong while loading Provider * Rearrange packages of base SPI mechanism NOTE: this PR contains breaking changes regarding API.
Describe what this PR does / why we need it
Refer and compared with some SPI implementations,
ServiceLoader
of JDK,ExtensionLoader
of Dubbo and SOFARPC, I'd like to introduce some of their advantages and try to enhanceSpiLoader
of Sentinel, to provide more functions to support more scenarios, such as singleton/multiple instance, loaded by alias name, default Provider of SPI, etc.Does this pull request fix one issue?
Fixes #1379
Describe how you did it
Add
@Spi
annotation with following fields:value
aliasname of concrete Provider
by default is Provider class name
isSingleton
whether create singleton Provider instance
by default is true, since singleton is more common
isDefault
whether is the default Provider
by default is false, if it's the default implementation, we explicitly identify it
order
order priority of concrete Provider class
by default is 0
For Provider class which need create multiple instances, such as
NodeSelectorSlot
:For Provider class which create singleton instance, such as
FlowSlot
:For Provider class which is the default Provider, such as
DefaultSlotChainBuilder
:I didn't exactly copy the way Dubbo and SOFARPC did, and maybe thought as an upgrade of default JDK's SPI, that is
ServiceLoader
, and enhance its functions.Along this line of thought, the usage is exactly same as JDK's SPI, we can also omit the
@Spi
annotation if wanted, worked with default behavior.Functions of
SpiLoader
:Load Provider instance list
loadInstanceList
/loadInstanceListSorted
Load specific order or default Provider instance
loadHighestPriorityInstance
/loadLowestPriorityInstance
/loadDefaultInstance
Load one specific Provider via Class or aliasname
loadInstance(Class)
/loadInstance(String aliasName)
Since the order of
ProcessorSlot
is very important, I add constant variables inConstants
class for the order value of@Spi
, so we can see the order of system default slots more intuitively. TheGatewayFlowSlot
andParamFlowSlot
are in other module, so add with comment lines.Constants
:SpiLoader
has been moved to spi package, the same as@Spi
.@SpiOrder
,ServiceLoaderUtil
has been removed sinceSpiLoader
include the functions.Not add more class like
ExtensionClass
in SOFARPC,ExtentionLoaderFactory
in Dubbo, just oneSpiLoder
class, to reduce complexity and make it simple.Add
SpiLoaderException
class for thrown when something goes wrong while loading Provider.Describe how to verify it
Run test cases.
Special notes for reviews
At first I use
singleton
as field name, when addingdefault
field, found that it's keyword of Java. So modified withisSingleton
,isDefault
, theis
prefix hinted that it's a boolean value with true/false. Maybe there are other better ways to name them, please tell me and I will optimize it.Now the implementation may not very perfect, please take a review and I'd like to improve the code step by step.