├── .editorconfig ├── .gitattributes ├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── oneapm │ │ └── touch │ │ └── retrofit │ │ ├── autoconfigure │ │ ├── RetrofitAutoConfiguration.java │ │ └── RetrofitProperties.java │ │ └── boot │ │ ├── RetrofitServiceBeanPostProcessorAdapter.java │ │ ├── RetrofitServiceComponentProvider.java │ │ ├── RetrofitServiceFactory.java │ │ ├── RetrofitServiceFactoryBeanRegistrar.java │ │ ├── RetrofitServiceScan.java │ │ ├── annotation │ │ └── RetrofitService.java │ │ ├── context │ │ ├── LocalRetrofitContext.java │ │ └── RetrofitContext.java │ │ └── intercepts │ │ └── RetryInterceptor.java └── resources │ └── META-INF │ └── spring.factories └── test ├── java └── com │ └── oneapm │ └── touch │ └── retrofit │ ├── Application.java │ └── autoconfigure │ ├── RetrofitAutoConfigurationTest.java │ └── RetrofitPropertiesTest.java └── resources ├── application.yml └── logback-test.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 4 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [{package,bower}.json] 23 | indent_style = space 24 | indent_size = 2 25 | 26 | [*.json] 27 | indent_style = space 28 | indent_size = 2 29 | 30 | [*.yml] 31 | indent_style = space 32 | indent_size = 2 33 | 34 | [*.sql] 35 | indent_style = space 36 | indent_size = 2 37 | 38 | [*.scala] 39 | indent_style = space 40 | indent_size = 2 41 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # All text files should have the "lf" (Unix) line endings 2 | * text eol=lf 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.java text 7 | *.js text 8 | *.css text 9 | *.html text 10 | *.feature text 11 | *.scala text 12 | *.xml text 13 | *.yml text 14 | *.sh text 15 | *.md text 16 | *.sql text 17 | 18 | # Denote all files that are truly binary and should not be modified. 19 | *.png binary 20 | *.jpg binary 21 | *.jar binary 22 | *.pdf binary 23 | *.eot binary 24 | *.ttf binary 25 | *.gzip binary 26 | *.gz binary 27 | *.ai binary 28 | *.eps binary 29 | *.swf binary 30 | *.ico binary 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.settings/ 2 | *target/ 3 | *.svn/ 4 | *.project 5 | *.classpath 6 | .DS_Store 7 | .idea/ 8 | *.iml 9 | 10 | **/pom.xml.releaseBackup 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Retrofit Springboot Auto Configuration 2 | 3 | This project is mainly for integration with other business system. We use springboot to 4 | get better configuration and dependence inversion. 5 | 6 | Retrofit 2 is just a simple abstraction for http request. The backend of its to perform http 7 | request is mainly depends on `okhttp3`. We also support async http request out of the box. 8 | 9 | ### Architecture 10 | 11 | ``` 12 | com.oneapm.touch.retrofit 13 | ├── autoconfigure: Retrofit configuration module and its configuration properties definition 14 | └── boot: the bean registry factory for building retrofit endpoint interface instance 15 | ``` 16 | 17 | ### Dependency 18 | 19 | Find the [latest version](http://nexus.oneapm.me:8081/nexus/#nexus-search;gav~com.oneapm.touch.retrofit~retrofit-spring-boot-starter~~~) 20 | and grad it via Maven: 21 | 22 | ```xml 23 | 24 | com.oneapm.touch.retrofit 25 | retrofit-spring-boot-starter 26 | ${latest.starter.version} 27 | 28 | ``` 29 | 30 | or Gradle: 31 | 32 | ``` 33 | compile "com.oneapm.touch.retrofit:retrofit-spring-boot-starter:latest.version" 34 | ``` 35 | 36 | ### Detailed Usage in Springboot Application 37 | 38 | In your main Spring Boot application, you need to add the annotation `@RetrofitServiceScan` on the configuration class 39 | (aka. the class which has spring annotation `@Configuration`) or spring boot's bootstrap class (the class annotated with `@SpringBootApplication`) 40 | to enable the auto bean generation for all the customized retrofit interface. e.g.: 41 | 42 | ```java 43 | @SpringBootApplication 44 | @RetrofitServiceScan 45 | public class MainApplication { 46 | 47 | public static void main(final String ... args) { 48 | SpringApplication.run(MainApplication.class, args); 49 | } 50 | } 51 | ``` 52 | 53 | All the customize retrofit interface should be annotated with `@RetrofitService` annotation, 54 | that would make these interface could be registed our own bean creation process. e.g.: 55 | 56 | ```java 57 | @RetrofitService("ai") 58 | public interface AiAgentEndpoint { 59 | 60 | @GET("ai/v5/apps/{appId}/tiers/{tierId}/agents") 61 | Call> getAgents(@Path("appId") long appId, @Path("tierId") long tierId); 62 | } 63 | ``` 64 | 65 | `@RetrofitService` have three properties, the `value` or `retrofit` stand for the identity of the retrofit configuration 66 | that your would like to use (It would be clarify in configuration file part). `name` attribute means the spring's bean name. 67 | If no attributes provided, we would pick the interface's class name as the bean name and the `default` retrofit configuration. 68 | 69 | In you `application.yml` set the configuration needed to send http request. 70 | 71 | **YAML** 72 | 73 | ```yaml 74 | # The http configuration part for integration with other system 75 | retrofit: 76 | connection: 77 | readTimeout: 10000 78 | writeTimeout: 10000 79 | connectTimeout: 10000 80 | maxIdleConnections: 5 # The maximum number of idle connections for each address. 81 | keepAliveDuration: 5 # The time (minutes) to live for each idle connections. 82 | retryTimes: 5 83 | 84 | # identity: current available 85 | # baseUrl: the base part of business system url, would be changed by nginx location, "/" is not required to be the end of url 86 | endpoints: 87 | - identity: default 88 | baseUrl: http://127.0.0.1:${random.int(10000,15000)} 89 | - identity: ai 90 | baseUrl: http://127.0.0.1:${random.int(10000,15000)} 91 | - identity: mi 92 | baseUrl: http://127.0.0.1:${random.int(10000,15000)} 93 | - identity: cep 94 | baseUrl: http://127.0.0.1:${random.int(10000,15000)} 95 | ``` 96 | 97 | **Java Properties** 98 | 99 | ```properties 100 | retrofit.timeout=5000 101 | retrofit.connection.read-timeout=10000 102 | retrofit.connection.write-timeout=10000 103 | retrofit.connection.connect-timeout=10000 104 | retrofit.connection.max-idle-connections=5 105 | retrofit.connection.keep-alive-duration=5 106 | retrofit.connection.retry-times=5 107 | # The list of outer system to communicate with should have a index form like 108 | # retrofit.endpoints[index], starts from 0 109 | retrofit.endpoints[0].identity=ai # The system id 110 | retrofit.endpoints[0].baseUrl: http://127.0.0.1:10010 # The system prefix url 111 | ``` 112 | 113 | You can now `@Autowired` the retrofit interface to send real http request. e.g.: 114 | 115 | ```java 116 | @Autowired 117 | private final AiAgentEndpoint agentEndpoint; 118 | 119 | agentEndpoint.getAgents(1L, 1L); 120 | ``` 121 | 122 | ### TODO List 123 | 124 | - [X] Support multiply retrofit instance. 125 | - [X] Jackson deserializer should omit the properties's informal case format. 126 | - [X] Improve async http request support. 127 | - [X] Remove the `Retrofit.Builder`, using a single bean to hold the real retrofit factory. 128 | - [X] Retry in limit times if request failed. 129 | - [ ] Drop `@RetrofitServiceScan` annotation or auto-configure it. 130 | 131 | --- 132 | 133 | > Last modified on: 2017-06-14 07:37 134 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.oneapm.touch.retrofit 5 | retrofit-spring-boot-starter 6 | 1.0.8-SNAPSHOT 7 | jar 8 | 9 | Spring Boot :: Starter :: Retrofit 10 | https://www.oneapm.com 11 | 12 | 13 | 1.8 14 | true 15 | UTF-8 16 | yyyy-MM-dd HH:mm:ss SSS 17 | 18 | 1.5.6.RELEASE 19 | 2.4.0 20 | 3.10.0 21 | 1.7.25 22 | 23 | 1.16.16 24 | 1.1.11 25 | 4.12 26 | 27 | 3.6.1 28 | 2.5.3 29 | 30 | 31 | 32 | JIRA 33 | http://jira.oneapm.me/browse/ALERTENG 34 | 35 | 36 | 37 | scm:git:git@scm.oneapm.me:touch/spring-boot-retrofit-support.git 38 | HEAD 39 | 40 | 41 | 42 | 43 | com.squareup.retrofit2 44 | retrofit 45 | ${retrofit.version} 46 | 47 | 48 | com.squareup.okhttp3 49 | okhttp 50 | ${okhttp3.version} 51 | 52 | 53 | com.squareup.retrofit2 54 | converter-jackson 55 | ${retrofit.version} 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter 61 | ${spring-boot.version} 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-configuration-processor 66 | ${spring-boot.version} 67 | true 68 | 69 | 70 | 71 | org.projectlombok 72 | lombok 73 | ${lombok.version} 74 | provided 75 | 76 | 77 | 78 | 79 | org.slf4j 80 | slf4j-api 81 | ${slf4j.version} 82 | 83 | 84 | com.squareup.okhttp3 85 | logging-interceptor 86 | ${okhttp3.version} 87 | true 88 | 89 | 90 | ch.qos.logback 91 | logback-core 92 | ${logback.version} 93 | true 94 | 95 | 96 | ch.qos.logback 97 | logback-classic 98 | ${logback.version} 99 | true 100 | 101 | 102 | 103 | 104 | org.springframework.boot 105 | spring-boot-starter-test 106 | ${spring-boot.version} 107 | test 108 | 109 | 110 | 111 | junit 112 | junit 113 | ${junit.version} 114 | test 115 | 116 | 117 | 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-release-plugin 123 | ${maven-release-plugin.version} 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-compiler-plugin 128 | ${maven-compiler-plugin.version} 129 | 130 | ${java.version} 131 | ${java.version} 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | maven 141 | http://maven.aliyun.com/nexus/content/groups/public/ 142 | 143 | 144 | local-central 145 | http://nexus.oneapm.me:8081/nexus/content/groups/public/ 146 | 147 | 148 | jcenter-release 149 | http://oss.jfrog.org/artifactory/oss-release-local/ 150 | 151 | 152 | 153 | 154 | 155 | local-snapshots 156 | false 157 | http://nexus.oneapm.me:8081/nexus/content/repositories/snapshots/ 158 | default 159 | 160 | 161 | local-releases 162 | true 163 | default 164 | http://nexus.oneapm.me:8081/nexus/content/repositories/releases/ 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /src/main/java/com/oneapm/touch/retrofit/autoconfigure/RetrofitAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.oneapm.touch.retrofit.autoconfigure; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.MapperFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.oneapm.touch.retrofit.autoconfigure.RetrofitProperties.Connection; 7 | import com.oneapm.touch.retrofit.autoconfigure.RetrofitProperties.Log; 8 | import com.oneapm.touch.retrofit.boot.context.LocalRetrofitContext; 9 | import com.oneapm.touch.retrofit.boot.context.RetrofitContext; 10 | import com.oneapm.touch.retrofit.boot.intercepts.RetryInterceptor; 11 | import lombok.extern.slf4j.Slf4j; 12 | import okhttp3.ConnectionPool; 13 | import okhttp3.Interceptor; 14 | import okhttp3.OkHttpClient; 15 | import okhttp3.logging.HttpLoggingInterceptor; 16 | import org.slf4j.Logger; 17 | import org.slf4j.event.Level; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 22 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 23 | import org.springframework.context.annotation.Bean; 24 | import org.springframework.context.annotation.Configuration; 25 | import org.springframework.core.Ordered; 26 | import org.springframework.core.annotation.Order; 27 | import org.springframework.util.Assert; 28 | import org.springframework.util.ResourceUtils; 29 | import retrofit2.Converter; 30 | import retrofit2.Retrofit; 31 | import retrofit2.Retrofit.Builder; 32 | import retrofit2.converter.jackson.JacksonConverterFactory; 33 | 34 | import java.util.List; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS; 38 | import static java.util.concurrent.TimeUnit.MINUTES; 39 | 40 | @Slf4j 41 | @Order(Ordered.HIGHEST_PRECEDENCE) 42 | @Configuration 43 | @ConditionalOnClass(Retrofit.class) 44 | @EnableConfigurationProperties(RetrofitProperties.class) 45 | public class RetrofitAutoConfiguration { 46 | 47 | private final List converterFactories; 48 | 49 | private final OkHttpClient okHttpClient; 50 | 51 | private final RetrofitProperties retrofitProperties; 52 | 53 | @Autowired 54 | public RetrofitAutoConfiguration(List converterFactories, OkHttpClient okHttpClient, 55 | RetrofitProperties retrofitProperties) { 56 | this.converterFactories = converterFactories; 57 | this.okHttpClient = okHttpClient; 58 | this.retrofitProperties = retrofitProperties; 59 | checkConfiguredUrl(this.retrofitProperties); 60 | } 61 | 62 | @Slf4j 63 | @Configuration 64 | @ConditionalOnClass(OkHttpClient.class) 65 | public static class OkHttpClientConfiguration { 66 | 67 | /** 68 | * Mark this as a bean for user to easy monitor the connection status 69 | */ 70 | @Bean 71 | @ConditionalOnMissingBean 72 | public ConnectionPool connectionPool(RetrofitProperties properties) { 73 | Connection connection = properties.getConnection(); 74 | return new ConnectionPool(connection.getMaxIdleConnections(), connection.getKeepAliveDuration(), MINUTES); 75 | } 76 | 77 | @Bean 78 | @ConditionalOnMissingBean 79 | public OkHttpClient okHttpClient(RetrofitProperties properties, ConnectionPool connectionPool, List interceptors) { 80 | Connection connection = properties.getConnection(); 81 | 82 | OkHttpClient.Builder builder = new OkHttpClient.Builder() 83 | .readTimeout(connection.getReadTimeout(), TimeUnit.MILLISECONDS) 84 | .writeTimeout(connection.getWriteTimeout(), TimeUnit.MILLISECONDS) 85 | .connectTimeout(connection.getConnectTimeout(), TimeUnit.MILLISECONDS) 86 | .connectionPool(connectionPool); 87 | 88 | for (Interceptor interceptor : interceptors) { 89 | builder.addInterceptor(interceptor); 90 | } 91 | 92 | return builder.build(); 93 | } 94 | 95 | @Configuration 96 | @ConditionalOnClass(Interceptor.class) 97 | public static class InterceptorConfiguration { 98 | 99 | @Bean 100 | @ConditionalOnClass(HttpLoggingInterceptor.class) 101 | @ConditionalOnProperty(value = "retrofit.log.enabled", havingValue = "true") 102 | public HttpLoggingInterceptor loggingInterceptor(RetrofitProperties properties) { 103 | Log logConf = properties.getLog(); 104 | HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(innerLogger(logConf.getLevel(), OkHttpClientConfiguration.log)); 105 | interceptor.setLevel(logConf.getContent()); 106 | return interceptor; 107 | } 108 | 109 | @Bean 110 | public RetryInterceptor retryInterceptor(RetrofitProperties properties) { 111 | return new RetryInterceptor(properties.getConnection().getRetryTimes()); 112 | } 113 | 114 | private HttpLoggingInterceptor.Logger innerLogger(Level level, Logger logger) { 115 | if (level == Level.DEBUG) { 116 | return logger::debug; 117 | } else if (level == Level.ERROR) { 118 | return logger::error; 119 | } else if (level == Level.INFO) { 120 | return logger::info; 121 | } else if (level == Level.TRACE) { 122 | return logger::trace; 123 | } else if (level == Level.WARN) { 124 | return logger::warn; 125 | } 126 | throw new UnsupportedOperationException("We don't support this log level currently."); 127 | } 128 | } 129 | } 130 | 131 | @Configuration 132 | @ConditionalOnClass(JacksonConverterFactory.class) 133 | public static class JacksonConverterFactoryConfiguration { 134 | 135 | @Bean 136 | @ConditionalOnMissingBean 137 | public ObjectMapper mapper() { 138 | ObjectMapper mapper = new ObjectMapper(); 139 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 140 | mapper.configure(WRITE_DATES_AS_TIMESTAMPS, false); 141 | return mapper; 142 | } 143 | 144 | @Bean 145 | @ConditionalOnMissingBean 146 | public JacksonConverterFactory jacksonConverterFactory(ObjectMapper mapper) { 147 | mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); 148 | return JacksonConverterFactory.create(mapper); 149 | } 150 | } 151 | 152 | @Bean 153 | @ConditionalOnMissingBean 154 | public RetrofitContext retrofitContext() { 155 | Builder builder = new Builder().validateEagerly(true); 156 | converterFactories.forEach(builder::addConverterFactory); 157 | if (okHttpClient != null) { 158 | builder.client(okHttpClient); 159 | } 160 | RetrofitContext context = new LocalRetrofitContext(); 161 | retrofitProperties.getEndpoints() 162 | .forEach(endPoint -> context.register(endPoint.getIdentity(), builder.baseUrl(endPoint.getBaseUrl()).build())); 163 | return context; 164 | } 165 | 166 | /** 167 | * Check the configured url format is valid by using {@code new URI()} 168 | */ 169 | private void checkConfiguredUrl(RetrofitProperties properties) { 170 | properties.getEndpoints().stream() 171 | .map(RetrofitProperties.EndPoint::getBaseUrl) 172 | .forEach(url -> { 173 | Assert.isTrue(ResourceUtils.isUrl(url), url + " is not a valid url"); 174 | if (!url.endsWith("/")) { 175 | log.warn("The [{}] didn't end with \"/\". This means a relative base url, end with / would be better.", url); 176 | } 177 | }); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/com/oneapm/touch/retrofit/autoconfigure/RetrofitProperties.java: -------------------------------------------------------------------------------- 1 | package com.oneapm.touch.retrofit.autoconfigure; 2 | 3 | import lombok.Data; 4 | import okhttp3.logging.HttpLoggingInterceptor; 5 | import org.slf4j.event.Level; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Spring Boot Retrofit autoconfigure properties 13 | */ 14 | @Data 15 | @ConfigurationProperties(prefix = "retrofit") 16 | public class RetrofitProperties { 17 | 18 | private Connection connection = new Connection(); 19 | 20 | private List 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 | --------------------------------------------------------------------------------