endpoints = new ArrayList<>();
21 |
22 | private Log log = new Log();
23 |
24 | @Data
25 | public static class EndPoint {
26 |
27 | private String identity;
28 |
29 | private String baseUrl;
30 | }
31 |
32 | @Data
33 | public static class Connection {
34 |
35 | private Long readTimeout = 10000L;
36 |
37 | private Long writeTimeout = 10000L;
38 |
39 | private Long connectTimeout = 10000L;
40 |
41 | private Integer maxIdleConnections = 5;
42 |
43 | private Integer keepAliveDuration = 5;
44 |
45 | private int retryTimes = 0;
46 | }
47 |
48 | @Data
49 | public static class Log {
50 |
51 | private Boolean enabled = false;
52 |
53 | private HttpLoggingInterceptor.Level content = HttpLoggingInterceptor.Level.NONE;
54 |
55 | private Level level = Level.DEBUG;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/oneapm/touch/retrofit/boot/RetrofitServiceBeanPostProcessorAdapter.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.boot;
2 |
3 | import com.oneapm.touch.retrofit.autoconfigure.RetrofitProperties;
4 | import com.oneapm.touch.retrofit.boot.annotation.RetrofitService;
5 | import com.oneapm.touch.retrofit.boot.context.RetrofitContext;
6 | import org.springframework.beans.BeansException;
7 | import org.springframework.beans.factory.BeanFactory;
8 | import org.springframework.beans.factory.BeanFactoryAware;
9 | import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
10 | import org.springframework.core.Ordered;
11 | import org.springframework.core.PriorityOrdered;
12 | import org.springframework.util.Assert;
13 | import retrofit2.Retrofit;
14 |
15 | /**
16 | * Instantiation aware bean post processor adapter to instantiate the bean interfaces marked with {@link RetrofitService}
17 | * annotation.
18 | *
19 | * The beans can't be annotated in the bean definition phase as the {@link Retrofit} bean is needed in order
20 | * to construct the actual service instances. In addition, the service specific configurations are accessed
21 | * through {@link RetrofitProperties} {@link org.springframework.boot.context.properties.ConfigurationProperties}
22 | *
23 | * @author troinine
24 | */
25 | public class RetrofitServiceBeanPostProcessorAdapter extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware, PriorityOrdered {
26 |
27 | /**
28 | * The name of this bean.
29 | */
30 | static final String BEAN_NAME = "retrofitServiceBeanPostProcessorAdapter";
31 |
32 | private BeanFactory beanFactory;
33 | private RetrofitServiceFactory retrofitServiceFactory;
34 |
35 | @Override
36 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
37 | this.beanFactory = beanFactory;
38 | }
39 |
40 | @Override
41 | public int getOrder() {
42 | return Ordered.HIGHEST_PRECEDENCE - 1;
43 | }
44 |
45 | @Override
46 | public Object postProcessBeforeInstantiation(Class> beanClass, String beanName) throws BeansException {
47 | Object ret = null;
48 |
49 | if (beanClass.isAnnotationPresent(RetrofitService.class)) {
50 | RetrofitService annotation = beanClass.getAnnotation(RetrofitService.class);
51 | String retrofitId = "".equals(annotation.retrofit()) ? annotation.value() : annotation.retrofit();
52 | ret = getRetrofitServiceFactory().createServiceInstance(beanClass, retrofitId);
53 | }
54 |
55 | return ret;
56 | }
57 |
58 | /**
59 | * Lazy-inits the associated Retrofit service factory because the needed dependencies are available after
60 | * the needed bean dependencies have been created by the {@link BeanFactory}.
61 | *
62 | * @return {@link RetrofitServiceFactory} ready to construct service instances.
63 | */
64 | private RetrofitServiceFactory getRetrofitServiceFactory() {
65 | Assert.notNull(beanFactory, "BeanFactory may not be null");
66 |
67 | if (retrofitServiceFactory == null) {
68 | RetrofitContext context = beanFactory.getBean(RetrofitContext.class);
69 |
70 | retrofitServiceFactory = new RetrofitServiceFactory(context);
71 | }
72 |
73 | return retrofitServiceFactory;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/oneapm/touch/retrofit/boot/RetrofitServiceComponentProvider.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.boot;
2 |
3 | import com.oneapm.touch.retrofit.boot.annotation.RetrofitService;
4 | import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
5 | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
6 | import org.springframework.core.type.filter.AnnotationTypeFilter;
7 |
8 | /**
9 | * Custom classpath scanner which includes interfaces that have been annotated with {@link RetrofitService}.
10 | *
11 | * Since Retrofit only supports interfaces, all other types are ignored.
12 | */
13 | public class RetrofitServiceComponentProvider extends ClassPathScanningCandidateComponentProvider {
14 |
15 | private RetrofitServiceComponentProvider() {
16 | super(false);
17 | addIncludeFilter(new AnnotationTypeFilter(RetrofitService.class, true, true));
18 | }
19 |
20 | @Override
21 | protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
22 | return beanDefinition.getMetadata().isInterface();
23 | }
24 |
25 | static RetrofitServiceComponentProvider getInstance() {
26 | return new RetrofitServiceComponentProvider();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/oneapm/touch/retrofit/boot/RetrofitServiceFactory.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.boot;
2 |
3 | import com.oneapm.touch.retrofit.boot.context.RetrofitContext;
4 | import retrofit2.Retrofit;
5 |
6 | /**
7 | * Factory for constructing {@link Retrofit} service instances.
8 | */
9 | class RetrofitServiceFactory {
10 | private final RetrofitContext retrofitContext;
11 |
12 | RetrofitServiceFactory(RetrofitContext retrofitContext) {
13 | this.retrofitContext = retrofitContext;
14 | }
15 |
16 | T createServiceInstance(Class serviceClass, String retrofitId) {
17 | Retrofit retrofit = getConfiguredRetrofit(retrofitId);
18 | return retrofit.create(serviceClass);
19 | }
20 |
21 | private Retrofit getConfiguredRetrofit(String beanId) {
22 | return retrofitContext.getRetrofit(beanId)
23 | .orElseThrow(() -> new RuntimeException("Cannot obtain [" + beanId + "] Retrofit in your application configuration file."));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/oneapm/touch/retrofit/boot/RetrofitServiceFactoryBeanRegistrar.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.boot;
2 |
3 | import com.oneapm.touch.retrofit.boot.annotation.RetrofitService;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.beans.factory.annotation.Qualifier;
6 | import org.springframework.beans.factory.config.BeanDefinition;
7 | import org.springframework.beans.factory.support.BeanDefinitionRegistry;
8 | import org.springframework.beans.factory.support.RootBeanDefinition;
9 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
10 | import org.springframework.core.annotation.AnnotationAttributes;
11 | import org.springframework.core.type.AnnotationMetadata;
12 | import org.springframework.util.Assert;
13 | import org.springframework.util.ClassUtils;
14 | import org.springframework.util.ObjectUtils;
15 | import org.springframework.util.StringUtils;
16 |
17 | import java.util.Arrays;
18 | import java.util.Collections;
19 | import java.util.LinkedHashSet;
20 | import java.util.Set;
21 |
22 | /**
23 | * Bean definition registrar responsible for registering Retrofit specific bean definitions.
24 | *
25 | * Currently registering instantiation aware bean post processor adapter responsible for instantiating Retrofit
26 | * services and registering individual Retrofit service interfaces as bean definitions so that they can
27 | * instantiated by the post processor adapter.
28 | */
29 | @Slf4j
30 | public class RetrofitServiceFactoryBeanRegistrar implements ImportBeanDefinitionRegistrar {
31 |
32 | @Override
33 | public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
34 | if (!registry.containsBeanDefinition(RetrofitServiceBeanPostProcessorAdapter.BEAN_NAME)) {
35 | registry.registerBeanDefinition(RetrofitServiceBeanPostProcessorAdapter.BEAN_NAME,
36 | new RootBeanDefinition(RetrofitServiceBeanPostProcessorAdapter.class));
37 | }
38 | doRegisterRetrofitServiceBeanDefinitions(annotationMetadata, registry);
39 | }
40 |
41 | /**
42 | * Scans for interfaces annotated with {@link RetrofitService} from the packages defined by
43 | * {@link RetrofitServiceScan}.
44 | *
45 | * @param annotationMetadata annotation metadata of the importing class
46 | * @param registry current bean definition registry
47 | */
48 | private void doRegisterRetrofitServiceBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
49 | RetrofitServiceComponentProvider provider = RetrofitServiceComponentProvider.getInstance();
50 |
51 | // Find packages to scan for Retrofit services.
52 | Set packagesToScan = getPackagesToScan(annotationMetadata);
53 |
54 | for (String packageToScan : packagesToScan) {
55 | log.debug("Trying to find candidates from package {}", packageToScan);
56 |
57 | Set candidates = provider.findCandidateComponents(packageToScan);
58 |
59 | if (!candidates.isEmpty()) {
60 | processCandidates(candidates, registry);
61 | }
62 | }
63 | }
64 |
65 | /**
66 | * Processes the given set of bean definitions and registers them to the bean definition registry
67 | * to be further processed by {@link RetrofitServiceBeanPostProcessorAdapter}.
68 | *
69 | * @param candidates the candidates to register.
70 | * @param registry the bean registry.
71 | */
72 | private void processCandidates(Set candidates, BeanDefinitionRegistry registry) {
73 | log.debug("Found {} Retrofit Service candidate(s)", candidates.size());
74 |
75 | for (BeanDefinition beanDefinition : candidates) {
76 | String beanName = generateBeanName(beanDefinition);
77 |
78 | log.debug("Processing candidate class {} with bean name {}",
79 | beanDefinition.getBeanClassName(),
80 | beanName);
81 |
82 | registry.registerBeanDefinition(beanName, beanDefinition);
83 | }
84 | }
85 |
86 | /**
87 | * Inspects the packages to be scanned for {@link RetrofitService} interfaces from the {@link RetrofitServiceScan}
88 | * import annotation.
89 | *
90 | * @param metadata the annotation metadata.
91 | * @return a set of packages to be scanned for {@link RetrofitService} candidates.
92 | */
93 | private Set getPackagesToScan(AnnotationMetadata metadata) {
94 | AnnotationAttributes attributes = AnnotationAttributes
95 | .fromMap(metadata.getAnnotationAttributes(RetrofitServiceScan.class.getName()));
96 |
97 | String[] value = attributes.getStringArray("value");
98 | String[] basePackages = attributes.getStringArray("basePackages");
99 | Class>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
100 |
101 | if (!ObjectUtils.isEmpty(value)) {
102 | Assert.state(ObjectUtils.isEmpty(basePackages),
103 | "@RetrofitServiceScan basePackages and value attributes are mutually exclusive");
104 | }
105 |
106 | Set packagesToScan = new LinkedHashSet();
107 | packagesToScan.addAll(Arrays.asList(value));
108 | packagesToScan.addAll(Arrays.asList(basePackages));
109 |
110 | for (Class> basePackageClass : basePackageClasses) {
111 | packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
112 | }
113 |
114 | if (packagesToScan.isEmpty()) {
115 | return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
116 | }
117 |
118 | return packagesToScan;
119 | }
120 |
121 | /**
122 | * Constructs a bean name for the given bean definition.
123 | *
124 | * @param beanDefinition the bean definition from which to extract the information in order to construct a name.
125 | * @return a bean name.
126 | */
127 | private String generateBeanName(BeanDefinition beanDefinition) {
128 | // Try obtaining the client specified bean name if available in the annotated interface
129 | try {
130 | Class> beanClass = Class.forName(beanDefinition.getBeanClassName());
131 | RetrofitService retrofitService = beanClass.getAnnotation(RetrofitService.class);
132 |
133 | if (retrofitService != null && StringUtils.hasText(retrofitService.name())) {
134 | return retrofitService.name();
135 | }
136 |
137 | // Support @Qualifier retrofitService
138 | Qualifier qualifier = beanClass.getAnnotation(Qualifier.class);
139 | if (qualifier != null && !"".equals(qualifier.value())) {
140 | return qualifier.value();
141 | }
142 |
143 | // Reduce the conflict of same endpoint class name, use full package class name instead
144 | // So we wouldn't prefer to use AnnotationBeanNameGenerator
145 | return beanClass.getName();
146 |
147 | } catch (ClassNotFoundException e) {
148 | throw new RuntimeException("Cannot obtain bean name for Retrofit service interface", e);
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/main/java/com/oneapm/touch/retrofit/boot/RetrofitServiceScan.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.boot;
2 |
3 | import com.oneapm.touch.retrofit.boot.annotation.RetrofitService;
4 | import org.springframework.context.annotation.Import;
5 |
6 | import java.lang.annotation.*;
7 |
8 | /**
9 | * Configures Retrofit auto-configuration to scan interfaces annotated with
10 | * {@link RetrofitService} from the given packages.
11 | *
12 | * One of {@link #basePackageClasses()}, {@link #basePackages()} or its alias
13 | * {@link #value()} may be specified to define specific packages to scan. If specific
14 | * packages are not defined scanning will occur from the package of the class with this
15 | * annotation.
16 | *
17 | * Note that without this annotation, the services will not be scanned.
18 | */
19 | @Target(ElementType.TYPE)
20 | @Retention(RetentionPolicy.RUNTIME)
21 | @Documented
22 | @Import(RetrofitServiceFactoryBeanRegistrar.class)
23 | public @interface RetrofitServiceScan {
24 |
25 | /**
26 | * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
27 | * declarations e.g.: {@code @RetrofitServiceScan("org.my.pkg")} instead of
28 | * {@code @RetrofitServiceScan(basePackages="org.my.pkg")}.
29 | *
30 | * @return the base packages to scan
31 | */
32 | String[] value() default {};
33 |
34 | /**
35 | * Base packages to scan for annotated entities. {@link #value()} is an alias for (and
36 | * mutually exclusive with) this attribute.
37 | *
38 | * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
39 | * package names.
40 | *
41 | * @return the base packages to scan
42 | */
43 | String[] basePackages() default {};
44 |
45 | /**
46 | * Type-safe alternative to {@link #basePackages()} for specifying the packages to
47 | * scan for annotated entities. The package of each class specified will be scanned.
48 | *
49 | * Consider creating a special no-op marker class or interface in each package that
50 | * serves no purpose other than being referenced by this attribute.
51 | *
52 | * @return classes from the base packages to scan
53 | */
54 | Class>[] basePackageClasses() default {};
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/oneapm/touch/retrofit/boot/annotation/RetrofitService.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.boot.annotation;
2 |
3 | import com.oneapm.touch.retrofit.boot.RetrofitServiceScan;
4 | import org.springframework.stereotype.Component;
5 |
6 | import java.lang.annotation.ElementType;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.RetentionPolicy;
9 | import java.lang.annotation.Target;
10 |
11 | /**
12 | * Annotates an interface as Retrofit service.
13 | *
14 | * Use this annotation to qualify a Retrofit annotated interface for auto-detection and automatic
15 | * instantiation.
16 | *
17 | * @see RetrofitServiceScan
18 | */
19 | @Retention(RetentionPolicy.RUNTIME)
20 | @Target(ElementType.TYPE)
21 | @Component
22 | public @interface RetrofitService {
23 |
24 | /**
25 | * Defines the name of the service bean when registered to the underlying context. If left unspecified
26 | * the name of the service bean is generated using {@link org.springframework.beans.factory.annotation.Qualifier},
27 | * If no Qualifier annotation, we would use full class name instead.
28 | *
29 | * @return the name of the bean.
30 | */
31 | String name() default "";
32 |
33 | /**
34 | * Alias for the {@link #retrofit()} attribute. Allows for more concise annotation
35 | * declarations e.g.: {@code @RetrofitService("ai")} instead of
36 | * {@code @RetrofitService(retrofit="ai")}.
37 | *
38 | * @return the specified retrofit instance to build endpoint
39 | */
40 | String value() default "default";
41 |
42 | /**
43 | * Defines the name of retrofit should be used in building the service endpoint
44 | * eg. ai, bi, mi or cep {@link #value()} is an alias for (and mutually exclusive with) this attribute.
45 | */
46 | String retrofit() default "";
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/oneapm/touch/retrofit/boot/context/LocalRetrofitContext.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.boot.context;
2 |
3 | import retrofit2.Retrofit;
4 |
5 | import java.util.Optional;
6 | import java.util.concurrent.ConcurrentHashMap;
7 |
8 | public class LocalRetrofitContext extends ConcurrentHashMap implements RetrofitContext {
9 | private static final long serialVersionUID = -5865286831705661141L;
10 |
11 | @Override
12 | public Retrofit register(String identity, Retrofit retrofit) {
13 | return put(identity, retrofit);
14 | }
15 |
16 | @Override
17 | public boolean unregister(String identity) {
18 | return remove(identity) != null;
19 | }
20 |
21 | @Override
22 | public Optional getRetrofit(String identity) {
23 | return Optional.ofNullable(get(identity));
24 | }
25 |
26 | @Override
27 | public boolean hasRetrofit(String identity) {
28 | return containsKey(identity);
29 | }
30 |
31 | @Override
32 | public boolean empty() {
33 | clear();
34 | return true;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/oneapm/touch/retrofit/boot/context/RetrofitContext.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.boot.context;
2 |
3 | import retrofit2.Retrofit;
4 |
5 | import java.util.Optional;
6 |
7 | /**
8 | * The k v store for retrofit instance, because the retrofit instance is immutable,
9 | * and we couldn't get some useful identify from it's public method.
10 | *
11 | * In order to support multiply base url endpoint instance, we must create and store them separately.
12 | */
13 | public interface RetrofitContext {
14 |
15 | /**
16 | * Register the given retrofit to specified identity,if the context already hold the given identity,
17 | * we would return the old retrofit instance
18 | */
19 | Retrofit register(String identity, Retrofit retrofit);
20 |
21 | /**
22 | * remove the given retrofit from context
23 | *
24 | * @return true for succeed in remove, false for the given retrofit identity doesn't existed
25 | */
26 | boolean unregister(String identity);
27 |
28 | Optional getRetrofit(String identity);
29 |
30 | boolean hasRetrofit(String identity);
31 |
32 | boolean empty();
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/oneapm/touch/retrofit/boot/intercepts/RetryInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.boot.intercepts;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.extern.slf4j.Slf4j;
5 | import okhttp3.Interceptor;
6 | import okhttp3.Request;
7 | import okhttp3.Response;
8 |
9 | import java.io.IOException;
10 |
11 | /**
12 | * The retry count limit intercept, would retry the net request until success or reach the limited times
13 | *
14 | * @link https://stackoverflow.com/questions/37883418/does-okhttpclient-have-a-max-retry-count
15 | */
16 | @Slf4j
17 | @AllArgsConstructor
18 | public class RetryInterceptor implements Interceptor {
19 |
20 | private final Integer retryTimes;
21 |
22 | @Override
23 | public Response intercept(Chain chain) throws IOException {
24 | Request request = chain.request();
25 | Response response = doRequest(chain, request);
26 | int tryCount = 0;
27 | while (response == null && tryCount < retryTimes) {
28 | Request newRequest = request.newBuilder().build(); // Avoid the cache
29 | response = doRequest(chain, newRequest);
30 | tryCount++;
31 | log.warn("Request failed, retry to acquire a new connection, {} in {} times", tryCount, retryTimes);
32 | }
33 | if (response == null) { // Important ,should throw an exception here
34 | throw new IOException();
35 | }
36 | return response;
37 | }
38 |
39 | private Response doRequest(Chain chain, Request request) {
40 | Response response = null;
41 | try {
42 | response = chain.proceed(request);
43 | } catch (Exception e) {
44 | log.error("", e);
45 | }
46 | return response;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 | com.oneapm.touch.retrofit.autoconfigure.RetrofitAutoConfiguration
3 |
--------------------------------------------------------------------------------
/src/test/java/com/oneapm/touch/retrofit/Application.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit;
2 |
3 | import org.springframework.boot.autoconfigure.SpringBootApplication;
4 |
5 | /**
6 | * This main class is mainly for test purpose
7 | * Plz don't run it directly
8 | */
9 | @SpringBootApplication
10 | public class Application {
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/java/com/oneapm/touch/retrofit/autoconfigure/RetrofitAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.autoconfigure;
2 |
3 | import com.oneapm.touch.retrofit.boot.RetrofitServiceScan;
4 | import com.oneapm.touch.retrofit.boot.annotation.RetrofitService;
5 | import com.oneapm.touch.retrofit.boot.context.RetrofitContext;
6 | import lombok.Data;
7 | import org.junit.After;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.test.util.EnvironmentTestUtils;
12 | import org.springframework.context.annotation.AnnotationConfigApplicationContext;
13 | import org.springframework.context.annotation.Configuration;
14 | import retrofit2.Call;
15 | import retrofit2.Converter;
16 | import retrofit2.Retrofit;
17 | import retrofit2.converter.jackson.JacksonConverterFactory;
18 | import retrofit2.http.GET;
19 |
20 | import java.util.List;
21 | import java.util.Optional;
22 |
23 | import static org.assertj.core.api.Assertions.assertThat;
24 |
25 | /**
26 | * Unit tests for {@link RetrofitAutoConfiguration}
27 | */
28 | public class RetrofitAutoConfigurationTest {
29 |
30 | @Autowired
31 | private AnnotationConfigApplicationContext context;
32 |
33 | @RetrofitService("ai")
34 | public interface MyService {
35 | @GET("/hello")
36 | Call sayHello();
37 |
38 | @GET("/hello-observable-scalar")
39 | Call toHelloObservable();
40 | }
41 |
42 | @RetrofitService(value = "ai")
43 | public interface MyService2 {
44 | @GET("/hello")
45 | Call sayHello();
46 |
47 | @GET("/hello-observable-scalar")
48 | Call toHelloObservable();
49 | }
50 |
51 | @RetrofitService(name = MyCustomBeanNameService.BEAN_NAME, retrofit = "bi")
52 | public interface MyCustomBeanNameService {
53 | String BEAN_NAME = "myBeanName";
54 |
55 | @GET("/hello")
56 | Call sayHello();
57 | }
58 |
59 | @Configuration
60 | @RetrofitServiceScan
61 | public static class RetrofitTestConfiguration {
62 | // To enable service scanning
63 | }
64 |
65 | @Before
66 | public void setup() {
67 | loadContext();
68 | }
69 |
70 | @After
71 | public void teardown() {
72 | if (context != null) {
73 | context.close();
74 | }
75 | }
76 |
77 | @Test
78 | public void testRetrofitAutoConfigured() {
79 | RetrofitContext context = this.context.getBean(RetrofitContext.class);
80 | assertThat(context).isNotNull();
81 | }
82 |
83 | @Test
84 | public void testRetrofitAutoConfiguredWithConverters() {
85 | RetrofitContext context = this.context.getBean(RetrofitContext.class);
86 | Optional retrofit = context.getRetrofit("ai");
87 | assertThat(retrofit.get()).isNotNull();
88 |
89 | // Assert that we have exactly the converter factories that are auto-configured
90 | List converterFactories = retrofit.get().converterFactories();
91 |
92 | // Retrofit internally adds BuildInConverters
93 | assertThat(converterFactories).hasSize(2).hasAtLeastOneElementOfType(JacksonConverterFactory.class);
94 | }
95 |
96 | @Test
97 | public void testMyServiceAutoConfigured() {
98 | MyService myService = context.getBean(MyService.class);
99 | assertThat(myService).isNotNull();
100 | }
101 |
102 | @Test
103 | public void testCustomizingRetrofitServiceBeanName() {
104 | MyCustomBeanNameService myCustomBeanNameService = context.getBean(MyCustomBeanNameService.BEAN_NAME, MyCustomBeanNameService.class);
105 | assertThat(myCustomBeanNameService).isNotNull();
106 | }
107 |
108 | private void loadContext() {
109 | context = new AnnotationConfigApplicationContext();
110 | EnvironmentTestUtils.addEnvironment(context, "retrofit.enable=true",
111 | "retrofit.endpoints[0].identity=ai", "retrofit.endpoints[0].baseUrl=http://127.0.0.1:10010",
112 | "retrofit.endpoints[1].identity=bi", "retrofit.endpoints[1].baseUrl=http://127.0.0.1:10011",
113 | "retrofit.connection.timeout=5000", "retrofit.connection.retry-times=5", "retrofit.log.enabled=true");
114 | context.register(RetrofitAutoConfiguration.class, RetrofitTestConfiguration.class);
115 | context.refresh();
116 | }
117 |
118 | @Data
119 | private static class Hello {
120 | private String message;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/test/java/com/oneapm/touch/retrofit/autoconfigure/RetrofitPropertiesTest.java:
--------------------------------------------------------------------------------
1 | package com.oneapm.touch.retrofit.autoconfigure;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.test.context.junit4.SpringRunner;
8 |
9 | import static org.hamcrest.CoreMatchers.is;
10 | import static org.hamcrest.CoreMatchers.notNullValue;
11 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
12 | import static org.junit.Assert.assertThat;
13 |
14 | @RunWith(SpringRunner.class)
15 | @SpringBootTest
16 | public class RetrofitPropertiesTest {
17 |
18 | @Autowired
19 | private RetrofitProperties properties;
20 |
21 | @Test
22 | public void listInjectionFromConfigurationFile() throws Exception {
23 | assertThat(properties, notNullValue());
24 | }
25 |
26 | @Test
27 | public void injectedValueShouldBeTheSameAsTheConfigurationFile() throws Exception {
28 | assertThat(properties.getEndpoints(), hasSize(4));
29 | RetrofitProperties.Connection connection = properties.getConnection();
30 | assertThat(connection.getConnectTimeout(), is(5000L));
31 | assertThat(connection.getKeepAliveDuration(), is(5));
32 | assertThat(connection.getMaxIdleConnections(), is(5));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/resources/application.yml:
--------------------------------------------------------------------------------
1 | # The http configuration part for integration with other system
2 | retrofit:
3 | log:
4 | enabled: true
5 | content: BASIC # NONE, BASIC, HEADERS, BODY
6 | level: INFO
7 |
8 | connection:
9 | retryTimes: 5
10 | connectTimeout: 5000 # The timeout for http request, mile seconds, so 5000 means 5 seconds
11 | maxIdleConnections: 5 # The maximum number of idle connections for each address.
12 | keepAliveDuration: 5 # The time (minutes) to live for each idle connections.
13 |
14 | # identity: current available
15 | # baseUrl: the base part of business system url, would be changed by nginx location, "/" is not required to be the end of url
16 | endpoints:
17 | - identity: default
18 | baseUrl: http://127.0.0.1:${random.int(10000,15000)}
19 | - identity: ai
20 | baseUrl: http://127.0.0.1:${random.int(10000,15000)}
21 | - identity: mi
22 | baseUrl: http://127.0.0.1:${random.int(10000,15000)}
23 | - identity: cep
24 | baseUrl: http://127.0.0.1:${random.int(10000,15000)}
25 |
--------------------------------------------------------------------------------
/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
7 |
8 | UTF-8
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------