mirror of
https://gitee.com/mirrors/Spring-Cloud-Alibaba.git
synced 2021-06-26 13:25:11 +08:00
Sentinel support feign
This commit is contained in:
parent
fb9e0f51f4
commit
6104799a36
@ -35,6 +35,13 @@
|
|||||||
<artifactId>sentinel-dubbo-adapter</artifactId>
|
<artifactId>sentinel-dubbo-adapter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
|
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cloud.alibaba.sentinel.feign;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import feign.Contract;
|
||||||
|
import feign.MethodMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Using static field {@link SentinelContractHolder#metadataMap} to hold
|
||||||
|
* {@link MethodMetadata} data
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||||
|
*/
|
||||||
|
public class SentinelContractHolder implements Contract {
|
||||||
|
|
||||||
|
private final Contract delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map key is constructed by ClassFullName + configKey. configKey is constructed by
|
||||||
|
* {@link feign.Feign#configKey}
|
||||||
|
*/
|
||||||
|
public final static Map<String, MethodMetadata> metadataMap = new HashMap();
|
||||||
|
|
||||||
|
public SentinelContractHolder(Contract delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {
|
||||||
|
List<MethodMetadata> metadatas = delegate.parseAndValidatateMetadata(targetType);
|
||||||
|
for (MethodMetadata metadata : metadatas) {
|
||||||
|
metadataMap.put(targetType.getName() + metadata.configKey(), metadata);
|
||||||
|
}
|
||||||
|
return metadatas;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cloud.alibaba.sentinel.feign;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.cloud.netflix.feign.FeignContext;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
import feign.Contract;
|
||||||
|
import feign.Feign;
|
||||||
|
import feign.InvocationHandlerFactory;
|
||||||
|
import feign.Target;
|
||||||
|
import feign.hystrix.FallbackFactory;
|
||||||
|
import feign.hystrix.HystrixFeign;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Feign.Builder} like {@link HystrixFeign.Builder}
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||||
|
*/
|
||||||
|
public class SentinelFeign {
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder extends Feign.Builder
|
||||||
|
implements ApplicationContextAware {
|
||||||
|
|
||||||
|
private Contract contract = new Contract.Default();
|
||||||
|
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
private FeignContext feignContext;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Feign.Builder invocationHandlerFactory(
|
||||||
|
InvocationHandlerFactory invocationHandlerFactory) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder contract(Contract contract) {
|
||||||
|
this.contract = contract;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Feign build() {
|
||||||
|
super.invocationHandlerFactory(new InvocationHandlerFactory() {
|
||||||
|
@Override
|
||||||
|
public InvocationHandler create(Target target,
|
||||||
|
Map<Method, MethodHandler> dispatch) {
|
||||||
|
// using reflect get fallback and fallbackFactory properties from
|
||||||
|
// FeignClientFactoryBean because FeignClientFactoryBean is a package
|
||||||
|
// level class, we can not use it in our package
|
||||||
|
Object feignClientFactoryBean = Builder.this.applicationContext
|
||||||
|
.getBean("&" + target.type().getName());
|
||||||
|
|
||||||
|
Class fallback = (Class) getFieldValue(feignClientFactoryBean,
|
||||||
|
"fallback");
|
||||||
|
Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
|
||||||
|
"fallbackFactory");
|
||||||
|
String name = (String) getFieldValue(feignClientFactoryBean, "name");
|
||||||
|
|
||||||
|
Object fallbackInstance;
|
||||||
|
FallbackFactory fallbackFactoryInstance;
|
||||||
|
// check fallback and fallbackFactory properties
|
||||||
|
if (void.class != fallback) {
|
||||||
|
fallbackInstance = getFromContext(name, "fallback", fallback,
|
||||||
|
target);
|
||||||
|
return new SentinelInvocationHandler(target, dispatch,
|
||||||
|
new FallbackFactory.Default(fallbackInstance));
|
||||||
|
}
|
||||||
|
if (void.class != fallbackFactory) {
|
||||||
|
fallbackFactoryInstance = (FallbackFactory) getFromContext(name,
|
||||||
|
"fallbackFactory", fallbackFactory, target);
|
||||||
|
return new SentinelInvocationHandler(target, dispatch,
|
||||||
|
fallbackFactoryInstance);
|
||||||
|
}
|
||||||
|
return new SentinelInvocationHandler(target, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getFromContext(String name, String type,
|
||||||
|
Class fallbackType, Target target) {
|
||||||
|
Object fallbackInstance = feignContext.getInstance(name,
|
||||||
|
fallbackType);
|
||||||
|
if (fallbackInstance == null) {
|
||||||
|
throw new IllegalStateException(String.format(
|
||||||
|
"No %s instance of type %s found for feign client %s",
|
||||||
|
type, fallbackType, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target.type().isAssignableFrom(fallbackType)) {
|
||||||
|
throw new IllegalStateException(String.format(
|
||||||
|
"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
|
||||||
|
type, fallbackType, target.type(), name));
|
||||||
|
}
|
||||||
|
return fallbackInstance;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
super.contract(new SentinelContractHolder(contract));
|
||||||
|
return super.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getFieldValue(Object instance, String fieldName) {
|
||||||
|
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
try {
|
||||||
|
return field.get(instance);
|
||||||
|
}
|
||||||
|
catch (IllegalAccessException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext)
|
||||||
|
throws BeansException {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
feignContext = this.applicationContext.getBean(FeignContext.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cloud.alibaba.sentinel.feign;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.SphU;
|
||||||
|
|
||||||
|
import feign.Feign;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass({ SphU.class, Feign.class })
|
||||||
|
public class SentinelFeignAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Scope("prototype")
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
@ConditionalOnProperty(name = "feign.sentinel.enabled")
|
||||||
|
public Feign.Builder feignSentinelBuilder() {
|
||||||
|
return SentinelFeign.builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cloud.alibaba.sentinel.feign;
|
||||||
|
|
||||||
|
import static feign.Util.checkNotNull;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.Entry;
|
||||||
|
import com.alibaba.csp.sentinel.EntryType;
|
||||||
|
import com.alibaba.csp.sentinel.SphU;
|
||||||
|
import com.alibaba.csp.sentinel.Tracer;
|
||||||
|
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||||
|
|
||||||
|
import feign.Feign;
|
||||||
|
import feign.InvocationHandlerFactory.MethodHandler;
|
||||||
|
import feign.MethodMetadata;
|
||||||
|
import feign.Target;
|
||||||
|
import feign.hystrix.FallbackFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link InvocationHandler} handle invocation that protected by Sentinel
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||||
|
*/
|
||||||
|
public class SentinelInvocationHandler implements InvocationHandler {
|
||||||
|
|
||||||
|
private final Target<?> target;
|
||||||
|
private final Map<Method, MethodHandler> dispatch;
|
||||||
|
|
||||||
|
private FallbackFactory fallbackFactory;
|
||||||
|
private Map<Method, Method> fallbackMethodMap;
|
||||||
|
|
||||||
|
SentinelInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch,
|
||||||
|
FallbackFactory fallbackFactory) {
|
||||||
|
this.target = checkNotNull(target, "target");
|
||||||
|
this.dispatch = checkNotNull(dispatch, "dispatch");
|
||||||
|
this.fallbackFactory = fallbackFactory;
|
||||||
|
this.fallbackMethodMap = toFallbackMethod(dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
SentinelInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch) {
|
||||||
|
this.target = checkNotNull(target, "target");
|
||||||
|
this.dispatch = checkNotNull(dispatch, "dispatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(final Object proxy, final Method method, final Object[] args)
|
||||||
|
throws Throwable {
|
||||||
|
if ("equals".equals(method.getName())) {
|
||||||
|
try {
|
||||||
|
Object otherHandler = args.length > 0 && args[0] != null
|
||||||
|
? Proxy.getInvocationHandler(args[0])
|
||||||
|
: null;
|
||||||
|
return equals(otherHandler);
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("hashCode".equals(method.getName())) {
|
||||||
|
return hashCode();
|
||||||
|
}
|
||||||
|
else if ("toString".equals(method.getName())) {
|
||||||
|
return toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object result;
|
||||||
|
MethodHandler methodHandler = this.dispatch.get(method);
|
||||||
|
// only handle by HardCodedTarget
|
||||||
|
if (target instanceof Target.HardCodedTarget) {
|
||||||
|
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
|
||||||
|
MethodMetadata methodMetadata = SentinelContractHolder.metadataMap
|
||||||
|
.get(method.getDeclaringClass().getName()
|
||||||
|
+ Feign.configKey(method.getDeclaringClass(), method));
|
||||||
|
// resource default is HttpMethod:protocol://url
|
||||||
|
String resourceName = methodMetadata.template().method().toUpperCase() + ":"
|
||||||
|
+ hardCodedTarget.url() + methodMetadata.template().url();
|
||||||
|
Entry entry = null;
|
||||||
|
try {
|
||||||
|
ContextUtil.enter(resourceName);
|
||||||
|
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
|
||||||
|
result = methodHandler.invoke(args);
|
||||||
|
}
|
||||||
|
catch (Throwable ex) {
|
||||||
|
// fallback handle
|
||||||
|
if (!BlockException.isBlockException(ex)) {
|
||||||
|
Tracer.trace(ex);
|
||||||
|
}
|
||||||
|
if (fallbackFactory != null) {
|
||||||
|
try {
|
||||||
|
Object fallbackResult = fallbackMethodMap.get(method)
|
||||||
|
.invoke(fallbackFactory.create(ex), args);
|
||||||
|
return fallbackResult;
|
||||||
|
}
|
||||||
|
catch (IllegalAccessException e) {
|
||||||
|
// shouldn't happen as method is public due to being an interface
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException e) {
|
||||||
|
throw new AssertionError(e.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// throw exception if fallbackFactory is null
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (entry != null) {
|
||||||
|
entry.exit();
|
||||||
|
}
|
||||||
|
ContextUtil.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// other target type using default strategy
|
||||||
|
result = methodHandler.invoke(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof SentinelInvocationHandler) {
|
||||||
|
SentinelInvocationHandler other = (SentinelInvocationHandler) obj;
|
||||||
|
return target.equals(other.target);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return target.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return target.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<Method, Method> toFallbackMethod(Map<Method, MethodHandler> dispatch) {
|
||||||
|
Map<Method, Method> result = new LinkedHashMap<>();
|
||||||
|
for (Method method : dispatch.keySet()) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
result.put(method, method);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\
|
org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\
|
||||||
org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\
|
org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\
|
||||||
org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration
|
org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration,\
|
||||||
|
org.springframework.cloud.alibaba.sentinel.feign.SentinelFeignAutoConfiguration
|
||||||
|
Loading…
x
Reference in New Issue
Block a user