1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.springframework.cloud.openfeign;
17
18 import io.github.rose.feign.FeignAutoConfiguration;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21 import org.springframework.beans.factory.BeanClassLoaderAware;
22 import org.springframework.beans.factory.config.BeanDefinitionHolder;
23 import org.springframework.beans.factory.support.AbstractBeanDefinition;
24 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
25 import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
26 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
27 import org.springframework.boot.context.annotation.ImportCandidates;
28 import org.springframework.context.EnvironmentAware;
29 import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
30 import org.springframework.core.annotation.AnnotatedElementUtils;
31 import org.springframework.core.annotation.AnnotationAttributes;
32 import org.springframework.core.env.Environment;
33 import org.springframework.core.io.support.SpringFactoriesLoader;
34 import org.springframework.core.type.AnnotationMetadata;
35 import org.springframework.lang.Nullable;
36 import org.springframework.util.StringUtils;
37
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Map;
41
42
43
44
45
46 public class AutoFeignClientsRegistrar
47 implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, EnvironmentAware {
48 private static final Logger log = LoggerFactory.getLogger(AutoFeignClientsRegistrar.class);
49
50 private static final String BASE_URL = "http://127.0.0.1:${server.port}${server.servlet.context-path}";
51
52 private ClassLoader beanClassLoader;
53
54 private Environment environment;
55
56 public ClassLoader getBeanClassLoader() {
57 return beanClassLoader;
58 }
59
60 @Override
61 public void setBeanClassLoader(ClassLoader classLoader) {
62 this.beanClassLoader = classLoader;
63 }
64
65 public Environment getEnvironment() {
66 return environment;
67 }
68
69 @Override
70 public void setEnvironment(Environment environment) {
71 this.environment = environment;
72 }
73
74 @Override
75 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
76 registerFeignClients(registry);
77 }
78
79 private void registerFeignClients(BeanDefinitionRegistry registry) {
80
81 List<String> feignClients = new ArrayList<>(
82 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
83
84
85 ImportCandidates.load(FeignClient.class, getBeanClassLoader()).forEach(feignClients::add);
86
87 if (feignClients.isEmpty()) {
88 return;
89 }
90 for (String className : feignClients) {
91 try {
92 Class<?> clazz = beanClassLoader.loadClass(className);
93 AnnotationAttributes attributes =
94 AnnotatedElementUtils.getMergedAnnotationAttributes(clazz, FeignClient.class);
95 if (attributes == null) {
96 continue;
97 }
98
99
100 Boolean isMicro = environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true);
101
102 if (registry.containsBeanDefinition(className) && isMicro) {
103 continue;
104 }
105
106 registerClientConfiguration(registry, getClientName(attributes), attributes.get("configuration"));
107
108 validate(attributes);
109 BeanDefinitionBuilder definition =
110 BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
111 definition.addPropertyValue("url", getUrl(registry, attributes));
112 definition.addPropertyValue("path", getPath(attributes));
113 String name = getName(attributes);
114 definition.addPropertyValue("name", name);
115
116
117 StringBuilder aliasBuilder = new StringBuilder(18);
118 if (attributes.containsKey("contextId")) {
119 String contextId = getContextId(attributes);
120 aliasBuilder.append(contextId);
121 definition.addPropertyValue("contextId", contextId);
122 } else {
123 aliasBuilder.append(name);
124 }
125
126 definition.addPropertyValue("type", className);
127 definition.addPropertyValue("decode404", attributes.get("decode404"));
128 definition.addPropertyValue("fallback", attributes.get("fallback"));
129 definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
130 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
131
132 AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
133
134
135 String alias = aliasBuilder.append("FeignClient").toString();
136
137
138 boolean primary = (Boolean) attributes.get("primary");
139
140 beanDefinition.setPrimary(primary);
141
142 String qualifier = getQualifier(attributes);
143 if (StringUtils.hasText(qualifier)) {
144 alias = qualifier;
145 }
146
147 BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
148 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
149 } catch (ClassNotFoundException e) {
150 log.error("ClassNotFound", e);
151 }
152 }
153 }
154
155
156
157
158
159
160
161 private Class<?> getSpringFactoriesLoaderFactoryClass() {
162 return FeignAutoConfiguration.class;
163 }
164
165 private void validate(Map<String, Object> attributes) {
166 AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
167
168 FeignClientsRegistrar.validateFallback(annotation.getClass("fallback"));
169 FeignClientsRegistrar.validateFallbackFactory(annotation.getClass("fallbackFactory"));
170 }
171
172 private String getName(Map<String, Object> attributes) {
173 String name = (String) attributes.get("serviceId");
174 if (!StringUtils.hasText(name)) {
175 name = (String) attributes.get("name");
176 }
177 if (!StringUtils.hasText(name)) {
178 name = (String) attributes.get("value");
179 }
180 name = resolve(name);
181 return FeignClientsRegistrar.getName(name);
182 }
183
184 private String getContextId(Map<String, Object> attributes) {
185 String contextId = (String) attributes.get("contextId");
186 if (!StringUtils.hasText(contextId)) {
187 return getName(attributes);
188 }
189
190 contextId = resolve(contextId);
191 return FeignClientsRegistrar.getName(contextId);
192 }
193
194 private String resolve(String value) {
195 if (StringUtils.hasText(value)) {
196 return this.environment.resolvePlaceholders(value);
197 }
198 return value;
199 }
200
201 private String getUrl(BeanDefinitionRegistry registry, Map<String, Object> attributes) {
202
203
204 Boolean isMicro = environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true);
205
206 if (isMicro) {
207 return null;
208 }
209
210 String url = resolve(BASE_URL);
211
212 return FeignClientsRegistrar.getUrl(url);
213 }
214
215 private String getPath(Map<String, Object> attributes) {
216 String path = resolve((String) attributes.get("path"));
217 return FeignClientsRegistrar.getPath(path);
218 }
219
220 @Nullable
221 private String getQualifier(@Nullable Map<String, Object> client) {
222 if (client == null) {
223 return null;
224 }
225 String qualifier = (String) client.get("qualifier");
226 if (StringUtils.hasText(qualifier)) {
227 return qualifier;
228 }
229 return null;
230 }
231
232 @Nullable
233 private String getClientName(@Nullable Map<String, Object> client) {
234 if (client == null) {
235 return null;
236 }
237 String value = (String) client.get("contextId");
238 if (!StringUtils.hasText(value)) {
239 value = (String) client.get("value");
240 }
241 if (!StringUtils.hasText(value)) {
242 value = (String) client.get("name");
243 }
244 if (!StringUtils.hasText(value)) {
245 value = (String) client.get("serviceId");
246 }
247 if (StringUtils.hasText(value)) {
248 return value;
249 }
250
251 throw new IllegalStateException(
252 "Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
253 }
254
255 private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
256 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
257 builder.addConstructorArgValue(name);
258 builder.addConstructorArgValue(configuration);
259 registry.registerBeanDefinition(
260 name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
261 }
262 }