Is it possible to use a system-property for the value of the #WebappConfiguration annotation? I have tried something like:
#WebAppConfiguration("#{systemProperties['webapproot'] ?: 'war'}")
But it doesn't seem to work at all. Is there any other way to do this through spring? I don't want to do this through our build tools as it would break executing our integration tests from our IDEs.
#WebAppConfiguration doesn't seems to support neither SpEL nor placeholder resolving.
I checked it in the following way: I tried to inject system property and resolve placeholder using #Value. When I tried to inject a nonexistent property #Value failed throwing SpelEvaluationException: EL1008E: Property or field 'asd' cannot be found on object of type 'java.util.Properties' and IllegalArgumentException: Could not resolve placeholder 'nonexistent_property' in value "${nonexistent_property}" respectively while #WebAppConfiguration's value had simply #{systemProperties.asd} and ${nonexistent_property} intialized as simple String's.
No, that is unfortunately not supported.
The value supplied to #WebAppConfiguration must be an an explicit resource path as stated in the class-level JavaDoc.
If you would like for us to consider dynamic evaluation of the value attribute, please feel free to open a JIRA issue to request such support.
Regards,
Sam (author of the Spring TestContext Framework)
I have found a solution to this problem. I wrote my own ContextBootsTraper by extending WebTestContextBootstrapper which Spring uses to load the WebAppConfiguration-Annotation value.
I extended the functionality to include a check if a certain system-property exists and overwrite the annotations value if it does:
public class SystemPropTestContextBootstrapper extends WebTestContextBootstrapper {
#Override
protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(mergedConfig.getTestClass(),
WebAppConfiguration.class);
if (webAppConfiguration != null) {
//implementation ommited
String webappDir = loadWebappDirFromSystemProperty();
if(webappDir == null) {
webappDir = webAppConfiguration.value();
}
return new WebMergedContextConfiguration(mergedConfig, webappDir);
}
return mergedConfig;
}
}
This class can then be used with the #BootstrapWith-Annotation:
#RunWith(SpringJUnit4ClassRunner.class)
#BootstrapWith(SystemPropTestContextBootstrapper.class)
#WebAppConfiguration("standardDir")
public class SomeTest {
}
This solution enables me to run the tests from my build-tool while maintaining the ability to run the tests from my IDE, which is great.
Related
I tried different approaches that I found here on Stackoverflow. This is the way I know how to use a mapper with MapStruct.
I have a Mapper class like this:
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DummyMapper {
DummyMapper INSTANCE = Mappers.getMapper(DummyMapper.class);
DummyResponseApi modelToApi(DummyResponse DummyResponseModel);
}
And my Unit test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {DummyMapper.class})
class ServiceClassTest {
private DummyService service;
}
My Service method that I want to test:
public DummyModelApi getSomething() {
DummyModel mapMe = new DummyModel();
return DummyMapper.INSTANCE.modelToApi(mapMe);
}
In older projects I did it the same way like this and had no problems. Now I'm using it in a new project with Spring Boot 2.5.6 and MapStruct 1.5.0.Beta1.
With using #SpringBootTest, as far as I know, Spring is actually starting the application and should create the Mapper class, so I don't understand, why the Mapper is always null?!
When I remove the DummyMapper.class in #SpringBootTest, an error appears with "Failed to load application context". That shows me, that the mapper is recognized.
Another thing that I find strange is, that I must use "unmappedTargetPolicy = ReportingPolicy.IGNORE" in my mapper, otherwise I get the error message "Unmapped properties could not found" or something, even though there is definetly the property with the same name in both models. This was always no problem in older projects, don't know why MapStruct is doing weird things now.
Omg, I found the solution. Now I know why I had to add "#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)" in my mapper.
Found the solution here: https://stackoverflow.com/a/69649688/8743351
Lombok needs to be before mapstruct in pom.xml/gradle.build.
For example, let's say that in my yml file I had a variable called indicator. And based on what the indicator variable's value was I want the code to do something different. How would I access the yml variable in the regular code and use it accordingly?
You can use this:
#Value("${your.path.yml.string}")
private String x;
YML:
your:
path:
yml:
string: hello
x will be "hello"
You need to use Spring Expression Language which says we should write it as
#Value("${spring.application.name}")
private String appName;
For Default value if key is not present in yaml/yml or properties file
#Value("${spring.application.name: defaultValue}")
private String appName;
The last way you can fetch value is using environment object
#Autowired
private Environment environment;
String appName = environment.get("spring.application.name");
You can add #Value annotation to any field in your beans.
#Value("$(path.to.your.variable)")
String myString;
Works with constructors as well.
public MyClass(#Value("$(path.to.your.variable)") String myString) {
You can use #Value on fields or parameters to assign the property to some variable.
Property example:
#Value("${indicator}")
private String indicator
Parameter example:
private void someMethod(#Value("${indicator}") String indicator) {
...
}
Then you can use indicator as you want.
Note: the class where you use #Value should be a Spring Component
With Spring-Boot, you have the file application.yml automatically provided for you. What you can do is adding a property in this file, for instance:
my.properties: someValue
Then, in one of your Spring Bean (either define with #Component or #Bean) you can retrieve this value using the annotation #Value. Then, do whatever you want with this variable.
For instance:
#Component
public class MyClass {
#Value("${my.properties"}
private String myProp; // will get "someValue" injected.
...
// Just use it in a method
public boolean myMethod() {
if(myProp.equals("someValue") {
// do something
} else {
// do something else
}
}
}
The best way to do this is not to have a tight coupling between Spring and your "normal" code at all, but instead to use the normal Java features like constructors along with Spring #Bean methods:
class MyService {
final String indicatorName;
MyService(String indicatorName) {
this.indicatorName = indicatorName;
}
}
... in your configuration class...
#Bean
MyService myService(#Value("indicator.name") String indicatorName) {
return new MyService(indicatorName);
}
Two notes for Spring Boot specifically:
The #ConfigurationProperties feature allows you to map properties onto structured Java data classes and is typically cleaner than using #Value by hand.
Always namespace properties that you define yourself to avoid future collisions with other libraries, so instead of indicator.name use company.project.indicator.name. I recommend looking at DataSourceProperties in Boot to see an example of how to set all this up.
More broadly, though, when you say that you want the code to "do something different", it sounds like the better option might be to have different classes that get activated under different circumstances. Both Spring profiles and Spring Boot auto-configuration help to do this.
The problem statement can be re-defined as Configuration Management in Java.
You should have a component like ConfigManager that gets instantiated as part of your application start up. That component will read a properties file, a yaml in your use case. Subsequent app logic will fetch these values from the ConfigManager exposed as simple key/value pairs.
All that is left for you to identify how to read and parse values from yaml file. This is already answered here:
Parse a YAML file
With Spring, you can have some kind of composed annotations. A prominent example is the #SpringBootApplication-annotation, which is a composite of an #Configuration, #EnableAutoConfiguration and #ComponentScan.
I am trying to get all Beans that are affected by a certain annotation, i.e. ComponentScan.
Following this answer, I am using the following code:
for (T o : applicationContext.getBeansWithAnnotation(ComponentScan.class).values()) {
ComponentScan ann = (ComponentScan) o.getClass().getAnnotation(ComponentScan.class);
...
}
which does not work, since not all beans, returned by getBeansWithAnnotation(ComponentScan.class) are indeed annotated with that annotation, since those that are e.g. annotated with #SpringBootApplication are (usually) not.
Now I am looking for some kind of generic way, to retrieve the value of an annotation, even when it is only added as piece of another annotation.
How can I do this?
It turns out, there is a utility set AnnotatedElementUtils which allows you to handle those merged annotations.
for (Object annotated : context.getBeansWithAnnotation(ComponentScan.class).values()) {
Class clazz = ClassUtils.getUserClass(annotated) // thank you jin!
ComponentScan mergedAnnotation = AnnotatedElementUtils.getMergedAnnotation(clazz, ComponentScan.class);
if (mergedAnnotation != null) { // For some reasons, this might still be null.
// TODO: useful stuff.
}
}
it may be CglibProxy. so you can not directly get the Annotation.
ClassUtils.isCglibProxyClass(o)
for more see this
edit,you can add your logic code. find the ComponentScan.
if (ClassUtils.isCglibProxyClass(o.getClass())) {
Annotation[] annotations = ClassUtils.getUserClass(o).getAnnotations();
for (Annotation annotation : annotations) {
ComponentScan annotation1 = annotation.annotationType().getAnnotation(ComponentScan.class);
// in my test code , ComponentScan can get here.for #SpringBootApplication
System.out.println(annotation1);
}
}
I know that with the #Profile annotation, you can tell Spring to only load a certain class when using the specified profile, like this:
#Configuration
#Profile("dev")
public class MyCustomConfigurationClass {
// this will only be instantiated when the "dev" profile is active
}
However I'm wondering if there's some equivalent way of doing that for a given application property / environment variable? Here's some pseudo code to illustrate what I want to do:
#Configuration
#OnlyInstantiateWhen(property = "${my.property}", value = "true")
public class MyCustomConfigurationClass {
// this would theoretically only be instantiated when
// the value of my.property is true, either in application.properties
// or in an environment variable
}
Is anything like this possible?
Take a look at implementation of your own condition or using #ConditionalOnProperty() like answers for [this question] suggest. (Conditional spring bean creation)
I have a annotation that does include several other annotations, pretty much like this one here:
#Component // Spring Component
#Interface OsgiService { boolean isFactory() }
meaning that all classes annotated with #OsgiService shall automatically also be annotated as #Component. Which works fine.
Now however, I'd like to add another annotation, that has a parameter which is dependent of the isFactory parameter of #OsgiService.
#Component // Spring Component
#Scope(isFactory() ? "prototype" : "singleton")
#Interface OsgiService { boolean isFactory() }
Which does not work. However, as isFactory property of an annotation requires to be a static value, shouldn't it be possible to have something like this?
I don't think this is possible.
You can create two annotations: #OsgiService and #OsgiServiceFactory