Retrofit Autoconfiguration in Spring - java

I am currently writing a Spring Boot autoconfiguration for Retrofit 2. What I am trying to do is to write some sort of an interface builder that is able instantiate an interface that is annotated with some annotation for autowiring just like Spring Data does it with repositories. As I cannot find any resources on how to do this (or if it can even be done with Spring), I would like to ask for your thoughts on that. Below is for an interface that I would like to instantiate.
My replacement for #Repository is #Retrofit the rest is just "ordinary" code you would write for any Retrofit repository.
The kind of interface I would like to autowire:
#Retrofit
public interface Api {
#GET("usernames")
String[] getUsernames();
}
An example for autowiring:
#SpringBootApplication
public class TestApplication {
#Autowired
private Api api;
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
CommandLineRunner runner() {
return args -> {
System.out.println(api.getUsernames());
};
}
}

As I said I found a solution for my problem.
First we need an auto configuration class that is loaded by Spring Boot - as stated here - by adding the file META-INF/spring.factories with the content that is shown below. This auto configuration loads a registrar which itself searches for classes annotated with #Retrofit via a component provider. At last the registrar creates instances of RetrofitFactoryBean for each instance that could be found while this factory bean creates the Retrofit proxies itself.
The auto configuration
#Configuration
#Import(RetrofitRegistrar.class)
public class RetrofitAutoConfiguration {
#Bean
#ConditionalOnMissingBean
public Retrofit retrofit() {
return new Retrofit.Builder().build();
}
}
META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
spring.retrofit.RetrofitAutoConfiguration
The imported registrar
public class RetrofitRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
#Setter
private BeanFactory beanFactory;
#Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
List<String> basePackages = AutoConfigurationPackages.get(this.beanFactory);
RetrofitComponentProvider provider = new RetrofitComponentProvider(registry);
basePackages.stream()
.map(provider::findCandidateComponents)
.flatMap(Set::stream)
.forEach(comp -> register(comp, registry));
}
private void register(BeanDefinition component, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.
rootBeanDefinition(RetrofitFactoryBean.class);
builder.addConstructorArgValue(component.getBeanClassName());
registry.registerBeanDefinition(
component.getBeanClassName().toLowerCase(), builder.getBeanDefinition());
}
}
The component provider
class RetrofitComponentProvider extends ClassPathScanningCandidateComponentProvider {
#Getter
private BeanDefinitionRegistry registry;
public RetrofitComponentProvider(BeanDefinitionRegistry registry) {
super(false);
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
this.registry = registry;
addIncludeFilter(new AnnotationTypeFilter(Retrofit.class, true, true));
}
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return true;
}
}
The factory bean
#Component
#RequiredArgsConstructor
public class RetrofitFactoryBean extends AbstractFactoryBean<Object> {
#Getter
private final Class<?> objectType;
private final retrofit2.Retrofit retrofit;
#Override
protected Object createInstance() throws Exception {
return retrofit.create(objectType);
}
}
The #Getter, #Setter and #RequiredArgsConstructor annotations are provided by ProjectLombok

Let me ask you firstly to avoid reinventing the wheel by creating a new Spring annotation (yours here is #Retrofit. However, it is absolutely okay to use retrofit with spring there is nothing to prevent it. You can simply try to use an existing Spring annotation which can be #Component as you can see in this question
you can autowire your interface without facing problems.
Hope this helps.

Related

Multiple instances of a bean of one class filled with values from application.properties

I intend to write some HealtCheckContributors for a Spring Boot application using spring-boot-actuator. Hence, I implemented two of them. they are intended for checking the health of different apps, of course, but have a nearly identical structure, except the configuration properties, ...
SonarQube complains about that and I wonder if it is possible to have a single health check class but instantiated as many times as defined in application.properties.
An example:
application.properties:
# actuator
app1.management.baseUrl=http://localhost:10000
app1.management.name=app1HealthCheckContributor
app2.management.basUrl=http://localhost:10001
app2.management.name=app2HealthCheckContributor
HealthCheckContributor for app1:
#Slf4j
#Component("xxx")
public class App1HealthCheckContributor extends AbstractHealthIndicator {
private final App1Properties app1Properties;
public App1HealthCheckContributor(final App1Properties app1Properties) {
this.app1Properties = app1Properties;
}
#Override
protected void doHealthCheck(Health.Builder builder) {...}
}
...and this code for each HealthCheckContributor only distinct in its appXProperties.
Isn't it possible to have some kind of base class like:
#Slf4j
#Component()
public class MyHealthCheckContributor extends AbstractHealthIndicator {
private final MyProperties myProperties;
public MyHealthCheckContributor(final MyProperties myProperties) {
this.myProperties = myProperties;
}
#Override
protected void doHealthCheck(Health.Builder builder) {...}
}
and let Spring Boot take care of instantiating two HealthCheckContributors (in our case App1HealthCheckContributor and App2HealthCheckContributor)?
This would eliminate code duplication.
An example of the properties class file:
#Slf4j
#Data
#ConfigurationProperties(prefix = "app1.management")
public class App1Properties {
private String baseUrl;
private String ...;
}
How can I achieve this and how must an application.properties file looks like to achieve what I intend to do?
The final question: How to test multiple instance creation of a bean of one class filled with values from application.properties?
Assuming the code in doHealthCheck is exactly the same for all apps to be checked you could do the following.
You would start by creating a single health check class:
#Slf4j
public class AppHealthCheckContributor extends AbstractHealthIndicator {
private final AppProperties appProperties;
public App1HealthCheckContributor(final AppProperties appProperties) {
this.appProperties = appProperties;
}
#Override
protected void doHealthCheck(Health.Builder builder) {...}
}
And the properties model as follows:
#Slf4j
#Data
public class AppProperties {
private String baseUrl;
private String name;
}
This means that the configuration would be something like the following (in application.yml):
health-check:
apps:
- baseUrl: http://localhost:10000
name: app1
- baseUrl: http://localhost:10001
name: app2
Finally, you would need to create a bean for each app and register them in the application context:
#Slf4j
#Data
#Configuration
#ConfigurationProperties(prefix = "health-check")
public class AllAppPropertiesConfiguration {
private List<AppProperties> apps;
#Autowired
private GenericApplicationContext applicationContext;
#PostConstruct
fun init() {
for (AppProperties app : apps) {
applicationContext.registerBean(app.getName(), AppHealthCheckContributor.class, app);
}
}
}

Spring specify implementation in configuration

I'm writing application using spring mvc/boot, and I have two storage implementations: database storage and in memory storage. My global idea is choose in configuration file what storage application should use.
My idea is
put #Qualifier annotation on each storage implementation
create two configurations, like databaseStorageConfiguration and InMemoryStorageConfiguration
depends on profile, apply first or second configuration
The thing is I don't know how to bind implementation and configuration.
I tried something like this:
#Configuration
public class InMemoryStorageConfig {
#Autowired
#Qualifier("inMemoryStorage")
private Storage storage;
#Bean
public Storage getStorage() {
return storage;
}
}
But I get an error, that 3 beans were found: 2 beans with dfferent implementation and the 3rd one - in config
UPDATE 1
I've added #Profile("InMemory") to Configuration and activated that profile in properties. That gave no changes but looks more logical now
UPDATE 2
Full configuration:
#SpringBootApplication
#ImportResource("classpath:spring-config.xml")
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
}
#Service
public class WidgetService {
private WidgetCache widgetCache;
#Autowired
public WidgetService(WidgetCache widgetCache) {
this.widgetCache = widgetCache;
}
....
#Qualifier("databaseWidgetCache")
#Transactional
#Repository
public class DatabaseWidgetCache implements WidgetCache {
private WidgetRepository widgetRepository;
#Autowired
public DatabaseWidgetCache(WidgetRepository widgetRepository) {
this.widgetRepository = widgetRepository;
}
#Qualifier("inMemoryWidgetCache")
#Repository
public class InMemoryWidgetCache implements WidgetCache {
private WidgetLayersStorage widgetLayersStorage;
#Autowired
public InMemoryWidgetCache(WidgetLayersStorage widgetLayersStorage) {
this.widgetLayersStorage = widgetLayersStorage;
}
#Profile("InMemory")
#Configuration
public class InMemoryStorageConfig {
#Autowired
#Qualifier("inMemoryWidgetCache")
private WidgetCache widgetCache;
#Bean
public WidgetCache getWidgetCache() {
return widgetCache;
}
}
Stacktrace:
Parameter 0 of constructor in
com.widgets.service.widget.WidgetService required a single
bean, but 3 were found:
- inMemoryWidgetCache: defined in file [..../MemoryWidgetCache.class]
- databaseWidgetCache: defined in file [..../DatabaseWidgetCache.class]
- getWidgetCache: defined by method 'getWidgetCache' in class path resource
[......../InMemoryStorageConfig.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer
to accept multiple beans, or using #Qualifier to identify the bean
that should be consumed
Your WidgetService should be changed to
#Service
public class WidgetService {
private WidgetCache widgetCache;
/** or
private List<WidgetCache> widgetCaches;
public WidgetService(List<WidgetCache> widgetCaches) {
this.widgetCaches = widgetCaches;
}
*/
public WidgetService(#Qualifier(<desired impl>) WidgetCache widgetCache) {
this.widgetCache = widgetCache;
}
}
and need to annotate your InMemoryWidgetCache and DatabaseWidgetCache with #Qualifier annotation. since you are using default convention.
and please remove
#Bean
public WidgetCache getWidgetCache() {
return widgetCache;
}
i don't see a real use there
In order to specify implementation in Configuration class, you don't need "Qualifier" annotation, and configuration should be changed to:
#Profile("inMemoryStorage")
#Import(InMemoryWidgetCache.class)
#Configuration
public class InMemoryStorageConfig {
}
thus, by activating profile, you choose the desire implementation

Spring inherited #Component with constructor arguments

I have a service which needs to create Agents on the runtime. Agents inherit from a base Agent class. I would like to use the Autowired ability of spring instead of doing my own dependency injections.
But I am running into this issue, even though I am marking the component as scope=prototype, and even #Lazy to prevent anything from happening at compile-time.
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.my.project.AgentType1 required a bean of type 'com.my.project.POJO' that could not be found.
This is the service that tries to create the agents:
#Service
public class ProjectMain {
#Autowired
ApplicationContext context;
List<IAgent> agents = new ArrayList<>();
void SetupAgents(List<POJO> agentPojos) {
for(POJO agentPojo: agentPojos) {
IAgent agent = AgentFactory.CreateAgent(agentPojo, context);
agents.add(agent);
}
}
}
This is the factory class, not marked as #Component etc. It uses the context passed to it to create the child class beans. It tries to pass the constructor argument via the getBean method.
public class AgentFactory {
public static IAgent CreateAgent(POJO agentPojo, ApplicationContext context) {
if (agentPojo.type.equals("AgentType1")) {
return context.getBean(AgentType1.class, agentPojo);
} else {
return context.getBean(AgentType2.class, agentPojo);
}
}
}
This is a custom annotation which I found is needed for inheritance scenarios.
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Component
#Inherited
#Lazy
#Scope("prototype")
public #interface AgentAnnotation {}
These are the base and child agent classes, which need a custom data structure called POJO to work.
#AgentAnnotation
public class BaseAgent implements IAgent {
#Autowired
Environment env;
public BaseAgent(POJO agentPojo, String someotherdata) {
}
}
public class AgentType1 extends BaseAgent {
public AgentType1(POJO agentPojo) {
super(agentPojo, "mydata1");
...
}
}
public class AgentType2 extends BaseAgent {
public AgentType2(POJO agentPojo) {
super(agentPojo, "mydata2");
...
}
}
This is the starter app.
#ComponentScan(basePackages = "com.my.project", includeFilters = #ComponentScan.Filter(AgentAnnotation.class))
#EnableScheduling
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I also tried the configuration approach:
#Configuration
public class BaseAgentConfig {
#Bean
#Scope("prototype")
public AgentType1 agentType1(POJO agentPojo) {
return new AgentType1(agentPojo);
}
#Bean
#Scope("prototype")
public AgentType2 agentType2(POJO agentPojo) {
return new AgentType2(agentPojo);
}
}
In this case, I removed the #AgentAnnotation from the baseAgent class as we are now instantiating through this config. Also removed the ComponentScan line from the main App.
This time around, the #Autowired doesn't work. All Autowired references in the baseAgent class are null.
Please advise on the best approach to solve this error. Thanks.
Found the issue and solution.
Basically, I was expecting child classes to inherit #Component and #Scope, which it doesn't.
So essentially, I need to annotate each child class with #Component and #Scope("prototype").
The other problem was that I was expecting Autowired items in the constructor, which was too early. Adding a #PostConstruct addressed that issue.
So I ended up deleting the custom annotation and the configuration class and making the changes I just described.

How to disable Spring autowiring in unit tests for #Configuration/#Bean usage

I want configure a component test using spring-test configuration inner class (#Configuration). Tested components has some services which I'd like to mock for the test. These services are classes (no interface used) and have spring annotations (#Autowired) in them. Mockito can easily mock them, however, I found no way of disabling spring autowiring.
Example how I can easily reproduce:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {
// configured in component-config.xml, using ThirdPartyService
#Autowired
private TestedBean entryPoint;
#Test
public void test() {
}
#Configuration
#ImportResource("/spring/component-config.xml")
static class Beans {
#Bean
ThirdPartyService createThirdPartyService() {
return mock(ThirdPartyService.class);
}
}
}
public class ThirdPartyService {
#Autowired
Foo bar;
}
public class TestedBean {
#Autowired
private ThirdPartyService service;
}
In this example "TestBean" represents the service to be mocked. I would NOT like "bar" to be injected by spring! #Bean(autowire = NO) does not help (in fact, that's the default value).
(Please save me from "use interfaces!" comments - the mocked service can be 3rd party which I can't do anything with.)
UPDATE
Springockito partially solves the problem, as long as you don't have to have anything else to configure (so you can't use configuration class with Springockito - it does not support it), but use mocks only.
Still looking for pure spring solution, if there's any...
Here is my solution to your problem:
import static org.mockito.Mockito.mockingDetails;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MockitoSkipAutowireConfiguration {
#Bean MockBeanFactory mockBeanFactory() {
return new MockBeanFactory();
}
private static class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter {
#Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return !mockingDetails(bean).isMock();
}
}
}
and then just
#Import(MockitoSkipAutowireConfiguration.class)
in your test #Configuration and you are all set
I solved it by creating FactoryBean for my bean instead of just mocking bean. At this way Spring don't try to autowire fields.
Factory bean helping class:
public class MockitoFactoryBean<T> implements FactoryBean<T> {
private final Class<T> clazz;
public MockitoFactoryBean(Class<T> clazz) {
this.clazz = clazz;
}
#Override public T getObject() throws Exception {
return mock(clazz);
}
#Override public Class<T> getObjectType() {
return clazz;
}
#Override public boolean isSingleton() {
return true;
}
}
Actual test context part:
#Configuration
public class TestContext {
#Bean
public FactoryBean<MockingService> mockingService() {
return new MockitoFactoryBean<>(MockingService.class);
}
}
Check Spring profiles. You don't need to disable auto wiring, you need to inject different beans for different configuration.
You could add the mocked service manually to the spring application context via org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton. This way the mock is not post-processed by spring and spring does not attempt to autowire the mock. The mock itself will be injected into your tested bean.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {
// configured in component-config.xml, using ThirdPartyService
#Autowired
private TestedBean entryPoint;
#Autowired
private ThirdPartyService thirdPartyServiceMock;
#Test
public void test() {
}
#Configuration
static class Beans {
#Autowired
private GenericApplicationContext ctx;
#Bean
TestedBean testedBean() {
ctx.getBeanFactory().registerSingleton("thirdPartyService", mock(ThirdPartyService.class));
return new TestedBean();
}
}
public static class ThirdPartyService {
#Autowired
Object bar;
}
public static class TestedBean {
#Autowired
private ThirdPartyService service;
}
}
I am in quite the same situation.
What I found that if you do not set the context loader by #ContextConfiguration annotation on your test class, the default context loader will be used, which derived from AbstractGenericContextLoader. I had a look at its source and turned out it registers all the bean post processors which are responsible for reading annotations such #Autowired. In other words, annotation config is enabled by default.
So the main problem is that there are two configurations which are in conflict: in the java config we said that autowiring is not needed, while the autowired annotation tells the opposite. The real question is how to disable the annotation processing in order to eliminate the undesired configuration.
As far as I know there is no such spring implementation of ContextLoader which would not be derived from AbstractGenericContextLoader so I guess the only we can do is to write our own. It would be something like this:
public static class SimpleContextLoader implements ContextLoader {
#Override
public String[] processLocations(Class<?> type, String... locations) {
return strings;
}
#Override
public ApplicationContext loadContext(String... locations) throws Exception {
// in case of xml configuration
return new ClassPathXmlApplicationContext(strings);
// in case of java configuration (but its name is quite misleading)
// return new AnnotationConfigApplicationContext(TestConfig.class);
}
}
Of course it would be worth to spend more time to find out how to implement ContextLoader properly.
Cheers,
Robert
There are so many ways of doing this, I'm pretty sure that this answer will be incomplete, but here are a few options...
As currently seems to be recommended practice, use constructor injection for your services rather than autowiring the fields directly. This makes testing like this so much easier.
public class SomeTest {
#Mock
private ThirdPartyService mockedBean;
#Before
public void init() {
initMocks(this);
}
#Test
public void test() {
BeanUnderTest bean = new BeanUnderTest(mockedBean);
// ...
}
}
public class BeanUnderTest{
private ThirdPartyService service;
#Autowired
public BeanUnderTest(ThirdPartyService ThirdPartyService) {
this.thirdPartyService = thirdPartyService;
}
}
By doing that, you can also mix up autowired and mocked services by autowiring into the test itself and then constructing the beans under test with the most useful mix of autowired and mocked beans.
A reasonable alternative is to use Spring profiles to define stub services. This is particularly useful when wish to use the same stubbed features in multiple tests:
#Service
#Primary
#Profile("test")
public class MyServiceStub implements MyService {
// ...
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SomeTest.Beans.class)
#ActiveProfiles({"test"})
public class SomeTest {
// ...
}
By using the #Primary annotation, it ensures that this stub bean will be used instead of any other bean implementing the MyService interface. I tend to use this approach for things like email services, where by changing profile, I'm able to switch between a real mail server and Wiser.

Overriding beans in Java-based spring configuration hierarchy

Let's assume we have an application that can be customized for some customers. The application is using Java-based spring configuration (a.k.a. Java config) for dependency injection. The application consists of modules and their submodules. Each module and submodule has its own #Configuration class which is imported by parent configuration using #Import. This creates the following hierarchy:
MainConfig
----------+---------------- ....
| |
ModuleAConfig ModuleBConfig
|--------------------|
| |
SubModuleA1Config SubModuleA2Config
For example ModuleAConfig looks like this:
#Configuration
#Import({SubModuleA1Config.class, SubModuleA2Config.class})
public class ModuleAConfig {
// some module level beans
}
Let's say that SubModuleA1Config defines bean someBean of type SomeBean:
#Configuration
public class SubModuleA1Config {
#Bean
public SomeBean someBean() { return new SomeBean(); }
}
Now I want to customize the application for Customer1 (C1) - I want to use C1SomeBean (extending SomeBean) instead of SomeBean as someBean.
How can I achieve this with minimum duplication?
One of my ideas was to prepare alternative hierarchy with C1Config inheriting from MainConfig, C1ModuleAConfig from ModuleAConfig and C1SubModuleA1Config from SubModuleA1Config. C1SubModuleA1Config would override someBean() method returning C1SomeBean. Unfortunately with Spring 4.0.6 I get something like:
Overriding bean definition for bean 'someBean': replacing [someBean defined in class C1SubmoduleA1Config] with [someBean defined in class SubModuleA1Config]
and indeed SomeBean class is returned from context instead of C1SomeBean. This is clearly not what I want.
Note that you cannot override #Import extending configuration classes.
If you want to select which imports to use at runtime, you could use a #ImportSelector instead.
However, #Configuration classes are not more that spring (scoped) managed factories so as you already have a factory method for someBean you don't need to go even further:
#Configuration
public class SubModuleA1Config {
#Autowired
private Environment env;
#Bean
public SomeBean someBean() {
String customerProperty = env.getProperty("customer");
if ("C1".equals(customerProperty))
return new C1SomeBean();
return new SomeBean();
}
}
Update
Using a ImportSelector:
class CustomerImportSelector implements ImportSelector, EnvironmentAware {
private static final String PACKAGE = "org.example.config";
private static final String CONFIG_CLASS = "SubModuleConfig";
private Environment env;
#Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String customer = env.getProperty("customer");
return new String[] { PACKAGE + "." + customer + "." + CONFIG_CLASS };
}
#Override
public void setEnvironment(Environment environment) {
this.env = environment;
}
}
#Configuration
#Import(CustomerImportSelector.class)
public class ModuleAConfig {
// some module level beans
}
However, as every customer has a a separate package, consider also using #ComponentScan. This will pick the configuration class present and don't need a extra configuration property.
#Configuration
#ComponentScan(basePackages="org.example.customer")
public class SubModuleA1Config {
#Autowired
private CustomerFactory customerFactory;
#Bean
public SomeBean someBean() {
return customerFactory.someBean();
}
}
public interface CustomerFactory {
SomeBean someBean();
}
#Component
public class C1CustomerFactory implements CustomerFactory {
#Override
public SomeBean someBean() {
return new C1SomeBean();
}
}

Categories