My problem is that I have application, which uses Spring profiles. Building application on server means that the profile is set to "wo-data-init". For other build there is "test" profile. When any of them is activated they are not supposed to run the Bean method, so I though this annotation should work:
#Profile({"!test","!wo-data-init"})
It seems more like it's running if(!test OR !wo-data-init) and in my case I need it to run if(!test AND !wo-data-init) - is it even possible?
In Spring 5.1.4 (Spring Boot 2.1.2) and above it is as easy as:
#Component
#Profile("!a & !b")
public class MyComponent {}
Ref: How to conditionally declare Bean when multiple profiles are not active?
Spring 4 has brought some cool features for conditional bean creation. In your case, indeed plain #Profile annotation is not enough because it uses OR operator.
One of the solutions you can do is to create your custom annotation and custom condition for it. For example
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
#Documented
#Conditional(NoProfilesEnabledCondition.class)
public #interface NoProfilesEnabled {
String[] value();
}
public class NoProfilesEnabledCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean matches = true;
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(NoProfileEnabled.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
String[] requiredProfiles = (String[]) value;
for (String profile : requiredProfiles) {
if (context.getEnvironment().acceptsProfiles(profile)) {
matches = false;
}
}
}
}
}
return matches;
}
}
Above is quick and dirty modification of ProfileCondition.
Now you can annotate your beans in the way:
#Component
#NoProfilesEnabled({"foo", "bar"})
class ProjectRepositoryImpl implements ProjectRepository { ... }
I found a better solution
#Profile("default")
Profile default means no foo and no bar profiles.
Related
I am trying to implement a Spring condition that will load my bean only if there is no other beans for a certain class.
The desired behavior is similar to "#ConditionalOnMissingBean" but without using spring-boot.
I am using Spring versions 5.3.13.
Is that possible? thanks.
I found a solution by digging around spring-boot source code.
ConditionalOnBean will only work on beans defined inside a configuration (bean methods).
This is also recommended by spring-boot java doc for #ConditionalOnBean
The condition can only match the bean definitions that have been processed by the
application context so far and, as such, it is strongly recommended to use this
condition on auto-configuration classes only. If a candidate bean may be created by
another auto-configuration, make sure that the one using this condition runs after.
Here is the basics of the solution I came up with, this may be improved but the basics operate well.
The condition:
#Slf4j
class MissingBeanCondition implements ConfigurationCondition {
#Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
var targetBeanType = metadata.getAnnotations()
.get(ConditionalOnMissingBean.class)
.getValue("value", Class.class)
// TODO throw a more informative error
.orElseThrow(() -> new RuntimeException("Failed to evaluate MissingBeanCondition"));
try {
context.getBeanFactory().getBean(targetBeanType);
} catch (NoSuchBeanDefinitionException e) {
return true;
}
return false;
}
}
The annotation:
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
#Conditional(MissingBeanCondition.class)
public #interface ConditionalOnMissingBean {
Class<?> value();
}
Usage example:
#Bean
#Singleton
#ConditionalOnMissingBean(Provider.class)
public Provider myClass() {
return new DefaultProvider();
}
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 the following classes:
#Component
#ConifgurationProperties("redis")
public class RedisProperties {
private List<String> hosts;
// getters, setters
}
#Component
public class StaticRedisHostsProvider implements RedisHostsProvider {
private final RedisProperties redisProperties;
public StaticRedisHostsProvider(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}
#Override
public List<String> getAll() {
return redisProperties.getHosts();
}
}
#Component
public DiscoveryBasedRedisHostsProvider { ... }
I want StaticRedisHostsProvider to be used if redis.hosts property is specified, DiscoveryBasedRedisHostsProvider otherwise.
I could annotate StaticRedisHostsProvider with #ConditionalOnProperty(prefix = "redis", name = "hosts"), but there is no similar #ConditionalOnMissingProperty annotation for using with DiscoveryBasedRedisHostsProvider.
I tried to use #ConditionalOnExpression("#redisProperties.hosts.empty"), but it doesn't work for some reason:
Description:
A component required a bean named 'redisProperties' that could not be found.
Action:
Consider defining a bean named 'redisProperties' in your configuration.
Is there some way to fix that (maybe with #Order or similar annotations)?
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration.
#Conditional annotations are executed very early in during the application startup. Properties sources are already loaded but ConfgurationProperties beans are not yet created. However we can work around that issue by binding properties to Java POJO ourselves.
First I introduce a functional interface which will enable us to define any custom logic checking if properties are in fact present or not. In your case this method will take care of checking if the property List is empty or null.
public interface OptionalProperties {
boolean isPresent();
}
Now let's create an annotation which will be metannotated with Spring #Conditional and allow us to define custom parameters. prefix represents the property namespace and targetClass represents the configuration properties model class to which properties should be mapped.
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnConfigurationPropertiesCondition.class)
public #interface ConditionalOnConfigurationProperties {
String prefix();
Class<? extends OptionalProperties> targetClass();
}
And now the main part. The custom condition implementation.
public class OnConfigurationPropertiesCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
String prefix = mergedAnnotation.getString("prefix");
Class<?> targetClass = mergedAnnotation.getClass("targetClass");
// type precondition
if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
}
// the crux of this solution, binding properties to Java POJO
Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
// if properties are not present at all return no match
if (bean == null) {
return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
}
OptionalProperties props = (OptionalProperties) bean;
// execute method from OptionalProperties interface
// to check if condition should be matched or not
// can include any custom logic using property values in a type safe manner
if (props.isPresent()) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("Properties are not present.");
}
}
}
Now you should create your own configuration properties class implementing OptionalProperties interface.
#ConfigurationProperties("redis")
#ConstructorBinding
public class RedisProperties implements OptionalProperties {
private final List<String> hosts;
#Override
public boolean isPresent() {
return hosts != null && !hosts.isEmpty();
}
}
And then in Spring #Configuration class.
#Configuration
class YourConfiguration {
#ConditionalOnConfigurationProperty(prefix = "redis", targetClass = RedisProperties.class)
StaticRedisHostsProvider staticRedisHostsProvider() {
...
}
#ConditionalOnMissingBean(StaticRedisHostsProvider.class)
DiscoveryBasedRedisHostsProvider discoveryRedisHostsProvider() {
...
}
}
There are two downsides to this solution:
Property prefix must be specified in two locations: on #ConfigurationProperties annotation and on #ConditionalOnConfigurationProperties annotation. This can partially be alleviated by defining a public static final String PREFIX = "namespace" in your configuration properties POJO.
Property binding process is executed separately for each use of our custom conditional annotation and then once again to create the configuration properties bean itself. It happens only during app startup so it shouldn't be an issue but it still is an inefficiency.
I need to build mappings for classes (literally a Map<Class<?>, String>), which won't vary at runtime, and keeping things decoupled is a priority. Since I'm in a Spring application, I thought I'd use an annotation and ClassPathScanningCandidateComponentProvider more or less like so:
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Mapping {
String value();
}
And:
public class MappingLookUp {
private static final Map<Class<?>, String> MAPPING_LOOK_UP;
static {
Map<Class<?>, String> lookUp = new HashMap<>();
ClassPathScanningCandidateComponentProvider scanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
scanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Mapping.class));
for (BeanDefinition beanDefinition : scanningCandidateComponentProvider.findCandidateComponents("blah")) {
Class<?> clazz;
try {
clazz = Class.forName(beanDefinition.getBeanClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Mapping mapping = AnnotationUtils.getAnnotation(clazz, Mapping.class);
if (mapping == null) {
throw new IllegalStateException("This should never be null");
}
lookUp.put(clazz, mapping.value());
}
MAPPING_LOOK_UP = Collections.unmodifiableMap(lookUp);
}
public static String getMapping(Class<?> clazz) {
...
}
}
Although I believe this will work, this feels like:
a lot to put in a static initialization
a hacky use of the scanning component provider, even though it's commonly recommended for this purpose; BeanDefinition makes it sound like it's intended for finding Spring beans rather than general class definitions.
To be clear, the annotated values are data classes -- not Spring-managed beans -- so a BeanPostProcessor pattern doesn't fit, and indeed, that's why it feels awkward to use the scanning component provider that, to me, seems intended for discovery of Spring managed beans.
Is this the proper way to be implementing this pattern? Is it a proper application of the provider? Is there a feasible alternative without pulling in other classpath scanning implementations?
I will suggest this doesn't look like it is done in a very Spring-y way.
If I were to be doing this, I would utilize Spring's BeanPostProcessor or BeanFactoryPostProcessor. Both of these allow for introspection on all Bean's in Spring's BeanFactory, and would allow you to get away from the static-ness of your current setup, as the PostProcessors are just Spring Bean's themselves.
class MappingLookup implements BeanPostProcessor {
private final Map<Class<?>, String> lookup = new HashMap<>();
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// check bean's class for annotation...
// add to lookup map as necessary...
// make sure to return bean (javadoc explains why)
return bean;
}
public String getMapping(Class<?> clazz) {
// ...
}
// omitted other methods...
}
I asked a very similar question recently How to get list of Interfaces from #ComponentScan packages and finally implemented the first of suggested approaches.
You can see the code https://github.com/StanislavLapitsky/SpringSOAProxy see https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/service/ProxyableScanRegistrar.java and of course initialization annotation https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/annotation/ProxyableScan.java the key thing is to add #Import({ProxyableScanRegistrar.class})
The key code is
public class ProxyableScanRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// Get the ProxyableScan annotation attributes
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ProxyableScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages.length == 0) {
// If value attribute is not set, fallback to the package of the annotated class
basePackages = new String[]{((StandardAnnotationMetadata) metadata).getIntrospectedClass().getPackage().getName()};
}
I have 5 controllers and i would like to register an InitBinder to all of them.
I know i can add this code to each of them.
#InitBinder
public void initBinder(WebDataBinder binder)
{
binder.registerCustomEditor(StringWrapper.class, new StringWrapperEditor());
}
But i would like to define it only once (even create a bean of StringWrapperEditor and use it instead of creating new every time.)
I searched SO and some other places but didn't find any answear.
Is it even possible?
Im using spring 3.1.1 with java 1.6.
Though the initial question was about Spring 3.1, the following might be useful for those who use newer Spring versions.
One possible option is to move your #InitBinder to #ControllerAdvice, for example
#ControllerAdvice
class InitBinderControllerAdvice {
#InitBinder
fun initBinder(dataBinder: WebDataBinder) {
dataBinder.registerCustomEditor(
MLQueryOutputFormat::class.java,
StringToMLQueryOutputFormat()
)
dataBinder.registerCustomEditor(
IDatabaseOps.SortDirection::class.java,
StringToSortDirection()
)
}
}
Regarding ConfigurableWebBindingInitializer, even though it's quite a powerful thing, it requires additional configuration in terms of validation and etc. So pay attention to detail once implementing it. For instance, the following code does the job as per InitBinder, but lacks setting a Validator. As a result, the validation of the rest controller param annotated with #Validated didn't work:
#Configuration
class WebMvcConfig {
#Bean
fun configurableWebBindingInitializer(): ConfigurableWebBindingInitializer {
val initializer = ConfigurableWebBindingInitializer()
initializer.propertyEditorRegistrars = arrayOf(
PropertyEditorRegistrar {
it.registerCustomEditor(
MLQueryOutputFormat::class.java,
StringToMLQueryOutputFormat()
)
}, PropertyEditorRegistrar {
it.registerCustomEditor(
IDatabaseOps.SortDirection::class.java,
StringToSortDirection()
)
}
)
return initializer
}
}
To add validation, one could do the following:
#Bean
fun configurableWebBindingInitializer(
#Qualifier("defaultValidator") validator: Validator
): ConfigurableWebBindingInitializer {
val initializer = ConfigurableWebBindingInitializer()
initializer.validator = validator
...
Implement a PropertyEditorRegistrar which registers all your custom PropertyEditors. Then in your configuration add a ConfigurableWebBindingInitializer which you hookup with the created PropertyEditorRegistrar and hook it to your HandlerAdapter.
public class MyPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(StringWrapper.class, new StringWrapperEditor());
}
}
If you have a <mvc:annotation-driven /> tag in your configuration, the problem is that with this tag you cannot add the WebBindingInitializer to the adapter next to that there is already a ConfigurableWebBindingInitializer added to the pre-configured HandlerAdapter. You can use a BeanPostProcessor to proces and configure the bean.
public class MyPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
if (bean instanceof RequestMappingHandlerAdapter) {
WebBindingInitializer wbi = ((RequestMappingHandlerAdapter) bean).getWebBindingInitializer();
if (wbi == null) {
wbi = new ConfigurableWebBindingInitializer();
((RequestMappingHandlerAdapter) bean).setWebBindingInitializer(wbi);
}
if (wbi instanceof ConfigurableWebBindingInitializer) {
((ConfigurableWebBindingInitializer) wbi).setPropertyEditorRegistrar(new MyPropertyEditorRegistrar());
}
}
}
}
Requires a bit of work but it is doable. You could also implement your own WebBindingInitializer.
If you don't have the tag you can simply manually configure a RequestMappingHandlerAdapter and wire everything together.
Links
PropertyEditorRegistrar javadoc
ConfigurableWebBindingInitializer javadoc
Reference Guide link