参考Feign实现一个http-client

words: 3.4k    time: 18min    views:

一个Java Http调用客户端,基于netflix feign的实现思路,使用apache的httpclient进行调用

https://github.com/cowave5/http-client

Feign的历史

Feign是Netflix发布的一款轻量级Rest客户端框架,提供了一种声明式的服务调用组件,通过Feign只需声明一个接口并通过注解进行简单的配置即可实现对Http接口的绑定。然后就可以像调用本地方法一样来调用远程服务,而完全感觉不到是在进行远程调用

Feign支持多种注解,比如Feign自带的注解以及JAX-RS注解,但遗憾的是Feign本身并不支持Spring mvc注解,这无疑会给广大Spring用户带来不便。于是Spring Cloud又对Feign进行了封装,使其方便集成到Spring项目中,并添加了对Spring mvc注解的支持,以及简化与负载均衡、服务注册发现、断路器等组件的集成,所以集成之后又叫Spring Cloud Feign

  • 2013年6月,Netflix Feign发布了第一个版本:1.0.0
  • 2015年3月,spring-cloud-starter-feign发布版本:1.0.0.RELEASE
  • 2016年7月,Netflix Feign发布最后一个版本:8.18.0

  • 2016年,     Netflix将Feign捐献给社区,并改名为OpenFeign
  • 2016年7月,OpenFeign发布首个版本:9.0.0,之后由社区维护一直持续发布
  • 2016年9月,spring-cloud-starter-feign将依赖从Netflix Feign改为OpenFeign,并发布了1.2.0.RELEASE
  • 2017年11月,Spring Cloud团队将两个”feign starter”进行合并,发布了spring-cloud-starter-openfeign的首个版本:1.4.0.RELEASE,以及spring-cloud-starter-feign的1.4.0.RELEASE版本
  • 2019年5月,spring-cloud-starter-feign发布了最后一个换皮版本:1.4.7.RELEASE,至此,过渡期结束

Feign的实现

Feign的实现是典型的动态代理模式,这里选择在Netflix Feign 8.18.0版本上进行改造,这样可以按照自己的意愿来进行Http远程服务调用,也方便自由修改,基本还是参考Feign原来的思路,主要简化了一些类的实现,换成使用HttpClient进行调用(原实现使用的Jdk提供的HttpURLConnection,不支持Patch方法),具体的类图可以参考下面UML:

从UML可以看出来,ProxyFactory之前只是做一些构建准备工作,将被代理的内容打包成ProxyTarget,创建一个HttpClientExecutor实例给后面用;

ProxyFactory中会进行真正的代理创建,先委托HttpMethodInvokerFactory将ProxyTarget中的目标方法翻译成对应的HttpMethodInvoker集合;然后交给新建的ProxyInvoker实例,其实现了Jdk的InvocationHandler接口,最终可以用来创建Proxy动态代理实例

ProxyFactory

ProxyFactory的任务就是将ProxyTarget中的目标方法列表转化成HttpMethodInvoker集合,然后包装成ProxyInvoker,并最终用来创建Proxy实例

https://github.com/cowave5/http-client/blob/master/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ProxyFactory {

private final HttpMethodInvokerFactory httpMethodInvokerFactory;

public ProxyFactory(HttpMethodInvokerFactory httpMethodInvokerFactory) {
this.httpMethodInvokerFactory = httpMethodInvokerFactory;
}

@SuppressWarnings("unchecked")
public <T> T newProxy(ProxyTarget<T> proxyTarget) throws UnsupportedEncodingException {
// 默认方法
List<DefaultMethodInvoker> defaultProxyInvokerList = new LinkedList<>();

// Http调用方法
Map<String, MethodInvoker> httpProxyInvokerMap = httpMethodInvokerFactory.create(proxyTarget);

Map<Method, MethodInvoker> methodInvokeHandlerMap = new LinkedHashMap<>();
for (Method method : proxyTarget.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
// object方法不进行代理
} else if (HttpMethodMetaParser.isDefault(method)) {
DefaultMethodInvoker handler = new DefaultMethodInvoker(method);
defaultProxyInvokerList.add(handler);
methodInvokeHandlerMap.put(method, handler);
} else {
methodInvokeHandlerMap.put(
method, httpProxyInvokerMap.get(HttpMethodMetaParser.methodKey(proxyTarget.type(), method)));
}
}

// 创建代理
T proxy = (T) Proxy.newProxyInstance(
proxyTarget.type().getClassLoader(),
new Class<?>[]{proxyTarget.type()},
new ProxyInvoker(proxyTarget, methodInvokeHandlerMap));
for (DefaultMethodInvoker methodHandler : defaultProxyInvokerList) {
methodHandler.bindTo(proxy);
}
return proxy;
}
}

HttpMethodInvokerFactory

HttpMethodInvokerFactory的任务就是帮助ProxyFactory将目标方法转化成HttpMethodInvoker,然后它会依赖HttpMethodMetaParser来进行目标解析

https://github.com/cowave5/http-client/blob/master/src/main/java/com/cowave/commons/client/http/invoke/proxy/HttpMethodInvokerFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class HttpMethodInvokerFactory {
private final HttpExecutor httpExecutor;
private final HttpMethodMetaParser metaParser;
private final HttpEncoder encoder;
private final HttpDecoder decoder;
private final Options options;
private final List<HttpClientInterceptor> httpClientInterceptors;
private final HttpExceptionHandler exceptionHandler;

private final boolean ignoreError;
private final Level level;

public Map<String, MethodInvoker> create(ProxyTarget<?> proxyTarget) throws UnsupportedEncodingException {

List<HttpMethodMeta> metaList = metaParser.parse(proxyTarget.type());

Map<String, MethodInvoker> result = new LinkedHashMap<>();
for (HttpMethodMeta meta : metaList) {
HttpRequestFactory httpRequestFactory;
if (meta.getMultipartFileIndex() != null
|| meta.getMultipartFormIndex() != null || !meta.getMultipartParams().isEmpty()) {
// multipart/form-data
httpRequestFactory = new MultipartRequestFactory(meta);
} else if (meta.getBodyIndex() != null) {
// 存在body
httpRequestFactory = new BodyRequestFactory(meta, encoder);
} else {
httpRequestFactory = new HttpRequestFactory(meta);
}

// <methodKey, MethodHandler>
result.put(meta.getMethodKey(),
new HttpMethodInvoker(proxyTarget, meta, httpRequestFactory,
httpExecutor, httpClientInterceptors, options, decoder, level, ignoreError, exceptionHandler));
}
return result;
}
}

ProxyInvoker

到了ProxyInvoker就是具体的方法调用了,如果通过Proxy代理实例的调用的话,就会将目标方法调用转成对应的Http远程调用了

https://github.com/cowave5/http-client/blob/master/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyInvoker.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ProxyInvoker implements InvocationHandler {

@SuppressWarnings("rawtypes")
private final ProxyTarget proxyTarget;

private final Map<Method, MethodInvoker> methodInvokeHandlerMap;

@SuppressWarnings("rawtypes")
ProxyInvoker(ProxyTarget proxyTarget, Map<Method, MethodInvoker> methodInvokeHandlerMap) {
this.proxyTarget = proxyTarget;
this.methodInvokeHandlerMap = methodInvokeHandlerMap;
}

@Override
public Object invoke(Object proxy, Method method, 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();
}
return methodInvokeHandlerMap.get(method).invoke(args);
}

// … …
}

JDK URL调用

上面最终的HttpMethodInvoker中会使用HttpClient进行远程调用就不赘述了,这里看一下Feign原来是如何使用Jdk原生URLConnection进行Http调用的

它会将调用的内容打包成一个Request实例交给Client,然后Client用这个Request构造一个 HttpURLConnection 来进行连接,并发送和接收消息

feign.Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public static class Default implements Client {

// ...

@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection);
}

HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
final HttpURLConnection connection = (HttpURLConnection) new URL(request.url()).openConnection(); // 创建连接实例
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection sslCon = (HttpsURLConnection) connection;
if (sslContextFactory != null) {
sslCon.setSSLSocketFactory(sslContextFactory);
}
if (hostnameVerifier != null) {
sslCon.setHostnameVerifier(hostnameVerifier);
}
}
connection.setConnectTimeout(options.connectTimeoutMillis()); // 设置连接超时
connection.setReadTimeout(options.readTimeoutMillis()); // 设置响应超时
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod(request.method());

Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean gzipEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
boolean deflateEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);
boolean hasAcceptHeader = false;
Integer contentLength = null;
for (String field : request.headers().keySet()) {
if (field.equalsIgnoreCase("Accept")) {
hasAcceptHeader = true;
}
for (String value : request.headers().get(field)) {
if (field.equals(CONTENT_LENGTH)) {
if (!gzipEncodedRequest && !deflateEncodedRequest) {
contentLength = Integer.valueOf(value);
connection.addRequestProperty(field, value);
}
} else {
connection.addRequestProperty(field, value);
}
}
}
// Some servers choke on the default accept string.
if (!hasAcceptHeader) {
connection.addRequestProperty("Accept", "*/*");
}

if (request.body() != null) {
if (contentLength != null) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(8196);
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream(); // 进行连接,并读取响应
if (gzipEncodedRequest) {
out = new GZIPOutputStream(out);
} else if (deflateEncodedRequest) {
out = new DeflaterOutputStream(out);
}
try {
out.write(request.body());
} finally {
try {
out.close();
} catch (IOException suppressed) { // NOPMD
}
}
}
return connection;
}
}

HttpURLConnection

在URL的构造器中会解析出协议protocol,设置协议对应的 URLStreamHandler,它其实是各种协议连接的一个抽象工厂

java.net.URL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException{
String original = spec;
int i, limit, c;
int start = 0;
String newProtocol = null;
boolean aRef=false;
boolean isRelative = false;

// Check for permission to specify a handler
if (handler != null) {
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkSpecifyHandler(sm);
}
}

try {
limit = spec.length();
while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
limit--; //eliminate trailing whitespace
}
while ((start < limit) && (spec.charAt(start) <= ' ')) {
start++; // eliminate leading whitespace
}

if (spec.regionMatches(true, start, "url:", 0, 4)) {
start += 4;
}
if (start < spec.length() && spec.charAt(start) == '#') {
/* we're assuming this is a ref relative to the context URL.
* This means protocols cannot start w/ '#', but we must parse
* ref URL's like: "hello:there" w/ a ':' in them.
*/
aRef=true;
}
for (i = start ; !aRef && (i < limit) && ((c = spec.charAt(i)) != '/') ; i++) {
if (c == ':') {
String s = toLowerCase(spec.substring(start, i));
if (isValidProtocol(s)) {
newProtocol = s;
start = i + 1;
}
break;
}
}

// Only use our context if the protocols match.
protocol = newProtocol;
if ((context != null) && ((newProtocol == null) || newProtocol.equalsIgnoreCase(context.protocol))) {
// inherit the protocol handler from the context
// if not specified to the constructor
if (handler == null) {
handler = context.handler;
}

// If the context is a hierarchical URL scheme and the spec
// contains a matching scheme then maintain backwards
// compatibility and treat it as if the spec didn't contain
// the scheme; see 5.2.3 of RFC2396
if (context.path != null && context.path.startsWith("/"))
newProtocol = null;

if (newProtocol == null) {
protocol = context.protocol;
authority = context.authority;
userInfo = context.userInfo;
host = context.host;
port = context.port;
file = context.file;
path = context.path;
isRelative = true;
}
}

if (protocol == null) {
throw new MalformedURLException("no protocol: "+original);
}

// Get the protocol handler if not specified or the protocol
// of the context could not be used
if (handler == null && (handler = getURLStreamHandler(protocol)) == null) {
throw new MalformedURLException("unknown protocol: "+protocol);
}
this.handler = handler;

i = spec.indexOf('#', start);
if (i >= 0) {
ref = spec.substring(i + 1, limit);
limit = i;
}

/*
* Handle special case inheritance of query and fragment
* implied by RFC2396 section 5.2.2.
*/
if (isRelative && start == limit) {
query = context.query;
if (ref == null) {
ref = context.ref;
}
}
handler.parseURL(this, spec, start, limit);
} catch(MalformedURLException e) {
throw e;
} catch(Exception e) {
MalformedURLException exception = new MalformedURLException(e.getMessage());
exception.initCause(e);
throw exception;
}
}
  • 然后调用openConnection的话,就是使用URLStreamHandler来创建对应的URLConnection了
java.net.URL
1
2
3
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}

