feature: support feign client custom configuration
There's a potential problem, if CircuitBreakerFactory was injected in spring's lifecycle, will possibly get the raw one(not customized). So, remove ReactiveSentinelCircuitBreakerAutoConfiguration and SentinelCircuitBreakerAutoConfiguration inner configuration class.pull/2342/head
parent
973372194a
commit
eb59569d35
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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
|
||||
*
|
||||
* https://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 com.alibaba.cloud.circuitbreaker.sentinel.feign;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.alibaba.cloud.circuitbreaker.sentinel.ReactiveSentinelCircuitBreakerFactory;
|
||||
import com.alibaba.cloud.circuitbreaker.sentinel.SentinelCircuitBreakerFactory;
|
||||
import com.alibaba.cloud.circuitbreaker.sentinel.SentinelConfigBuilder;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||
import feign.Feign;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.circuitbreaker.AbstractCircuitBreakerFactory;
|
||||
import org.springframework.cloud.client.circuitbreaker.Customizer;
|
||||
import org.springframework.cloud.openfeign.FeignClientFactoryBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Auto configuration for feign client circuit breaker rules.
|
||||
*
|
||||
* @author freeman
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Feign.class, FeignClientFactoryBean.class })
|
||||
@ConditionalOnProperty(name = "spring.cloud.circuitbreaker.sentinel.enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(SentinelFeignClientProperties.class)
|
||||
public class SentinelFeignClientAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public static class SentinelCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
public Customizer<SentinelCircuitBreakerFactory> configureRulesCustomizer(
|
||||
SentinelFeignClientProperties properties) {
|
||||
return factory -> {
|
||||
configureDefault(properties, factory);
|
||||
configureCustom(properties, factory);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(name = { "reactor.core.publisher.Mono",
|
||||
"reactor.core.publisher.Flux" })
|
||||
public static class ReactiveSentinelCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
public Customizer<ReactiveSentinelCircuitBreakerFactory> reactiveConfigureRulesCustomizer(
|
||||
SentinelFeignClientProperties properties) {
|
||||
return factory -> {
|
||||
configureDefault(properties, factory);
|
||||
configureCustom(properties, factory);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void configureCustom(SentinelFeignClientProperties properties,
|
||||
AbstractCircuitBreakerFactory factory) {
|
||||
properties.getRules().forEach((resourceName, degradeRules) -> {
|
||||
if (!Objects.equals(properties.getDefaultRule(), resourceName)) {
|
||||
factory.configure(builder -> ((SentinelConfigBuilder) builder)
|
||||
.rules(properties.getRules().getOrDefault(resourceName,
|
||||
new ArrayList<>())),
|
||||
resourceName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void configureDefault(SentinelFeignClientProperties properties,
|
||||
AbstractCircuitBreakerFactory factory) {
|
||||
List<DegradeRule> defaultConfigurations = properties.getRules()
|
||||
.getOrDefault(properties.getDefaultRule(), new ArrayList<>());
|
||||
factory.configureDefault(
|
||||
resourceName -> new SentinelConfigBuilder(resourceName.toString())
|
||||
.entryType(EntryType.OUT).rules(defaultConfigurations).build());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.alibaba.cloud.circuitbreaker.sentinel.feign;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Sentinel feign client properties.
|
||||
*
|
||||
* @author freeman
|
||||
*/
|
||||
@ConfigurationProperties("feign.sentinel")
|
||||
public class SentinelFeignClientProperties {
|
||||
|
||||
/**
|
||||
* default rule name
|
||||
*/
|
||||
private String defaultRule = "default";
|
||||
|
||||
private Map<String, List<DegradeRule>> rules = new HashMap<>();
|
||||
|
||||
public String getDefaultRule() {
|
||||
return defaultRule;
|
||||
}
|
||||
|
||||
public void setDefaultRule(String defaultRule) {
|
||||
this.defaultRule = defaultRule;
|
||||
}
|
||||
|
||||
public Map<String, List<DegradeRule>> getRules() {
|
||||
return rules;
|
||||
}
|
||||
|
||||
public void setRules(Map<String, List<DegradeRule>> rules) {
|
||||
this.rules = rules;
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.alibaba.cloud.circuitbreaker.sentinel.SentinelCircuitBreakerAutoConfiguration,\
|
||||
com.alibaba.cloud.circuitbreaker.sentinel.ReactiveSentinelCircuitBreakerAutoConfiguration
|
||||
com.alibaba.cloud.circuitbreaker.sentinel.ReactiveSentinelCircuitBreakerAutoConfiguration,\
|
||||
com.alibaba.cloud.circuitbreaker.sentinel.feign.SentinelFeignClientAutoConfiguration
|
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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
|
||||
*
|
||||
* https://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 com.alibaba.cloud.circuitbreaker.sentinel.feign;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
|
||||
|
||||
/**
|
||||
* @author freeman
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = DEFINED_PORT, classes = CustomFeignClientCircuitBreakerRuleIntegrationTest.Application.class, properties = {
|
||||
"server.port=10101", "feign.circuitbreaker.enabled=true",
|
||||
"spring.cloud.discovery.client.health-indicator.enabled=false",
|
||||
"feign.sentinel.default-rule=default",
|
||||
"feign.sentinel.rules.default[0].grade=2",
|
||||
"feign.sentinel.rules.default[0].count=1",
|
||||
"feign.sentinel.rules.default[0].timeWindow=1",
|
||||
"feign.sentinel.rules.default[0].statIntervalMs=1000",
|
||||
"feign.sentinel.rules.default[0].minRequestAmount=5",
|
||||
"feign.sentinel.rules.[UserClient#success(boolean)][0].grade=2",
|
||||
"feign.sentinel.rules.[UserClient#success(boolean)][0].count=1",
|
||||
"feign.sentinel.rules.[UserClient#success(boolean)][0].timeWindow=1",
|
||||
"feign.sentinel.rules.[UserClient#success(boolean)][0].statIntervalMs=1000",
|
||||
"feign.sentinel.rules.[UserClient#success(boolean)][0].minRequestAmount=5" })
|
||||
@DirtiesContext
|
||||
public class CustomFeignClientCircuitBreakerRuleIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private Application.UserClient userClient;
|
||||
|
||||
@Test
|
||||
public void testConfigSpecificRule() throws Exception {
|
||||
// test specific configuration is working
|
||||
|
||||
// ok
|
||||
assertThat(userClient.success(true)).isEqualTo("ok");
|
||||
|
||||
// occur exception, circuit breaker open
|
||||
assertThat(userClient.success(false)).isEqualTo("fallback");
|
||||
assertThat(userClient.success(false)).isEqualTo("fallback");
|
||||
assertThat(userClient.success(false)).isEqualTo("fallback");
|
||||
assertThat(userClient.success(false)).isEqualTo("fallback");
|
||||
assertThat(userClient.success(false)).isEqualTo("fallback");
|
||||
|
||||
// test circuit breaker open
|
||||
assertThat(userClient.success(true)).isEqualTo("fallback");
|
||||
assertThat(userClient.success(true)).isEqualTo("fallback");
|
||||
|
||||
Thread.sleep(1100L);
|
||||
|
||||
// test circuit breaker close
|
||||
assertThat(userClient.success(true)).isEqualTo("ok");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigDefaultRule() throws Exception {
|
||||
// test default configuration is working
|
||||
|
||||
// ok
|
||||
assertThat(userClient.defaultConfig(true)).isEqualTo("ok");
|
||||
|
||||
// occur exception, circuit breaker open
|
||||
assertThat(userClient.defaultConfig(false)).isEqualTo("fallback");
|
||||
assertThat(userClient.defaultConfig(false)).isEqualTo("fallback");
|
||||
assertThat(userClient.defaultConfig(false)).isEqualTo("fallback");
|
||||
assertThat(userClient.defaultConfig(false)).isEqualTo("fallback");
|
||||
assertThat(userClient.defaultConfig(false)).isEqualTo("fallback");
|
||||
|
||||
// test circuit breaker open
|
||||
assertThat(userClient.defaultConfig(true)).isEqualTo("fallback");
|
||||
assertThat(userClient.defaultConfig(true)).isEqualTo("fallback");
|
||||
|
||||
Thread.sleep(1100L);
|
||||
|
||||
// test circuit breaker close
|
||||
assertThat(userClient.defaultConfig(true)).isEqualTo("ok");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void reset() {
|
||||
DegradeRuleManager.loadRules(new ArrayList<>());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableFeignClients
|
||||
protected static class Application {
|
||||
|
||||
@FeignClient(value = "user", url = "http://localhost:${server.port}", fallback = UserClientFallback.class)
|
||||
interface UserClient {
|
||||
|
||||
@GetMapping("/{success}")
|
||||
String success(@PathVariable boolean success);
|
||||
|
||||
@GetMapping("/default/{success}")
|
||||
String defaultConfig(@PathVariable boolean success);
|
||||
}
|
||||
|
||||
@Component
|
||||
static class UserClientFallback implements UserClient {
|
||||
|
||||
@Override
|
||||
public String success(boolean success) {
|
||||
return "fallback";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String defaultConfig(boolean success) {
|
||||
return "fallback";
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class UserController {
|
||||
|
||||
@GetMapping("/{success}")
|
||||
public String success(@PathVariable boolean success) {
|
||||
if (success) {
|
||||
return "ok";
|
||||
}
|
||||
throw new RuntimeException("failed");
|
||||
}
|
||||
|
||||
@GetMapping("/default/{success}")
|
||||
String defaultConfig(@PathVariable boolean success) {
|
||||
if (success) {
|
||||
return "ok";
|
||||
}
|
||||
throw new RuntimeException("failed");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue