Is it possible to set a value from a field from the application.properties file?
I am looking for something like
#Mapping(target="version", expression="${application.version}")
StateDto stateToStateDto(State state);
where application.version=v1 is from the application.properties file.
Consider a "util service" like:
#Service
public class PropertyService {
#org.springframework.beans.factory.annotation.Value("${application.version}")
private String appVersion;
// accessors, more properties/stuff..
}
Then you can define your Mapping like:
#Mapper(// ..., ...
componentModel = "spring")
public abstract class StateMapper {
#Autowired
protected PropertyService myService;
#org.mapstruct.Mapping(target="version", expression="java(myService.getAppVersion())")
public abstract StateDto stateToStateDto(State state);
// ...
}
See also:
Mapstruct - How can I inject a spring dependency in the Generated Mapper class
Mapstruct Expressions
My minimal solution #github
As far as my knowledge goes, this is not possible. Mapstruct analyses the #Mapping annotation in compile time. And the annotation parameters require constants. So getting them from a file would not be possible.
You can always implement something in MapStruct that fulfills your needs. But I would go with a simple self-implemented mapper where you take the value from your version field in runtime from the environment.
This is not possible through MapStruct. However, a feature could be raised that would support some custom expression language that would use Spring #Value and inject that.
e.g.
#Mapping(target="version", expression="springValue(${application.version})")
StateDto stateToStateDto(State state);
and then MapStruct will generate something like:
#Component
public class StateMapperImpl {
#Value("${application.version}")
private String version;
// ...
}
Related
I am trying to instantiate a class passing a parameter by constructor drMessage, I am using the #RequiredArgsConstructor annotation, and some dependency injections using #Autowired as I show below, the problem is that when using the #RequiredArgsConstructor annotation it implements the default constructors internally, and I tried to build the constructors manually but the IDE tells me that the variable of those constructors has not been initialized, how could I solve it? Thanks
#RequiredArgsConstructor
#Service
public class ServiceImpl implements IService {
#Autowired
private final DtoMapper dtoMapper;
#Autowired
private final CDtoMapper cDtoMapper;
private DrMessage drMessage;
**other sentences**
}
Constructor Injection With Lombok
With Lombok, it's possible to generate a constructor for either all class's fields (with #AllArgsConstructor) or all final class's fields (with #RequiredArgsConstructor). Moreover, if you still need an empty constructor, you can append an additional #NoArgsConstructor annotation.
Let's create a third component, analogous to the previous two:
1.Example
#Component
#RequiredArgsConstructor
public class ThankingService {
private final Translator translator;
public String produce() {
return translator.translate("thank you");
}
}
The above annotation will cause Lombok to generate a constructor for us:
2.Example
#Component
public class ThankingService {
private final Translator translator;
public String thank() {
return translator.translate("thank you");
}
/* Generated by Lombok */
public ThankingService(Translator translator) {
this.translator = translator;
}
}
#Service annotated classes are Spring managed beans. You don't manually instantiate these classes. Spring does it for you.
These #Service annotated beans have default singleton scope. Which means this bean is only initialize once. So they are called stateless beans, or says they have a shared state. You do not crate a state or change the state of these kind of beans.
Note: Read this document about bean scopes.
In your ServiceImpl you only have two final state variables. Which means are the only required filed when ServiceImpl initialize, so only they will be included in #RequiredArgsConstructor.
Lombok works at compile time while Spring works at runtime.
So when you place a Lombok annotation of the constructor (#AllArgsConstructor, #RequiredArgsConstructor, etc) on your class (it can be a regular java class, spring bean, whatever), Lombok creates a constructor for you.
In this case, it will create a constructor for all final fields (see the documentation
As you see lombok doesn't take into consideration the #Autowired annotation,
Since you haven't placed #NonNull annotation on drMessage (check the documentation) it won't generate a constructor parameter for it, so your class will look like:
#RequiredArgsConstructor
#Service
public class ServiceImpl implements IService {
#Autowired
private final DtoMapper dtoMapper;
#Autowired
private final CDtoMapper cDtoMapper;
private DrMessage drMessage;
public ServiceImpl(DtoMapper dtoMapper, cDtoMapper cDtoMapper) {
this.dtoMapper = dtoMapper;
this.cDtoMapper = cDtoMapper;
}
**other sentences**
}
So if you want the lombok to generate a parameter for DrMessage make it final, or put a #NonNull annotation on it.
At this point the job of Lombok is done, in fact, you can exclude it from being available at runtime at all. Now regarding the spring part:
First of all, you say that you by yourself are trying to create the instance of ServiceImpl class, why? It's a spring bean (you've put a #Service annotation on it), so let Spring manage this class.
With this definition, placing #Autowired on the final field won't work in spring.
See this thread, so you should not place #Autowired on these fields.
Luckily Spring in its recent versions is smart enough to understand that if the class has a single constructor (in your case the one that you've generated with Lombok) spring will call it to create the instance of your class, so instead of field injection, you'll use constructor injection.
How can I enable a #Configuration class only if the corresponding #EnableCustomConfiguration annotation has been used?
To make it clear I am trying to recreate the behaviour of common SpringBoot annotations, like e.g. #EnableEurekaClient, #EnableWebSecurity and so on.
This is my enabler:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface EnableMultitenancy {}
Here my configuration properties:
#Getter
#Setter
#Validated
#ConfigurationProperties("multitenancy")
public class MultitenancyProperties {
#NotEmpty(message = "You must provide at least one tenant")
private List<Tenant> tenants;
}
and this is my Configuration class:
#Configuration
#Conditional("On EnableMultitenancy used")
#EnableConfigurationProperties(MultitenancyProperties.class)
public class MultitenancyConfiguration{
#Bean
public MyFirstBean first(MultitenancyProperties properties){
return new MyFirstBean(properties);
}
#Bean
public MySecondBean second(MultitenancyProperties properties){
return new MySecondBean(properties);
}
}
How can I write such a condition, e.g. the annotation has been used on a class/component?
org.springframework.context.annotation.ConditionContext interface shows theability of #Conditional.
ConditionContext contains five methods:
getRegistry
getBeanFactory
getEnvironment
getResourceLoader
getClassLoader
So it seems your cannot directly depend on annotation, eg: #EnableCaching. But you can depend on the bean related to them, eg: ProxyCachingConfiguration for EnableCaching.
But i think is not a good design. Usually we will declare our dependency, this makes component sperately, also make things easy. When user want to use our component, they don't need to add all of depended annotation, eg: #EnableEureka,#EnableWebSecurity, just # MultitenancyConfiguration.
In your MultitenancyConfiguration class, you just need to replace the current #Conditional annotation with #ConditionalOnClass(EnableMulitenancy.class).
Update: Sorry, I misunderstood. Why don't you just import the MultitenancyConfiguration class by using #Import(MultitenancyConfiguration.class) in your EnableMultitenancy class? This will guarantee that your MultitenancyConfiguration class is enabled whenever #EnableMultitenancy is used.
I have a class with custom annotation for one of class field:
public class Test {
#CustomAnnotation
private String name;
...
}
I just want to know if it possible to get Class<Test> by this annotation? Can't find any suitable api..
public Class<?> getOuterClass(CustomAnnotation annotation) {
...
}
#CustomAnnotation is declared as #Retention(RetentionPolicy.RUNTIME)
No, annotation does not store any data about where it was declared.
Also annotation can work just like any normal interface, so someone can implement annotation in class an make instances of it that were never used as annotations.
You need either include that information yourself, by adding parameter to annotation and then using it #CustomAnn(Test.class) or when reading annotations just remember and include that information yourself in some other object.
I have a class that I serialize and I use the #JsonIgnoreProperties at the class level to exclude some fields from it.
Lately I have an use case where I need those fields serialized.
Is there a way to make a writer/reader that ignores the annotation?
I was looking into #JsonView but it seems #JsonIgnoreProperties takes precedence over it.
#JsonFilter could help you in this case.
By defining custom json filter, Jackson will dynamically resolve filter given class uses, dynamically, allowing per-call reconfiguration of filtering.
You can find detailed explanation and usage example here
Some usefull information about dynamic ignoral you find here
I have come up with a solution, don't know if it the best one but gets the job done ...
I ended up using #JsonView.
So I have 2 views like this:
public class Views {
public static class Public { }
public static class Extended extends Public { }
}
and the default Spring mapper configured as
mapper.setConfig(mapper.getSerializationConfig().withView(Views.Public.class));
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
and the class looks like
public class Foo{
String name;
#JsonView(Views.Extended.class)
String title;
...}
By setting up a default view on the object mapper it causes it to ignore all the other not specified views. The fields with no annotation will always be serialized, as per config.
Then, when I need the whole class to be serialized I use:
objectMapper.writer().withView(Views.Extended.class).writeValueAsString(value);
Say I have an annotation with a property:
#Named(name = "Steve")
private Person person
and I want to create a compound annotation with several meta-annotations, including the one that takes a property
#Named
#AnotherAnnotation
#YetAnotherAnnotation
public #interface CompoundAnnotation {
...
}
Is there a way that I can pass properties to the compound annotation to one of the meta annotations?
Eg, something like this:
#CompoundAnnotation(name = "Bob")
private Person person;
that is equivalent to, but much more convenient than
#Named(name = "Bob")
#AnotherAnnotation
#YetAnotherAnnotation
private Person person;
Thanks!
PS apologies for my poor choice of an example annotation - I didn't have the javax.inject.#Named annotation in mind, just some arbitrary annotation that has properties.
Thank you everyone for your answers/comments.
It definitely seems to be the case that this is not possible. However, it just happens that there is a simple work-around for my case-in-point, which I will share in case it helps anyone:
I am working with Spring and want to create my own Annotations that have #Component as a meta-annotation, thus being autodetected by component scanning. However, I also wanted to be able to set the BeanName property (corresponding to the value property in #Component) so I could have custom bean names.
Well it turns out that the thoughtful guys at Spring made it possible to do just that - the AnnotationBeanNameGenerator will take the 'value' property of whatever annotation it is passed and use that as the bean name (and of course, by default, it will only get passed annotations that are #Component or have #Component as a meta-annotation). In retrospect this should have been obvious to me from the start - this is how existing annotations with #Component as a meta-annotation, such as #Service and #Registry, can provide bean names.
Hope that is useful to someone. I still think it's a shame that this is not possible more generally though!
It is a few years later now, and since you are using Spring, what you are asking for is sort of possible now using the #AliasFor annotation.
For example:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#SpringApplicationConfiguration
#ActiveProfiles("test")
public #interface SpringContextTest {
#AliasFor(annotation = SpringApplicationConfiguration.class, attribute = "classes")
Class<?>[] value() default {};
#AliasFor("value")
Class<?>[] classes() default {};
}
Now you can annotate your test with #SpringContextTest(MyConfig.class), and the amazing thing is that it actually works the way you would expect.
N.B. When you need to programmatically get the attribute values, the Spring automagical aliasing works only when you use AnnotatedElementUtils instead of AnnotationUtils, as the documentation says:
AnnotatedElementUtils defines the public API for Spring's meta-annotation programming model with support for annotation attribute overrides. If you do not need support for annotation attribute overrides, consider using AnnotationUtils instead.
Example:
final Named namedAnnotation = AnnotatedElementUtils.findMergedAnnotation(Person.class, Named.class);
final String name = namedAnnotation.name();
assertEquals("Steve", name);
Is there a way that I can pass properties to the compound annotation to one of the meta annotations?
I think the simple answer is "no". There is no way to ask Person what annotations it has on it and get #Named for example.
The more complex answer is that you can chain annotations but you would have to investigate these annotations via reflection. For example, the following works:
#Bar
public class Foo {
public static void main(String[] args) {
Annotation[] fooAnnotations = Foo.class.getAnnotations();
assertEquals(1, fooAnnotations.length);
for (Annotation annotation : fooAnnotations) {
Annotation[] annotations =
annotation.annotationType().getAnnotations();
assertEquals(2, annotations.length);
assertEquals(Baz.class, annotations[0].annotationType());
}
}
#Baz
#Retention(RetentionPolicy.RUNTIME)
public #interface Bar {
}
#Retention(RetentionPolicy.RUNTIME)
public #interface Baz {
}
}
However the following statement will return null:
// this always returns null
Baz baz = Foo.class.getAnnotation(Baz.class)
This means that any 3rd party class that is looking for the #Baz annotation won't see it.