In my spring.factories I have such a definition:
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
MyBootstrapConfiguration
In MyBootstrapConfiguration I have :
#Configuration
public class MyBootstrapConfiguration {
#Bean
#ConditionalOnMissingBean
public ApiClient apiClient() {
return someClient;
}
}
Now in my test (junit-5 and #SpringBootTest), I would like to override this bean. Notice the #ConditionalOnMissingBean... If I can hook somehow to a "before" my bootstrap is started, to provide that ApiClient, that method apiClient would obviously not be called and I would be happy.
Please notice that MyBootstrapConfiguration is not something I have control of - its an invariant to me. For non-bootstrap configuration this is easily doable, but is there a way to do this for bootstrap?
Given a test configuration class:
#Configuration
public class TestBootstrapConfiguration implements Ordered {
#Bean
public ApiClient testApiClient() {
return testApiClient;
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
In src/test/resources/META-INF/spring.factories
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
TestBootstrapConfiguration
Related
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
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.
I passed one day and a half looking for answer, but this thing is going to put me crazy!
My teammates and I are working on an project, based on springboot. I work specificaly on the administration part, which is a web administration.
There are mainly three layers on my project: Controllers which use Services which use Repositories.
I want my project work with #Transactional for the Service layer (we made some successful efforts until now to use only annotations for configuration).
But, it seems that it doesn't work: One of my service throws a RuntimeException and no rollback is done. I allready read all the proposition in the others sibling subjects. The only thing, related to my problem, that i'm not sure to do neatly is the contexts configuration. Eventhow, i'm not sure that it's really my problem.
I show you the actual configuration:
#SpringBootApplication
#EnableScheduling
#EnableTransactionManagement
public class Application extends SpringBootServletInitializer {
#Value("${ajp.port}")
private int ajpPort;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {};
tomcat.addAdditionalTomcatConnectors(createConnector(ajpPort));
return tomcat;
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return container -> {
ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/static/401.html");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/static/404.html");
container.addErrorPages(error401Page, error404Page);
};
}
#Bean
public EmailValidator emailValidator() {
return EmailValidator.getInstance();
}
private static Connector createConnector(int ajpPort) {
Connector connector = new Connector("AJP/1.3");
connector.setPort(ajpPort);
return connector;
}
}
The web config:
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Autowired
private RequestProcessingTimeInterceptor requestProcessingTimeInterceptor;
#Autowired
private CertificateInterceptor certificateInterceptor;
#Autowired
private ProfilesAuthorizationInterceptor profilesAuthorizationInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestProcessingTimeInterceptor);
registry.addInterceptor(certificateInterceptor);
registry.addInterceptor(profilesAuthorizationInterceptor);
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setExposeContextBeansAsAttributes(true);
resolver.setPrefix("/WEB-INF/");
resolver.setSuffix(".jsp");
return resolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/admin/css/**").addResourceLocations("/WEB-INF/admin/css/").setCachePeriod(CACHE_PERIOD);
registry.addResourceHandler("/admin/img/**").addResourceLocations("/WEB-INF/admin/img/").setCachePeriod(CACHE_PERIOD);
registry.addResourceHandler("/admin/js/**").addResourceLocations("/WEB-INF/admin/js/").setCachePeriod(CACHE_PERIOD);
registry.addResourceHandler("/admin/plugins/**").addResourceLocations("/WEB-INF/admin/plugins/").setCachePeriod(CACHE_PERIOD);
}
}
A Controler-like:
#RestController
#RequestMapping("/pathA")
public class ControlerA {
#Autowired
public ServiceA serviceA;
#RequestMapping(value = "{id}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public A getA(#PathVariable long id) {
return serviceA.getA(id);
}
}
A Service-like (interface + implémentation):
public interface ServiceA {
A getA(long id);
}
#Service
#Transactional
public class ServiceAImpl implements ServiceA {
#Autowired
public RepositoryA repositoryA;
public A getA(long id) {
(...)
A a = repositoryA.findOne(id);
a.updatesomething(something);
repositoryA.update(a);
doOtherThing(a); //throw RuntimeException
(...)
return a;
}
}
And the Repository:
#Repository
public interface RepositoryA extends JpaRepository<A, Long> {
(...)
}
Here is the configuration of the MySQL database:
# Configuration de la base de donnée
spring.datasource.url=jdbc:mysql://localhost/name_innodb
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.testOnBorrow=true
spring.datasource.validationQuery=SELECT 1
I know that repository transaction works by default (I saw it, when a SQLException happen). But in the service layer, nothing happen (cf. the throwing exception line) ; when the exception is thrown, the update is done and not rollback. Then it mean that my #Transactional is ignored.
Edit :
I manage to get a transaction like I want, adding #Transactional on the method getA(...) of the Controller. It works, but it's not the place to manage Transaction.
Then my question is: How can I make it work?
Ok, after some days of brainstorming, I found!
The only reasonnable answer is to take care about your Configuration class. My problem was only a crossover configuration problem which leaded to a DispatcherServlet configuration who caused the mess.
Related Subject: For web MVC Spring app should #Transactional go on controller or service?
Edit:
I add some details because it'll be hard to find some information in order to separate context. And I'm still calibrating the configuration because there's no complete and exhaustive information about all the spring's annotations.
You could create parent and child context like this:
#SpringBootApplication
#ComponentScan({"com.mycompany.service", "com.mycompany.interceptors","com.mycompany.manager"})
#PropertySource("file:config/application.properties")
public class ParentConfig{
public static void main(String[] args) {
new SpringApplicationBuilder()
.parent(ParentConfig.class)
.child(ChildConfig1.class, ChildConfig2.class, ChildConfig3.class, ..., ChildConfigN.class)
.run(args);
}
(...)
}
I'm still wondering why I must add the #PropertySource in order children are aware of property values, why "classpath:path" have not work in #PropertySource, why I have to add a static PropertySourcesPlaceholderConfigurer for using #Value in my children (before I do that, i.e without this hierarchical contexts, every context was aware of the properties)
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
and I'm still playing with annotations in order every configuration work.
Edit:
I finally have found something in order to work correctly with spring configuration: Different configurations must respect packaging hierarchy.
I stop working with parent and child configuration and let spring work. I ordonate my different config class like this:
MainConfig
|
|__________my.package.mvc.MVCConfig
|
|__________my.package.schedulers.SchedulerConfig
|
|
and so on..
And in my MainConfig I add:
#ComponentScan({"my.package.mvc", "my.package.services", "my.package.interceptors","my.package.managers", "my.package.schedulers"})
And everything is good now! Mostly, MVCConfig can not create conflict with services, because of the different hierarchy.
On a project I'm currently working on, we have the need for multiple profiles, i.e. "default" and "cloud".
both DefaultContext and CloudContext contains the same bean definitions
We are using PCF(Pivotal Cloud Foundry)
we have created an interface
public interface Config {
public DataSource getDataSource();
public SomeService getService();
}
Then implement each profile with this interface
#Primary
#Configuration
#Profile("default")
public class DevConfig implements Config
{
public DataSource getDataSource() {
// create and return production datasource
}
public SomeService getService() {
// Create and return production service
}
}
And then do the same for cloud.
#Configuratio
#Profile("cloud")
public class CloudConfig extends AbstractCloudConfig implements Config
{
public DataSource getDataSource() {
// create and return dummy datasource
}
public SomeService getService() {
// Create and return dummy service
}
}
And we are Autowiring in the service call, in processor file.
#Service("processor")
public class Processor {
#Autowired Config dsConfig;
public object get(int Number)
{
return dao.get(Number,dsConfig.getDataSource());
}
}
If we deploy in PCF, its working fine, as the profile is cloud. If we are running in local, it should get the default profile, but dsConfig is null.
Could you please help on this.
#Configuration classes aren't availalbe for autowiring.
As #spencergibb pointed out in the comment you need to tell the container to make this classes available for autowiring.
For that annotate them with #Component.
Something like this:
#Component
#Profile("default")
public class DevConfig implements Config
{
public DataSource getDataSource() {
// create and return production datasource
}
public SomeService getService() {
// Create and return production service
}
}
In case it still doesn't work, check the following two points:
Do you have the configs (DevConfig and Cloudconfig) in different packages so the ContextScan doesn't find it?
Are you running in another profile locally? (like Dev).
You can put this snipped to your code (its from JHipster) to log the active profiles.
#Autowired
private Environment env;
/**
* Initializes Application.
* <p/>
* Spring profiles can be configured with a program arguments --spring.profiles.active=your-active-profile
* <p/>
*/
#PostConstruct
public void initApplication() throws IOException {
if (env.getActiveProfiles().length == 0) {
log.warn("No Spring profile configured, running with default configuration");
}
else {
log.info("Running with Spring profile(s) : {}", Arrays.toString(env.getActiveProfiles()));
}
}
I'd rather autowire datasource and service classes instead of configuration class.
In this way you wouldn't need any instance of configuration and directly autowire whatever class you want.
So the classes will look like below.
Default Config:
#Primary
#Configuration
#Profile("default")
public class DevConfig implements Config
{
#Bean
public DataSource getDataSource() {
// create and return production datasource
}
#Bean
public SomeService getService() {
// Create and return production service
}
}
Cloud Config:
#Configuration
#Profile("cloud")
public class CloudConfig extends AbstractCloudConfig implements Config
{
#Bean
public DataSource getDataSource() {
// create and return dummy datasource
}
#Bean
public SomeService getService() {
// Create and return dummy service
}
}
Processor Class:
#Service("processor")
public class Processor {
#Autowired
private DataSource dataSource;
public object get(int Number)
{
return dao.get(Number,datasource);
}
}
I'm unable to get #Profile to work on a #Bean, but it works fine on the #Configuration class. In my module tests I correctly get the MyServiceStub autowired in the first example, but not in the second.
Does anyone know why?
This works:
MyConfig.java
#Configuration
#Import({StubConfig.class, RealConfig.class}
public class MyConfig {}
StubConfig.java
#Profile({"featureTest", "moduleTest"})
#Configuration
public class StubConfig {
#Bean
public MyService myService() {
return new MyServiceStub();
}
}
RealConfig.java
#Profile({"!featureTest", "!moduleTest"})
#Configuration
public class RealConfig {
#Bean
public MyService myService() {
return new MyService();
}
}
But this doesn't work:
MyConfig.java
#Configuration
public class MyConfig {
#Bean
#Profile({"featureTest", "moduleTest"})
public MyService myServiceStub() {
return new MyServiceStub();
}
#Bean
#Profile({"!featureTest", "!moduleTest"})
public MyService myService() {
return new MyService();
}
}
Instead of using #Profile approach, you could try using the #Conditional to have conditional bean creation.
Or have an explicit if-stmt to check what is the activeProfile. And you can create the Bean instance depending on the activeProfile. This approach I think would mirror what could be done in Spring XML using their expression language. But the #Conditional seems better fit for you.