I have been searching for this for a while.
Till now, I found this blog very useful but did not solve my problem.
I want to #Autowired a bean only if a flag is true, else I want that to be null
Use Case:
If one of the DB is under maintenance, I do not want my app to fail.
#Bean("sqlDatabase")
public Database getSqlDatabase(#Value("${datasource.sql.url}") String url,
#Value("${datasource.sql.username}") String username, #Value("${datasource.sql.password}") String password,
#Value("${datasource.poolsize.min}") int minPoolSize, #Value("${datasource.poolsize.max}") int maxPoolSize,
#Value("${database.enable-sql}") boolean isSqlEnabled) {
if (isSqlEnabled)
return Database.builder().url(url).pool(minPoolSize, maxPoolSize).username(username).password(password)
.build();
else
return null;
}
Now, in this case, its throwing error as I cannot autowire a null bean.
I wanted to use #Conditional but my case is a bit complex. I already need all 3 databases to be updated. I just want to skip one of them if conditions are not met.
You can use profiles.
One profile for every database
db1
db2
db3
Than annotate the bean class or the bean method with the profile that must be activated to use that bean like
#Profile("db1")
#Bean("db1")
public Database getSqlDatabase(...){...}
When you start your app, beans annotated with #Profile will only be created, if the regarding profile is activated.
You activate a profile by setting the property 'spring.profiles.active'.
To activate db1 and db2 :
spring.profiles.active=db1,db3
You can set that property in a properties file or as a command line parameter.
Profiles give you a lot of flexibility to change you spring context by configuration
you can annotate many beans with the same profile
you can annotate a configuration class with a profile
you can use profile specific property files
you can use many profiles in one #Profile annotations. Logical 'or' will be used, so a bean annotated with #Profile("db1","db2") will be created if profile 'db1' is active or profile 'db2' is active
if you want something else than 'or' you can use #Conditional to define your own logic
Please note : If you use do not use component scan or xml configuration, the annotation #Profile at a bean class has no effect. You need to annotate the bean method with #Profile or the whole configuration class instead.
We can leverage #Conditional property during the initial component scan to avoid error while initializing based on the environment properties.
A valid use case would be enable a repository to be scanned for bean initialization when the environment db.enabled property is true
example:
https://javapapers.com/spring/spring-conditional-annotation/
Conditional helper class
public class DocumentDBInitializerPresence implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
DocumentDBInitializerInfo employeeBeanConfig = null;
try {
employeeBeanConfig = (DocumentDBInitializerInfo)context.getBeanFactory().getBean("DocumentDBInitializerInfo");
} catch(NoSuchBeanDefinitionException ex) {
System.out.println("BEAN NOT FOUND:: " + employeeBeanConfig != null );
}
System.out.println("BEAN FOUND :: " + employeeBeanConfig != null );
boolean matches = Boolean.valueOf(context.getEnvironment().getProperty("db.enabled"));
System.out.println("CONFIG ENABLED :: " + employeeBeanConfig != null );
return employeeBeanConfig != null && matches;
}
}
Using in service
#Service
#RefreshScope
#Conditional(value= DocumentDBInitializerPresence.class)
public class RepositoryDocumentDB {
private static Logger LOGGER = LoggerFactory.getLogger(RepositoryDocumentDB.class);
public static final String DOCUMENT_DB_LOCAL_HOST = "localhost";
DocumentDBInitializerInfo documentDbConfig;
#Autowired
public RepositoryDocumentDB(final DocumentDBInitializerInfo documentDbConfig) {
this.documentDbConfig = documentDbConfig;
}
}
This will not throw error on application startup if you are not autowiring
RepositoryDocumentDB anywhere yet and the db.enabled is set to false.
Hope this helps!
Related
I need to initialize a Configuration based on some other bean value that I am fetching from database,
how can we refer the bean in #ConditionalOnProperty value attribute ?
Or is there any other way to achieve the same.
// Bean that will query database
#Bean
public String checkAndCreate()
{
// Logic to Query DB
return "true";
}
Want to Refer the bean checkAndCreate in value
#ConditionalOnPropery(value = "", havingValue = "true")
#Configuration
public class MyConfiguration
{
// Some configuration Code
}
Don't use #ConditionalOnProperty, use #Conditional with a custom condition instead. The ConditionContext should give you access to this bean. You may need to use #DependsOn on to ensure that the class providing the string bean is already initialized.
I'm running a PoC around replacing bean injection at runtime after a ConfigurationProperties has changed. This is based on spring boot dynamic configuration properties support as well summarised here by Dave Syer from Pivotal.
In my application I have a simple interface implemented by two different concrete classes:
#Component
#RefreshScope
#ConditionalOnExpression(value = "'${config.dynamic.context.country}' == 'it'")
public class HelloIT implements HelloService {
#Override
public String sayHello() {
return "Ciao dall'italia";
}
}
and
#Component
#RefreshScope
#ConditionalOnExpression(value = "'${config.dynamic.context.country}' == 'us'")
public class HelloUS implements HelloService {
#Override
public String sayHello() {
return "Hi from US";
}
}
application.yaml served by spring cloud config server is:
config:
name: Default App
dynamic:
context:
country: us
and the related ConfigurationProperties class:
#Configuration
#ConfigurationProperties (prefix = "config.dynamic")
public class ContextHolder {
private Map<String, String> context;
Map<String, String> getContext() {
return context;
}
public void setContext(Map<String, String> context) {
this.context = context;
}
My client app entrypoint is:
#SpringBootApplication
#RestController
#RefreshScope
public class App1Application {
#Autowired
private HelloService helloService;
#RequestMapping("/hello")
public String hello() {
return helloService.sayHello();
}
First time I browse http://locahost:8080/hello endpoint it returns "Hi from US"
After that I change country: us in country: it in application.yaml in spring config server, and then hit the actuator/refresh endpoint ( on the client app).
Second time I browse http://locahost:8080/hello it stills returns "Hi from US" instead of "ciao dall'italia" as I would expect.
Is this use case supported in spring boot 2 when using #RefreshScope? In particular I'm referring to the fact of using it along with #Conditional annotations.
This implementation worked for me:
#Component
#RefreshScope
public class HelloDelegate implements HelloService {
#Delegate // lombok delegate (for the sake of brevity)
private final HelloService delegate;
public HelloDelegate(
// just inject value from Spring configuration
#Value("${country}") String country
) {
HelloService impl = null;
switch (country) {
case "it":
this.delegate = new HelloIT();
break;
default:
this.delegate = new HelloUS();
break;
}
}
}
It works the following way:
When first invocation of service method happens Spring creates bean HelloDelegate with configuration effective at that moment; bean is put into refresh scope cache
Because of #RefreshScope whenever configuration is changed (country property particularly in this case) HelloDelegate bean gets cleared from refresh scope cache
When next invocation happens, Spring has to create bean again because it does not exist in cache, so step 1 is repeated with new country property
As far as I watched the behavior of this implementation, Spring will try to avoid recreating RefreshScope bean if it's configuration was untouched.
I was looking for more generic solution of doing such "runtime" implementation replacement when found this question. This implementation has one significant disadvantage: if delegated beans have complex non-homogeneous configuration (e.g. each bean has it's own properties) code becomes lousy and therefore unsafe.
I use this approach to provide additional testability for artifacts. So that QA would be able to switch between stub and real integration without significant efforts. I would strongly recommend to avoid using such approach for business functionality.
I have a Spring 4 JUnit test which should verify only a particular part of my application.
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:context-test.xml")
#ActiveProfiles("test")
public class FooControllerIntegrationTest {
...
}
So I don't want to configure and instantiate all those beans which are actually aren't involved into the scope of my test. For example I don't want to configure beans which are used in another controller which I am not going to test here.
However, because I don't want to narrow component-scan pathes, I get "No qualifying bean of type" exception:
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type [...
Is any way how to ignore such missed definitions if I certainly sure that they aren't involved into the functionality I am testing?
Is any way how to ignore such missed definitions if I certainly sure that they aren't involved into the functionality I am testing?
No, there is no automated or built-in mechanism for such a purpose.
If you are instructing Spring to load beans that have mandatory dependencies on other beans, those other beans must exist.
For testing purposes, the best practices for limiting the scope of which beans are active include modularization of your config (e.g., horizontal slicing that allows you to selectively choose which layers of your application are loaded) and the use of bean definition profiles.
If you're using Spring Boot, you can then also make use of "testing slices" or #MockBean/#SpyBean in Spring Boot Test.
However, you should keep in mind that it's typically not a bad thing to load beans that you are not using in a given integration test, since you are (hopefully) testing other components that in fact need those beans in other test classes within your test suite, and the ApplicationContext would then be loaded only once and cached across your different integration testing classes.
Regards,
Sam (author of the Spring TestContext Framework)
I have found a way how to automatically mock absent bean definitions.
The core idea is to create own BeanFactory:
public class AutoMockBeanFactory extends DefaultListableBeanFactory {
#Override
protected Map<String, Object> findAutowireCandidates(final String beanName, final Class<?> requiredType, final DependencyDescriptor descriptor) {
String mockBeanName = Introspector.decapitalize(requiredType.getSimpleName()) + "Mock";
Map<String, Object> autowireCandidates = new HashMap<>();
try {
autowireCandidates = super.findAutowireCandidates(beanName, requiredType, descriptor);
} catch (UnsatisfiedDependencyException e) {
if (e.getCause() != null && e.getCause().getCause() instanceof NoSuchBeanDefinitionException) {
mockBeanName = ((NoSuchBeanDefinitionException) e.getCause().getCause()).getBeanName();
}
this.registerBeanDefinition(mockBeanName, BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition());
}
if (autowireCandidates.isEmpty()) {
final Object mock = mock(requiredType);
autowireCandidates.put(mockBeanName, mock);
this.addSingleton(mockBeanName, mock);
}
return autowireCandidates;
}
}
It also should be registered by creating own AbstractContextLoader implementation, based on the GenericXmlWebContextLoader. Unfortunately the latter one has a final loadContext(MergedContextConfiguration mergedConfig) method, so it is needed to fully copy its implementation (say into class AutoMockGenericXmlWebContextLoader) with one difference:
GenericWebApplicationContext context =
new GenericWebApplicationContext(new AutoMockBeanFactory());
No it can be used in the test:
#ContextConfiguration(
value = "classpath:context-test.xml",
loader = AutoMockGenericXmlWebContextLoader.class)
If you don't narrow your component-scan, then usually you would have all the beans available to the test, EXCEPT some specific ones that become available conditionally (e.g. beans defined by spring-batch)
In this case, one option that has worked for me is to mark such dependencies and components as #Lazy. This will make sure that they will only be loaded when needed. Note that (depending on scenario) you may have to mark both the #Autowired dependency and the #Component as #Lazy
Like the OP posted, here is the annotation context equivalent to inject any mock missing beans:
context = new CustomAnnotationConfigApplicationContext(SpringDataJpaConfig.class);
public class CustomAnnotationConfigApplicationContext extends AnnotationConfigApplicationContext {
public CustomAnnotationConfigApplicationContext() {
super(new AutoMockBeanFactory());
}
public CustomAnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
this.register(annotatedClasses);
this.refresh();
}
}
public class AutoMockBeanFactory extends DefaultListableBeanFactory {
#Override
protected Map<String, Object> findAutowireCandidates(final String beanName, final Class<?> requiredType, final DependencyDescriptor descriptor) {
String mockBeanName = Introspector.decapitalize(requiredType.getSimpleName());
Map<String, Object> autowireCandidates = new HashMap<>();
try {
autowireCandidates = super.findAutowireCandidates(beanName, requiredType, descriptor);
} catch (UnsatisfiedDependencyException e) {
if (e.getCause() != null && e.getCause().getCause() instanceof NoSuchBeanDefinitionException) {
mockBeanName = ((NoSuchBeanDefinitionException) e.getCause().getCause()).getBeanName();
}
this.registerBeanDefinition(mockBeanName, BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition());
}
if (autowireCandidates.isEmpty()) {
System.out.println("Mocking bean: " + mockBeanName);
final Object mock = Mockito.mock(requiredType);
autowireCandidates.put(mockBeanName, mock);
this.addSingleton(mockBeanName, mock);
}
return autowireCandidates;
}
}
I have some beans (of multiple types, CDI, #Stateless and #Singleton beans). Some of their fields shall get injected from database values.
public class MyBean {
#Inject
#DbConfigValue(MyConfig.HOST)
String host;
}
So I added a custom #Qualifier (DbConfigValue) used by a Producer. The producer reads and caches config values from a database and injects them into the beans.
#Singleton
#Lock(LockType.READ)
public class Configuration {
#Produces
#Dependent
#DbConfigValue
public String getDbConfigValue(InjectionPoint point) {
// get key for the config-value from qualifier-annotation of the injected field
String key = point.getAnnotated().getAnnotation(DbConfigValue.class).value();
// i have read+cached database config values in #PostConstruct before
return cachedConfigValues.get(key);
}
}
This works well for initial injection / bean construction. Some web tutorials out there are suggesting this approach.
Now, I think it is reasonable to assume that config values, if stored in DB, might change at runtime. So, whenever an admin changes a database config value, I currently do fire a CDI-event.
Question: is there any way to re-inject values into fields of already-initialized bean-instances? Or is injection always related to instance-creation only?
E.g. I had s.th. similar to this in mind:
public class MyEventListener {
#Inject
BeanManager beanManager;
#Asynchronous
public void onDbConfigValueChangedEvent (#Observes(during = TransactionPhase.AFTER_SUCCESS) DbConfigValueChangedEvent event) {
try {
// could be filtered by custom qualifier:
Set<Bean<?>> beans = beanManager.getBeans(Object.class,new AnnotationLiteral<Any>() {});
for (Bean<?> bean : beans) {
Set<InjectionPoint> points = bean.getInjectionPoints();
// What now? javax.enterprise.inject.spi.Bean is the
// bean-representation only.
// Can I somehow resolve the actual bean-instances here?
// Then update Field via Reflection?
}
}
catch(Exception e){
// ...
}
}
}
I also considered DeltaSpike which has some methods for injection-control. However, I did only find methods to inject into new bean instances, or even with new- or null-CreationalContexts (beans not CDI-managed afterwards)
Please note: I am aware that I can solve this specific use-case by injecting the configuration and explicitly getting the current values on each request like this:
public class MyBean {
#Inject
Configuration config;
public void someMethod(){
String host = config.getConfig(MyConfig.HOST);
// ...
}
}
However, I am wondering about the question in general: is there any support for re-injection? Or if not, do the specs (CDI or Java EE) forbid it?
Depending on how fast/slow your db is, this may be expensive. You could probably leverage some cacheing mechanism in the producer method.
Leverage on Instance injection mechanims, which lazily loads the actual injected bean.
Your Producer (Probably leveraging on some of cache to avoid db calls all the tome)
#Singleton
#Lock(LockType.READ)
public class Configuration {
#Produces
#RequestScoped //May fail if not in a request-context, but for ejb-calls, it is guaranteed to work as CDI has EJB Request Context
#DbConfigValue
public String getDbConfigValue(InjectionPoint point) {
// get key for the config-value from qualifier-annotation of the injected field
String key = point.getAnnotated().getAnnotation(DbConfigValue.class).value();
// i have read+cached database config values in #PostConstruct before
return cachedConfigValues.get(key);
}
}
And the injection points:
#SessionScoped
public class MyBean {
#Inject
#DbConfigValue(MyConfig.HOST)
private Instance<String> host;
public void doSomething() {
String myHost = host.get(); // of course will throw exception if value is failing. It will be resolved with every request.
}
}
Is there any way to load a class marked with #ConfigurationProperties without using a Spring Context directly? Basically I want to reuse all the smart logic that Spring does but for a bean I manually instantiate outside of the Spring lifecycle.
I have a bean that loads happily in Spring (Boot) and I can inject this into my other Service beans:
#ConfigurationProperties(prefix="my")
public class MySettings {
String property1;
File property2;
}
See the spring docco for more info http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-command-line-args
But now I need to access this bean from a class that is created outside of Spring (by Hibernate). The class is created so early in the app startup process that Spring Boot has not yet made the application context available through the classic lookup helper methods or roll-my-own static references.
So I instead want to do something like:
MySettings mySettings = new MySettings();
SpringPropertyLoadingMagicClass loader = new SpringPropertyLoadingMagicClass();
loader.populatePropertyValues(mySettings);
And have MySettings end up with all its values loaded, from the command line, system properties, app.properties, etc. Is there some class in Spring that does something like this or is it all too interwoven with the application context?
Obviously I could just load the Properties file myself, but I really want to keep Spring Boot's logic around using command line variables (e.g. --my.property1=xxx), or system variables, or application.properties or even a yaml file, as well as its logic around relaxed binding and type conversion (e.g. property2 is a File) so that it all works exactly the same as when used in the Spring context.
Possible or pipe dream?
Thanks for your help!
I had the same "issue".
Here is how I solved it in SpringBoot version 1.3.xxx and 1.4.1.
Let's say we have the following yaml configuration file:
foo:
apis:
-
name: Happy Api
path: /happyApi.json?v=bar
-
name: Grumpy Api
path: /grumpyApi.json?v=grrr
and we have the following ConfigurationProperties:
#ConfigurationProperties(prefix = "foo")
public class ApisProperties {
private List<ApiPath> apis = Lists.newArrayList();
public ApisProperties() {
}
public List<ApiPath> getApis() {
return apis;
}
public static class ApiPath {
private String name;
private String path;
public String getName() {
return name;
}
public void setName(final String aName) {
name = aName;
}
public String getPath() {
return path;
}
public void setPath(final String aPath) {
path = aPath;
}
}
}
Then, to do the "magic" things of Spring Boot programmatically (e.g. loading some properties in a static method), you can do:
private static ApisProperties apiProperties() {
try {
ClassPathResource resource;
resource = new ClassPathResource("/config/application.yml");
YamlPropertiesFactoryBean factoryBean;
factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setSingleton(true); // optional depends on your use-case
factoryBean.setResources(resource);
Properties properties;
properties = factoryBean.getObject();
MutablePropertySources propertySources;
propertySources = new MutablePropertySources();
propertySources.addLast(new PropertiesPropertySource("apis", properties));
ApisProperties apisProperties;
apisProperties = new ApisProperties();
PropertiesConfigurationFactory<ApisProperties> configurationFactory;
configurationFactory = new PropertiesConfigurationFactory<>(apisProperties);
configurationFactory.setPropertySources(propertySources);
configurationFactory.setTargetName("foo"); // it's the same prefix as the one defined in the #ConfigurationProperties
configurationFactory.bindPropertiesToTarget();
return apisProperties; // apiProperties are fed with the values defined in the application.yaml
} catch (BindException e) {
throw new IllegalArgumentException(e);
}
}
Here's an update to ctranxuan's answer for Spring Boot 2.x. In our situation, we avoid spinning up a Spring context for unit tests, but do like to test our configuration classes (which is called AppConfig in this example, and its settings are prefixed by app):
public class AppConfigTest {
private static AppConfig config;
#BeforeClass
public static void init() {
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setResources(new ClassPathResource("application.yaml"));
Properties properties = factoryBean.getObject();
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(propertySource);
config = binder.bind("app", AppConfig.class).get(); // same prefix as #ConfigurationProperties
}
}
The "magic" class you are looking for is PropertiesConfigurationFactory. But I would question your need for it - if you only need to bind once, then Spring should be able to do it for you, and if you have lifecycle issues it would be better to address those (in case they break something else).
This post is going into similar direction but extends the last answer with also validation and property placeholder resolutions.
Spring Boot Binder API support for #Value Annotations
#Value annotations in ConfigurationPropertys don't seem to bind properly though (at least if the referenced values are not part of the ConfigurationProperty's prefix namespace).
import org.springframework.boot.context.properties.bind.Binder
val binder = Binder.get(environment)
binder.bind(prefix, MySettings.class).get