不过这里只是创建了一个连接实例,真正进行连接还需要主动调用connect()

Returns a URLConnection instance that represents a connection to the remote object referred to by the URL.
A new instance of URLConnection is created every time when invoking the URLStreamHandler.openConnection(URL) method of the protocol handler for this URL.
It should be noted that a URLConnection instance does not establish the actual network connection on creation. This will happen only when calling URLConnection.connect().
If for the URL’s protocol (such as HTTP or JAR), there exists a public, specialized URLConnection subclass belonging to one of the following packages or one of their subpackages: java.lang, java.io, java.util, java.net, the connection returned will be of that subclass. For example, for HTTP an HttpURLConnection will be returned, and for JAR a JarURLConnection will be returned.

具体连接时,它会先将状态置为连接中connecting,然后再进行连接,当然连接之前它还会检查下是否已经connected

sun.net.www.protocol.http.HttpURLConnection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public void connect() throws IOException {
lock();
try {
connecting = true;
} finally {
unlock();
}
plainConnect();
}

protected void plainConnect() throws IOException {
lock();
try {
if (connected) {
return;
}
} finally {
unlock();
}
SocketPermission p = URLtoSocketPermission(this.url);
if (p != null) {
try {
AccessController.doPrivilegedWithCombiner(
new PrivilegedExceptionAction<>() {
public Void run() throws IOException {
plainConnect0();
return null;
}
}, null, p
);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
} else {
// run without additional permission
plainConnect0();
}
}

plainConnect0 中会新建HttpClient实例,并将真正的建立连接工作委托给它,到这里基本就交给sun提供的网络工具包了

sun.net.www.http.HttpClient
1
2
3
4
5
6
7
8
9
10
11
12
13
protected HttpClient(URL url, Proxy p, int to) throws IOException {
proxy = (p == null) ? Proxy.NO_PROXY : p;
this.host = url.getHost();
this.url = url;
port = url.getPort();
if (port == -1) {
port = getDefaultPort();
}
setConnectTimeout(to);

capture = HttpCapture.getCapture(url);
openServer();
}

最终还是创建Socket实例进行连接,到这里也可以看到connectTimeoutreadTimeout的最终设置了

sun.net.NetworkClient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
protected Socket serverSocket = null;

public void openServer(String server, int port) throws IOException, UnknownHostException {
if (serverSocket != null)
closeServer();
serverSocket = doConnect (server, port);
try {
serverOutput = new PrintStream(new BufferedOutputStream(serverSocket.getOutputStream()), true, encoding);
} catch (UnsupportedEncodingException e) {
throw new InternalError(encoding +"encoding not found", e);
}
serverInput = new BufferedInputStream(serverSocket.getInputStream());
}

protected Socket doConnect (String server, int port) throws IOException, UnknownHostException {
Socket s;
if (proxy != null) {
if (proxy.type() == Proxy.Type.SOCKS) {
s = AccessController.doPrivileged(
new PrivilegedAction<>() {
public Socket run() {
return new Socket(proxy);
}
});
} else if (proxy.type() == Proxy.Type.DIRECT) {
s = createSocket();
} else {
// Still connecting through a proxy
// server & port will be the proxy address and port
s = new Socket(Proxy.NO_PROXY);
}
} else {
s = createSocket();
}

// Instance specific timeouts do have priority, that means
// connectTimeout & readTimeout (-1 means not set)
// Then global default timeouts
// Then no timeout.
if (connectTimeout >= 0) {
s.connect(new InetSocketAddress(server, port), connectTimeout);
} else {
if (defaultConnectTimeout > 0) {
s.connect(new InetSocketAddress(server, port), defaultConnectTimeout);
} else {
s.connect(new InetSocketAddress(server, port));
}
}
if (readTimeout >= 0)
s.setSoTimeout(readTimeout);
else if (defaultSoTimeout > 0) {
s.setSoTimeout(defaultSoTimeout);
}
return s;
}

不支持Patch方法

这里不明白为什么Jdk的HttpURLConnection不支持Patch,而且写死了,好像也不打算修改了,也可能是真正使用这个Api的人实在太少了

java.net.HttpURLConnection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static final String[] methods = {
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};

public void setRequestMethod(String method) throws ProtocolException {
if (connected) {
throw new ProtocolException("Can't reset method: already connected");
}
// This restriction will prevent people from using this class to
// experiment w/ new HTTP methods using java. But it should
// be placed for security - the request String could be
// arbitrarily long.

for (int i = 0; i < methods.length; i++) {
if (methods[i].equals(method)) {
if (method.equals("TRACE")) {
SecurityManager s = System.getSecurityManager();
if (s != null) {
s.checkPermission(new NetPermission("allowHttpTrace"));
}
}
this.method = method;
return;
}
}
throw new ProtocolException("Invalid HTTP method: " + method);
}

Spring加载

HttpClientBeanDefinitionRegistrar

这里也是通过ImportBeanDefinitionRegistrar进行引入的,但是不希望再手动指定一个scan路径,所以就直接默认就扫一下ComponentScan指定的路径,以及启动类下面的路径

https://github.com/cowave5/http-client/blob/master/src/main/java/com/cowave/commons/client/http/register/HttpClientBeanDefinitionRegistrar.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class HttpClientBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

@Override
public void registerBeanDefinitions(AnnotationMetadata meta, @NonNull BeanDefinitionRegistry registry) {
TreeSet<String> packageSet = new TreeSet<>();
String[] beanNames = registry.getBeanDefinitionNames();

// 省掉package指定扫描,就检查下启动类下的路径,以及@ComponentScan指定的路径
try {
for (String beanName : beanNames) {
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
String className = beanDefinition.getBeanClassName();
if (className != null) {
Class<?> clazz = Class.forName(className);

// 启动类路径
SpringBootApplication springBoot = clazz.getAnnotation(SpringBootApplication.class);
if (springBoot != null) {
packageSet.add(clazz.getPackage().getName());
}

// 扫描类路径
ComponentScan componentScan = clazz.getAnnotation(ComponentScan.class);
if (componentScan != null) {
String[] values = componentScan.value();
String[] basePackages = componentScan.basePackages();
packageSet.addAll(Arrays.asList(values));
packageSet.addAll(Arrays.asList(basePackages));
}
}
}
} catch (ClassNotFoundException e) {
throw new ApplicationContextException("", e);
}

List<String> packageList = new ArrayList<>(packageSet);

// 合并package
List<String> packages = new ArrayList<>();
for(int right = packageList.size() - 1; right >= 0; right--){
boolean hasPrefix = false;
for(int i = right - 1; i >= 0; i--){
if(packageList.get(right).startsWith(packageList.get(i) + ".")){
hasPrefix = true;
break;
}
}
if(hasPrefix){
continue;
}
packages.add(packageList.get(right));
}

// 扫描注册
for(String pack : packages){
Reflections reflections = new Reflections(pack);
for(Class<?> clazz : reflections.getTypesAnnotatedWith(HttpClient.class)){
HttpClient feign = AnnotationUtils.getAnnotation(clazz, HttpClient.class);
if(feign == null){
continue;
}

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
GenericBeanDefinition beanDefinition = (GenericBeanDefinition)builder.getBeanDefinition();
beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
beanDefinition.getPropertyValues().add("targetClass", clazz);
beanDefinition.setBeanClass(HttpClientFactoryBean.class);
registry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition);
}
}
}
}

HttpClientFactoryBean

扫描注入的是一个FactoryBean,然后实例化时再创建对应的代理实例

https://github.com/cowave5/http-client/blob/master/src/main/java/com/cowave/commons/client/http/register/HttpClientFactoryBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class HttpClientFactoryBean<T> implements FactoryBean<T>, EmbeddedValueResolverAware, ApplicationContextAware {

private ApplicationContext applicationContext;

private StringValueResolver valueResolver;

private Class<T> targetClass;

public HttpClientFactoryBean() {

}

@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

@Override
public void setEmbeddedValueResolver(@NonNull StringValueResolver valueResolver) {
this.valueResolver = valueResolver;
}

@Override
public T getObject() throws UnsupportedEncodingException {
HttpClient httpClient = AnnotationUtils.getAnnotation(targetClass, HttpClient.class);
return HttpProxyFactory.newProxy(targetClass, httpClient, applicationContext, valueResolver);
}

@Override
public Class<?> getObjectType() {
return targetClass;
}
}


参考: