Skip to content

Commit 2e22849

Browse files
committed
Use FactoryBean.
1 parent 124df57 commit 2e22849

File tree

7 files changed

+344
-119
lines changed

7 files changed

+344
-119
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.interfaceclients;
18+
19+
import org.springframework.beans.BeansException;
20+
import org.springframework.beans.factory.FactoryBean;
21+
import org.springframework.beans.factory.InitializingBean;
22+
import org.springframework.context.ApplicationContext;
23+
import org.springframework.context.ApplicationContextAware;
24+
import org.springframework.util.Assert;
25+
26+
import static com.mysema.commons.lang.Assert.assertThat;
27+
28+
/**
29+
* @author Olga Maciaszek-Sharma
30+
*/
31+
public abstract class AbstractInterfaceClientsFactoryBean
32+
implements FactoryBean<Object>, ApplicationContextAware, InitializingBean {
33+
34+
protected Class<?> type;
35+
36+
protected String beanName;
37+
38+
protected String clientId;
39+
40+
protected ApplicationContext applicationContext;
41+
42+
public AbstractInterfaceClientsFactoryBean() {
43+
}
44+
45+
// TODO
46+
@Override
47+
public Object getObject() throws Exception {
48+
throw new UnsupportedOperationException("Please, implement me.");
49+
}
50+
51+
@Override
52+
public void afterPropertiesSet() throws Exception {
53+
Assert.notNull(this.type, "Type must not be null");
54+
}
55+
56+
@Override
57+
public Class<?> getObjectType() {
58+
return this.type;
59+
}
60+
61+
@Override
62+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
63+
this.applicationContext = applicationContext;
64+
65+
}
66+
67+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/interfaceclients/AbstractInterfaceClientsImportRegistrar.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,109 @@
1717
package org.springframework.boot.autoconfigure.interfaceclients;
1818

1919
import java.lang.annotation.Annotation;
20+
import java.text.Normalizer;
2021
import java.util.HashSet;
2122
import java.util.List;
2223
import java.util.Set;
2324

25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
import org.apache.commons.text.CaseUtils;
28+
2429
import org.springframework.beans.factory.ListableBeanFactory;
2530
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
2631
import org.springframework.beans.factory.config.BeanDefinition;
32+
import org.springframework.beans.factory.config.BeanDefinitionHolder;
33+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
34+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
35+
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
36+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
37+
import org.springframework.beans.factory.support.BeanNameGenerator;
2738
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
2839
import org.springframework.context.EnvironmentAware;
2940
import org.springframework.context.ResourceLoaderAware;
3041
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
3142
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
43+
import org.springframework.core.annotation.MergedAnnotation;
3244
import org.springframework.core.env.Environment;
3345
import org.springframework.core.io.ResourceLoader;
46+
import org.springframework.core.type.AnnotationMetadata;
3447
import org.springframework.core.type.filter.AnnotationTypeFilter;
48+
import org.springframework.util.Assert;
49+
import org.springframework.util.ObjectUtils;
3550

3651
/**
3752
* @author Olga Maciaszek-Sharma
3853
*/
54+
// TODO: Handle AOT
3955
// TODO: remove abstract supertype or move to a shared package
4056
public abstract class AbstractInterfaceClientsImportRegistrar
4157
implements ImportBeanDefinitionRegistrar, EnvironmentAware, ResourceLoaderAware {
4258

59+
// TODO: work on IntelliJ plugin /other plugins/ to show that the client beans are
60+
// autoconfigured
61+
private static final Log logger = LogFactory.getLog(AbstractInterfaceClientsImportRegistrar.class);
62+
63+
private static final String INTERFACE_CLIENT_SUFFIX = "InterfaceClient";
64+
65+
private static final String BEAN_NAME_ATTRIBUTE_NAME = "beanName";
66+
67+
private static final String CLIENT_ID_ATTRIBUTE_NAME = "clientId";
68+
69+
private static final String BEAN_CLASS_ATTRIBUTE_NAME = "type";
70+
4371
private Environment environment;
4472

4573
private ResourceLoader resourceLoader;
4674

75+
@Override
76+
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
77+
BeanNameGenerator importBeanNameGenerator) {
78+
Assert.isInstanceOf(ListableBeanFactory.class, registry,
79+
"Registry must be an instance of " + ListableBeanFactory.class.getSimpleName());
80+
ListableBeanFactory beanFactory = (ListableBeanFactory) registry;
81+
Set<BeanDefinition> candidateComponents = discoverCandidateComponents(beanFactory);
82+
for (BeanDefinition candidateComponent : candidateComponents) {
83+
if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
84+
registerInterfaceClient(registry, beanDefinition);
85+
}
86+
}
87+
}
88+
89+
private void registerInterfaceClient(BeanDefinitionRegistry registry, AnnotatedBeanDefinition beanDefinition) {
90+
AnnotationMetadata annotatedBeanMetadata = beanDefinition.getMetadata();
91+
Assert.isTrue(annotatedBeanMetadata.isInterface(),
92+
getAnnotation().getSimpleName() + "can only be placed on an interface.");
93+
MergedAnnotation<? extends Annotation> annotation = annotatedBeanMetadata.getAnnotations().get(getAnnotation());
94+
String beanClassName = annotatedBeanMetadata.getClassName();
95+
// Class<?> beanClass;
96+
// try {
97+
// beanClass = Class.forName(beanClassName);
98+
// }
99+
// catch (ClassNotFoundException e) {
100+
// if (logger.isDebugEnabled()) {
101+
// logger.debug("Class not found for interface client " + beanClassName + ": " +
102+
// e.getMessage());
103+
// }
104+
// throw new RuntimeException(e);
105+
// }
106+
// TODO: consider naming conventions: value of the annotation is the
107+
// qualifier to look for related beans
108+
// TODO: while the actual beanName corresponds to the simple class name
109+
// suffixed with InterfaceClient
110+
String clientId = annotation.getString(MergedAnnotation.VALUE);
111+
String beanName = !ObjectUtils.isEmpty(annotation.getString(BEAN_NAME_ATTRIBUTE_NAME))
112+
? annotation.getString(BEAN_NAME_ATTRIBUTE_NAME) : buildBeanName(clientId);
113+
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(getFactoryBeanClass());
114+
definitionBuilder.addPropertyValue(BEAN_NAME_ATTRIBUTE_NAME, beanName);
115+
definitionBuilder.addPropertyValue(CLIENT_ID_ATTRIBUTE_NAME, clientId);
116+
definitionBuilder.addPropertyValue(BEAN_CLASS_ATTRIBUTE_NAME, beanClassName);
117+
definitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
118+
AbstractBeanDefinition definition = definitionBuilder.getBeanDefinition();
119+
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, beanName, new String[] { clientId });
120+
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
121+
}
122+
47123
protected Set<BeanDefinition> discoverCandidateComponents(ListableBeanFactory beanFactory) {
48124
Set<BeanDefinition> candidateComponents = new HashSet<>();
49125
ClassPathScanningCandidateComponentProvider scanner = getScanner();
@@ -83,4 +159,13 @@ public void setResourceLoader(ResourceLoader resourceLoader) {
83159

84160
protected abstract Class<? extends Annotation> getAnnotation();
85161

162+
protected abstract Class<?> getFactoryBeanClass();
163+
164+
private String buildBeanName(String clientId) {
165+
// TODO: research Normalizer form types
166+
String normalised = Normalizer.normalize(clientId, Normalizer.Form.NFD);
167+
String camelCased = CaseUtils.toCamelCase(normalised, false, '-', '_');
168+
return camelCased + INTERFACE_CLIENT_SUFFIX;
169+
}
170+
86171
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.interfaceclients.http;
18+
19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
21+
22+
import org.springframework.boot.autoconfigure.interfaceclients.AbstractInterfaceClientsFactoryBean;
23+
import org.springframework.web.service.invoker.HttpExchangeAdapter;
24+
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
25+
26+
/**
27+
* @author Olga Maciaszek-Sharma
28+
*/
29+
public abstract class AbstractHttpInterfaceClientsFactoryBean extends AbstractInterfaceClientsFactoryBean {
30+
31+
private static final Log logger = LogFactory.getLog(AbstractHttpInterfaceClientsFactoryBean.class);
32+
33+
@Override
34+
public Object getObject() throws Exception {
35+
HttpServiceProxyFactory proxyFactory = proxyFactory();
36+
return proxyFactory.createClient(this.type);
37+
}
38+
39+
private HttpServiceProxyFactory proxyFactory() {
40+
HttpServiceProxyFactory userProvidedProxyFactory = QualifiedBeanProvider.qualifiedBean(this.applicationContext,
41+
HttpServiceProxyFactory.class, this.clientId);
42+
if (userProvidedProxyFactory != null) {
43+
return userProvidedProxyFactory;
44+
}
45+
// create an HttpServiceProxyFactory bean with default implementation
46+
if (logger.isDebugEnabled()) {
47+
logger.debug("Creating HttpServiceProxyFactory for '" + this.clientId + "'");
48+
}
49+
HttpExchangeAdapter adapter = exchangeAdapter();
50+
return HttpServiceProxyFactory.builderFor(adapter).build();
51+
}
52+
53+
protected abstract HttpExchangeAdapter exchangeAdapter();
54+
55+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/interfaceclients/http/HttpInterfaceClientsAutoConfiguration.java

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -46,61 +46,59 @@
4646
*/
4747
@AutoConfiguration(after = { RestTemplateAutoConfiguration.class, RestClientAutoConfiguration.class,
4848
WebClientAutoConfiguration.class })
49-
@Import(HttpInterfaceClientsImportRegistrar.class)
5049
@EnableConfigurationProperties(HttpInterfaceClientsProperties.class)
5150
public class HttpInterfaceClientsAutoConfiguration {
5251

53-
@Bean
54-
HttpInterfaceClientsAdapter httpInterfaceClientAdapter(HttpExchangeAdapterProvider adapterProvider) {
55-
return new HttpInterfaceClientsAdapter(adapterProvider);
56-
}
52+
// @Bean
53+
// HttpInterfaceClientsAdapter httpInterfaceClientAdapter(HttpExchangeAdapterProvider
54+
// adapterProvider) {
55+
// return new HttpInterfaceClientsAdapter(adapterProvider);
56+
// }
5757

5858
@Configuration(proxyBeanMethods = false)
5959
@ConditionalOnClass({ RestClient.class, RestClientAdapter.class, HttpServiceProxyFactory.class })
6060
@Conditional(NotReactiveWebApplicationCondition.class)
6161
@ConditionalOnProperty(value = "spring.interfaceclients.resttemplate.enabled", havingValue = "false",
6262
matchIfMissing = true)
63+
@Import(RestClientInterfaceClientsImportRegistrar.class)
6364
protected static class RestClientAdapterProviderConfiguration {
6465

65-
@Bean
66-
@ConditionalOnBean(RestClient.Builder.class)
67-
@ConditionalOnMissingBean
68-
HttpExchangeAdapterProvider restClientAdapterProvider(RestClient.Builder restClientBuilder,
69-
ObjectProvider<HttpInterfaceClientsProperties> propertiesProvider) {
70-
return new RestClientAdapterProvider(restClientBuilder, propertiesProvider);
71-
}
72-
73-
}
74-
75-
@Configuration(proxyBeanMethods = false)
76-
@ConditionalOnClass({ RestTemplate.class, RestTemplateAdapter.class, HttpServiceProxyFactory.class })
77-
@Conditional(NotReactiveWebApplicationCondition.class)
78-
protected static class RestTemplateAdapterProviderConfiguration {
79-
80-
@Bean
81-
@ConditionalOnBean(RestTemplateBuilder.class)
82-
@ConditionalOnMissingBean
83-
@ConditionalOnProperty(value = "spring.interfaceclients.resttemplate.enabled", havingValue = "true")
84-
HttpExchangeAdapterProvider restTemplateAdapterProvider(RestTemplateBuilder restTemplateBuilder,
85-
ObjectProvider<HttpInterfaceClientsProperties> propertiesProvider) {
86-
return new RestTemplateAdapterProvider(restTemplateBuilder, propertiesProvider);
87-
}
88-
89-
}
90-
91-
@Configuration(proxyBeanMethods = false)
92-
@ConditionalOnClass({ WebClient.class, WebClientAdapter.class, HttpServiceProxyFactory.class })
93-
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
94-
protected static class WebClientAdapterProviderConfiguration {
95-
96-
@Bean
97-
@ConditionalOnBean(WebClient.Builder.class)
98-
@ConditionalOnMissingBean
99-
HttpExchangeAdapterProvider webClientAdapterProvider(WebClient.Builder webClientBuilder,
100-
ObjectProvider<HttpInterfaceClientsProperties> propertiesProvider) {
101-
return new WebClientAdapterProvider(webClientBuilder, propertiesProvider);
102-
}
103-
10466
}
67+
//
68+
// @Configuration(proxyBeanMethods = false)
69+
// @ConditionalOnClass({ RestTemplate.class, RestTemplateAdapter.class,
70+
// HttpServiceProxyFactory.class })
71+
// @Conditional(NotReactiveWebApplicationCondition.class)
72+
// protected static class RestTemplateAdapterProviderConfiguration {
73+
//
74+
// @Bean
75+
// @ConditionalOnBean(RestTemplateBuilder.class)
76+
// @ConditionalOnMissingBean
77+
// @ConditionalOnProperty(value = "spring.interfaceclients.resttemplate.enabled",
78+
// havingValue = "true")
79+
// HttpExchangeAdapterProvider restTemplateAdapterProvider(RestTemplateBuilder
80+
// restTemplateBuilder,
81+
// ObjectProvider<HttpInterfaceClientsProperties> propertiesProvider) {
82+
// return new RestTemplateAdapterProvider(restTemplateBuilder, propertiesProvider);
83+
// }
84+
//
85+
// }
86+
//
87+
// @Configuration(proxyBeanMethods = false)
88+
// @ConditionalOnClass({ WebClient.class, WebClientAdapter.class,
89+
// HttpServiceProxyFactory.class })
90+
// @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
91+
// protected static class WebClientAdapterProviderConfiguration {
92+
//
93+
// @Bean
94+
// @ConditionalOnBean(WebClient.Builder.class)
95+
// @ConditionalOnMissingBean
96+
// HttpExchangeAdapterProvider webClientAdapterProvider(WebClient.Builder
97+
// webClientBuilder,
98+
// ObjectProvider<HttpInterfaceClientsProperties> propertiesProvider) {
99+
// return new WebClientAdapterProvider(webClientBuilder, propertiesProvider);
100+
// }
101+
//
102+
// }
105103

106104
}

0 commit comments

Comments
 (0)