Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
spring-cloud-netflix
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
openSource
spring-cloud-netflix
Commits
8e18c3d3
Commit
8e18c3d3
authored
Oct 05, 2016
by
bpicode
Committed by
Spencer Gibb
Oct 04, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add support for fallback factories in feign client annotation. (#1373)
Adds `@FeignClient.fallbackFactory` to define a `feign.hystrix.FallbackFactory`. fixes gh-1117
parent
0adbd566
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
242 additions
and
31 deletions
+242
-31
spring-cloud-netflix.adoc
docs/src/main/asciidoc/spring-cloud-netflix.adoc
+24
-0
FeignClient.java
.../org/springframework/cloud/netflix/feign/FeignClient.java
+9
-0
FeignClientFactoryBean.java
...framework/cloud/netflix/feign/FeignClientFactoryBean.java
+2
-0
FeignClientsRegistrar.java
...gframework/cloud/netflix/feign/FeignClientsRegistrar.java
+1
-0
HystrixTargeter.java
.../springframework/cloud/netflix/feign/HystrixTargeter.java
+55
-11
FeignClientValidationTests.java
...oud/netflix/feign/invalid/FeignClientValidationTests.java
+107
-17
FeignClientTests.java
...framework/cloud/netflix/feign/valid/FeignClientTests.java
+44
-3
No files found.
docs/src/main/asciidoc/spring-cloud-netflix.adoc
View file @
8e18c3d3
...
...
@@ -1012,6 +1012,30 @@ static class HystrixClientFallback implements HystrixClient {
}
----
If
one
needs
access
to
the
cause
that
made
the
fallback
trigger
,
one
can
use
the
`
fallbackFactory
`
attribute
inside
`@
FeignClient
`.
[
source
,
java
,
indent
=
0
]
----
@
FeignClient
(
name
=
"hello"
,
fallbackFactory
=
HystrixClientFallbackFactory
.
class
)
protected
interface
HystrixClient
{
@
RequestMapping
(
method
=
RequestMethod
.
GET
,
value
=
"/hello"
)
Hello
iFailSometimes
();
}
@
Component
static
class
HystrixClientFallbackFactory
implements
FallbackFactory
<
HystrixClient
>
{
@
Override
public
HystrixClient
create
(
Throwable
cause
)
{
return
new
HystrixClientWithFallBackFactory
()
{
@
Override
public
Hello
iFailSometimes
()
{
return
new
Hello
(
"fallback; reason was: "
+
cause
.
getMessage
());
}
};
}
}
----
WARNING
:
There
is
a
limitation
with
the
implementation
of
fallbacks
in
Feign
and
how
Hystrix
fallbacks
work
.
Fallbacks
are
currently
not
supported
for
methods
that
return
`
com
.
netflix
.
hystrix
.
HystrixCommand
`
and
`
rx
.
Observable
`.
[[
spring
-
cloud
-
feign
-
inheritance
]]
...
...
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java
View file @
8e18c3d3
...
...
@@ -91,6 +91,15 @@ public @interface FeignClient {
Class
<?>
fallback
()
default
void
.
class
;
/**
* Define a fallback factory for the specified Feign client interface. The fallback
* factory must produce instances of fallback classes that implement the interface
* annotated by {@link FeignClient}.
*
* @see feign.hystrix.FallbackFactory for details.
*/
Class
<?>
fallbackFactory
()
default
void
.
class
;
/**
* Path prefix to be used by all method-level mappings. Can be used with or without
* <code>@RibbonClient</code>.
*/
...
...
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientFactoryBean.java
View file @
8e18c3d3
...
...
@@ -69,6 +69,8 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
private
Class
<?>
fallback
=
void
.
class
;
private
Class
<?>
fallbackFactory
=
void
.
class
;
@Override
public
void
afterPropertiesSet
()
throws
Exception
{
Assert
.
hasText
(
this
.
name
,
"Name must be set"
);
...
...
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsRegistrar.java
View file @
8e18c3d3
...
...
@@ -179,6 +179,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
definition
.
addPropertyValue
(
"type"
,
className
);
definition
.
addPropertyValue
(
"decode404"
,
attributes
.
get
(
"decode404"
));
definition
.
addPropertyValue
(
"fallback"
,
attributes
.
get
(
"fallback"
));
definition
.
addPropertyValue
(
"fallbackFactory"
,
attributes
.
get
(
"fallbackFactory"
));
definition
.
setAutowireMode
(
AbstractBeanDefinition
.
AUTOWIRE_BY_TYPE
);
String
alias
=
name
+
"FeignClient"
;
...
...
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/HystrixTargeter.java
View file @
8e18c3d3
...
...
@@ -19,6 +19,9 @@ package org.springframework.cloud.netflix.feign;
import
feign.Feign
;
import
feign.Target
;
import
feign.hystrix.FallbackFactory
;
import
feign.hystrix.HystrixFeign
;
import
org.springframework.util.Assert
;
/**
* @author Spencer Gibb
...
...
@@ -29,26 +32,67 @@ class HystrixTargeter implements Targeter {
@Override
public
<
T
>
T
target
(
FeignClientFactoryBean
factory
,
Feign
.
Builder
feign
,
FeignContext
context
,
Target
.
HardCodedTarget
<
T
>
target
)
{
if
(
factory
.
getFallback
()
==
void
.
class
||
!(
feign
instanceof
feign
.
hystrix
.
HystrixFeign
.
Builder
))
{
if
(!(
feign
instanceof
feign
.
hystrix
.
HystrixFeign
.
Builder
))
{
return
feign
.
target
(
target
);
}
feign
.
hystrix
.
HystrixFeign
.
Builder
builder
=
(
feign
.
hystrix
.
HystrixFeign
.
Builder
)
feign
;
Class
<?>
fallback
=
factory
.
getFallback
();
if
(
fallback
!=
void
.
class
)
{
return
targetWithFallback
(
factory
.
getName
(),
context
,
target
,
builder
,
fallback
);
}
Class
<?>
fallbackFactory
=
factory
.
getFallbackFactory
();
if
(
fallbackFactory
!=
void
.
class
)
{
return
targetWithFallbackFactory
(
factory
.
getName
(),
context
,
target
,
builder
,
fallbackFactory
);
}
return
feign
.
target
(
target
);
}
private
<
T
>
T
targetWithFallbackFactory
(
String
feignClientName
,
FeignContext
context
,
Target
.
HardCodedTarget
<
T
>
target
,
HystrixFeign
.
Builder
builder
,
Class
<?>
fallbackFactoryClass
)
{
FallbackFactory
<?
extends
T
>
fallbackFactory
=
(
FallbackFactory
<?
extends
T
>)
getFromContext
(
"fallbackFactory"
,
feignClientName
,
context
,
fallbackFactoryClass
,
FallbackFactory
.
class
);
/* We take a sample fallback from the fallback factory to check if it returns a fallback
that is compatible with the annotated feign interface. */
Object
exampleFallback
=
fallbackFactory
.
create
(
new
RuntimeException
());
Assert
.
notNull
(
exampleFallback
,
String
.
format
(
"Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!"
,
feignClientName
));
if
(!
target
.
type
().
isAssignableFrom
(
exampleFallback
.
getClass
()))
{
throw
new
IllegalStateException
(
String
.
format
(
"Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'"
,
feignClientName
,
exampleFallback
.
getClass
(),
target
.
type
()));
}
return
builder
.
target
(
target
,
fallbackFactory
);
}
Object
fallbackInstance
=
context
.
getInstance
(
factory
.
getName
(),
factory
.
getFallback
());
private
<
T
>
T
targetWithFallback
(
String
feignClientName
,
FeignContext
context
,
Target
.
HardCodedTarget
<
T
>
target
,
HystrixFeign
.
Builder
builder
,
Class
<?>
fallback
)
{
T
fallbackInstance
=
getFromContext
(
"fallback"
,
feignClientName
,
context
,
fallback
,
target
.
type
());
return
builder
.
target
(
target
,
fallbackInstance
);
}
private
<
T
>
T
getFromContext
(
String
fallbackMechanism
,
String
feignClientName
,
FeignContext
context
,
Class
<?>
beanType
,
Class
<
T
>
targetType
)
{
Object
fallbackInstance
=
context
.
getInstance
(
feignClientName
,
beanType
);
if
(
fallbackInstance
==
null
)
{
throw
new
IllegalStateException
(
String
.
format
(
"No fallback
instance of type %s found for feign client %s"
,
factory
.
getFallback
(),
factory
.
getName
()
));
"No "
+
fallbackMechanism
+
"
instance of type %s found for feign client %s"
,
beanType
,
feignClientName
));
}
if
(!
target
.
type
().
isAssignableFrom
(
factory
.
getFallback
()
))
{
if
(!
target
Type
.
isAssignableFrom
(
beanType
))
{
throw
new
IllegalStateException
(
String
.
format
(
"Incompatible fallback instance. Fallback
of type %s is not assignable to %s for feign client %s"
,
factory
.
getFallback
(),
target
.
type
(),
factory
.
getName
()
));
"Incompatible "
+
fallbackMechanism
+
" instance. Fallback/fallbackFactory
of type %s is not assignable to %s for feign client %s"
,
beanType
,
targetType
,
feignClientName
));
}
feign
.
hystrix
.
HystrixFeign
.
Builder
builder
=
(
feign
.
hystrix
.
HystrixFeign
.
Builder
)
feign
;
return
builder
.
target
(
target
,
(
T
)
fallbackInstance
);
return
(
T
)
fallbackInstance
;
}
}
spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/invalid/FeignClientValidationTests.java
View file @
8e18c3d3
...
...
@@ -16,6 +16,7 @@
package
org
.
springframework
.
cloud
.
netflix
.
feign
.
invalid
;
import
feign.hystrix.FallbackFactory
;
import
org.junit.Rule
;
import
org.junit.Test
;
import
org.junit.rules.ExpectedException
;
...
...
@@ -42,10 +43,7 @@ public class FeignClientValidationTests {
@Test
public
void
testNameAndValue
()
{
this
.
expected
.
expectMessage
(
"only one is permitted"
);
AnnotationConfigApplicationContext
context
=
new
AnnotationConfigApplicationContext
(
NameAndValueConfiguration
.
class
);
assertNotNull
(
context
.
getBean
(
NameAndValueConfiguration
.
Client
.
class
));
context
.
close
();
new
AnnotationConfigApplicationContext
(
NameAndValueConfiguration
.
class
);
}
@Configuration
...
...
@@ -86,10 +84,7 @@ public class FeignClientValidationTests {
@Test
public
void
testNotLegalHostname
()
{
this
.
expected
.
expectMessage
(
"not legal hostname (foo_bar)"
);
AnnotationConfigApplicationContext
context
=
new
AnnotationConfigApplicationContext
(
BadHostnameConfiguration
.
class
);
assertNotNull
(
context
.
getBean
(
BadHostnameConfiguration
.
Client
.
class
));
context
.
close
();
new
AnnotationConfigApplicationContext
(
BadHostnameConfiguration
.
class
);
}
@Configuration
...
...
@@ -107,11 +102,12 @@ public class FeignClientValidationTests {
@Test
public
void
testMissingFallback
()
{
t
his
.
expected
.
expectMessage
(
"No fallback instance of type"
);
t
ry
(
AnnotationConfigApplicationContext
context
=
new
AnnotationConfigApplicationContext
(
MissingFallbackConfiguration
.
class
);
assertNotNull
(
context
.
getBean
(
MissingFallbackConfiguration
.
Client
.
class
));
context
.
close
();
MissingFallbackConfiguration
.
class
))
{
this
.
expected
.
expectMessage
(
"No fallback instance of type"
);
assertNotNull
(
context
.
getBean
(
MissingFallbackConfiguration
.
Client
.
class
));
}
}
@Configuration
...
...
@@ -136,11 +132,11 @@ public class FeignClientValidationTests {
@Test
public
void
testWrongFallbackType
()
{
t
his
.
expected
.
expectMessage
(
"Incompatible fallback instance"
);
AnnotationConfigApplicationContext
context
=
new
AnnotationConfigApplicationContext
(
WrongFallbackTypeConfiguration
.
class
);
assertNotNull
(
context
.
getBean
(
WrongFallbackTypeConfiguration
.
Client
.
class
));
context
.
close
();
t
ry
(
AnnotationConfigApplicationContext
context
=
new
AnnotationConfigApplicationContext
(
WrongFallbackTypeConfiguration
.
class
))
{
this
.
expected
.
expectMessage
(
"Incompatible fallback instance"
);
assertNotNull
(
context
.
getBean
(
WrongFallbackTypeConfiguration
.
Client
.
class
));
}
}
@Configuration
...
...
@@ -163,4 +159,98 @@ public class FeignClientValidationTests {
}
}
@Test
public
void
testMissingFallbackFactory
()
{
try
(
AnnotationConfigApplicationContext
context
=
new
AnnotationConfigApplicationContext
(
MissingFallbackFactoryConfiguration
.
class
))
{
this
.
expected
.
expectMessage
(
"No fallbackFactory instance of type"
);
assertNotNull
(
context
.
getBean
(
MissingFallbackFactoryConfiguration
.
Client
.
class
));
}
}
@Configuration
@Import
(
FeignAutoConfiguration
.
class
)
@EnableFeignClients
(
clients
=
MissingFallbackFactoryConfiguration
.
Client
.
class
)
protected
static
class
MissingFallbackFactoryConfiguration
{
@FeignClient
(
name
=
"foobar"
,
url
=
"http://localhost"
,
fallbackFactory
=
ClientFallback
.
class
)
interface
Client
{
@RequestMapping
(
method
=
RequestMethod
.
GET
,
value
=
"/"
)
String
get
();
}
class
ClientFallback
implements
FallbackFactory
<
Client
>
{
@Override
public
Client
create
(
Throwable
cause
)
{
return
null
;
}
}
}
@Test
public
void
testWrongFallbackFactoryType
()
{
try
(
AnnotationConfigApplicationContext
context
=
new
AnnotationConfigApplicationContext
(
WrongFallbackFactoryTypeConfiguration
.
class
))
{
this
.
expected
.
expectMessage
(
"Incompatible fallbackFactory instance"
);
assertNotNull
(
context
.
getBean
(
WrongFallbackFactoryTypeConfiguration
.
Client
.
class
));
}
}
@Configuration
@Import
(
FeignAutoConfiguration
.
class
)
@EnableFeignClients
(
clients
=
WrongFallbackFactoryTypeConfiguration
.
Client
.
class
)
protected
static
class
WrongFallbackFactoryTypeConfiguration
{
@FeignClient
(
name
=
"foobar"
,
url
=
"http://localhost"
,
fallbackFactory
=
Dummy
.
class
)
interface
Client
{
@RequestMapping
(
method
=
RequestMethod
.
GET
,
value
=
"/"
)
String
get
();
}
@Bean
Dummy
dummy
()
{
return
new
Dummy
();
}
class
Dummy
{
}
}
@Test
public
void
testWrongFallbackFactoryGenericType
()
{
try
(
AnnotationConfigApplicationContext
context
=
new
AnnotationConfigApplicationContext
(
WrongFallbackFactoryGenericTypeConfiguration
.
class
))
{
this
.
expected
.
expectMessage
(
"Incompatible fallbackFactory instance"
);
assertNotNull
(
context
.
getBean
(
WrongFallbackFactoryGenericTypeConfiguration
.
Client
.
class
));
}
}
@Configuration
@Import
(
FeignAutoConfiguration
.
class
)
@EnableFeignClients
(
clients
=
WrongFallbackFactoryGenericTypeConfiguration
.
Client
.
class
)
protected
static
class
WrongFallbackFactoryGenericTypeConfiguration
{
@FeignClient
(
name
=
"foobar"
,
url
=
"http://localhost"
,
fallbackFactory
=
ClientFallback
.
class
)
interface
Client
{
@RequestMapping
(
method
=
RequestMethod
.
GET
,
value
=
"/"
)
String
get
();
}
@Bean
ClientFallback
dummy
()
{
return
new
ClientFallback
();
}
class
ClientFallback
implements
FallbackFactory
<
String
>
{
@Override
public
String
create
(
Throwable
cause
)
{
return
"tryinToTrickYa"
;
}
}
}
}
spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientTests.java
View file @
8e18c3d3
...
...
@@ -74,6 +74,7 @@ import feign.Client;
import
feign.Logger
;
import
feign.RequestInterceptor
;
import
feign.RequestTemplate
;
import
feign.hystrix.FallbackFactory
;
import
lombok.AllArgsConstructor
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
...
...
@@ -116,6 +117,9 @@ public class FeignClientTests {
HystrixClient
hystrixClient
;
@Autowired
private
HystrixClientWithFallBackFactory
hystrixClientWithFallBackFactory
;
@Autowired
@Qualifier
(
"localapp3FeignClient"
)
HystrixClient
namedHystrixClient
;
...
...
@@ -237,6 +241,27 @@ public class FeignClientTests {
Future
<
Hello
>
failFuture
();
}
@FeignClient
(
name
=
"localapp4"
,
fallbackFactory
=
HystrixClientFallbackFactory
.
class
)
protected
interface
HystrixClientWithFallBackFactory
{
@RequestMapping
(
method
=
RequestMethod
.
GET
,
path
=
"/fail"
)
Hello
fail
();
}
static
class
HystrixClientFallbackFactory
implements
FallbackFactory
<
HystrixClientWithFallBackFactory
>
{
@Override
public
HystrixClientWithFallBackFactory
create
(
final
Throwable
cause
)
{
return
new
HystrixClientWithFallBackFactory
()
{
@Override
public
Hello
fail
()
{
assertNotNull
(
"Cause was null"
,
cause
);
return
new
Hello
(
"Hello from the fallback side: "
+
cause
.
getMessage
());
}
};
}
}
static
class
HystrixClientFallback
implements
HystrixClient
{
@Override
public
Hello
fail
()
{
...
...
@@ -268,13 +293,15 @@ public class FeignClientTests {
@EnableAutoConfiguration
@RestController
@EnableFeignClients
(
clients
=
{
TestClientServiceId
.
class
,
TestClient
.
class
,
DecodingTestClient
.
class
,
HystrixClient
.
class
},
defaultConfiguration
=
TestDefaultFeignConfig
.
class
)
DecodingTestClient
.
class
,
HystrixClient
.
class
,
HystrixClientWithFallBackFactory
.
class
},
defaultConfiguration
=
TestDefaultFeignConfig
.
class
)
@RibbonClients
({
@RibbonClient
(
name
=
"localapp"
,
configuration
=
LocalRibbonClientConfiguration
.
class
),
@RibbonClient
(
name
=
"localapp1"
,
configuration
=
LocalRibbonClientConfiguration
.
class
),
@RibbonClient
(
name
=
"localapp2"
,
configuration
=
LocalRibbonClientConfiguration
.
class
),
@RibbonClient
(
name
=
"localapp3"
,
configuration
=
LocalRibbonClientConfiguration
.
class
),
})
@RibbonClient
(
name
=
"localapp3"
,
configuration
=
LocalRibbonClientConfiguration
.
class
),
@RibbonClient
(
name
=
"localapp4"
,
configuration
=
LocalRibbonClientConfiguration
.
class
)
})
protected
static
class
Application
{
// needs to be in parent context to test multiple HystrixClient beans
...
...
@@ -284,6 +311,11 @@ public class FeignClientTests {
}
@Bean
public
HystrixClientFallbackFactory
hystrixClientFallbackFactory
()
{
return
new
HystrixClientFallbackFactory
();
}
@Bean
FeignFormatterRegistrar
feignFormatterRegistrar
()
{
return
new
FeignFormatterRegistrar
()
{
...
...
@@ -585,6 +617,15 @@ public class FeignClientTests {
}
@Test
public
void
testHystrixClientWithFallBackFactory
()
throws
Exception
{
Hello
hello
=
hystrixClientWithFallBackFactory
.
fail
();
assertNotNull
(
"hello was null"
,
hello
);
assertNotNull
(
"hello#message was null"
,
hello
.
getMessage
());
assertTrue
(
"hello#message did not contain the cause (status code) of the fallback invocation"
,
hello
.
getMessage
().
contains
(
"500"
));
}
@Test
public
void
namedFeignClientWorks
()
{
assertNotNull
(
"namedHystrixClient was null"
,
this
.
namedHystrixClient
);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment