I have developed a simple Annotation Interface
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomAnnotation {
String foo() default "foo";
}
then I test it annotating a Class
#CustomAnnotation
public class AnnotatedClass {
}
and call it using a method
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
logger.info(customAnnotation.foo());
}
and all works fine because it logs foo. I try also change the annotated class to #CustomAnnotation(foo = "123") and all works fine too, becuase it logs 123.
Now I want that the value passed to the annotation is retrieved by the application.properties, so I have changed my annotated class to
#CustomAnnotation(foo = "${my.value}")
public class AnnotatedClass {
}
but now the log returns the String ${my.vlaue} and not the value in application.properties.
I know that is possible use ${} instruction in annotation because I always use a #RestController like this #GetMapping(path = "${path.value:/}") and all works fine.
My solution on Github repository: https://github.com/federicogatti/annotatedexample
Spring Core-based approach
First off, I want to show you a standalone application that doesn't utilise Spring Boot auto-configurable facilities. I hope you will appreciate how much Spring does for us.
The idea is to have a ConfigurableBeanFactory set up with StringValueResolver which will be aware of our context (particularly, of the application.yaml properties).
class Application {
public static void main(String[] args) {
// read a placeholder from CustomAnnotation#foo
// foo = "${my.value}"
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String foo = customAnnotation.foo();
// create a placeholder configurer which also is a properties loader
// load application.properties from the classpath
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("application.properties"));
// create a factory which is up to resolve embedded values
// configure it with our placeholder configurer
ConfigurableListableBeanFactory factory = new DefaultListableBeanFactory();
configurer.postProcessBeanFactory(factory);
// resolve the value and print it out
String value = factory.resolveEmbeddedValue(foo);
System.out.println(value);
}
}
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#interface CustomAnnotation {
String foo() default "foo";
}
#CustomAnnotation(foo = "${my.value}")
class AnnotatedClass {}
Spring Boot-based approach
Now, I will demonstrate how to do it within your Spring Boot application.
We are going to inject ConfigurableBeanFactory (which has already been configured) and resolve the value similarly to the previous snippet.
#RestController
#RequestMapping("api")
public class MyController {
// inject the factory by using the constructor
private ConfigurableBeanFactory factory;
public MyController(ConfigurableBeanFactory factory) {
this.factory = factory;
}
#GetMapping(path = "/foo")
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String foo = customAnnotation.foo();
// resolve the value and print it out
String value = factory.resolveEmbeddedValue(foo);
System.out.println(value);
}
}
I don't like mixing up low-level Spring components, such as BeanFactory, in business logic code, so I strongly suggest we narrow the type to StringValueResolver and inject it instead.
#Bean
public StringValueResolver getStringValueResolver(ConfigurableBeanFactory factory) {
return new EmbeddedValueResolver(factory);
}
The method to call is resolveStringValue:
// ...
String value = resolver.resolveStringValue(foo);
System.out.println(value);
Proxy-based approach
We could write a method that generates a proxy based on the interface type; its methods would return resolved values.
Here's a simplified version of the service.
#Service
class CustomAnnotationService {
#Autowired
private StringValueResolver resolver;
public <T extends Annotation> T getAnnotationFromType(Class<T> annotation, Class<?> type) {
return annotation.cast(Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{annotation},
((proxy, method, args) -> {
T originalAnnotation = type.getAnnotation(annotation);
Object originalValue = method.invoke(originalAnnotation);
return resolver.resolveStringValue(originalValue.toString());
})));
}
}
Inject the service and use it as follows:
CustomAnnotation customAnnotation = service.getAnnotationFromType(CustomAnnotation.class, AnnotatedClass.class);
System.out.println(customAnnotation.foo());
You can't do something like directly as an annotation attribute's value must be a constant expression.
What you can do is, you can pass foo value as string like #CustomAnnotation(foo = "my.value") and create advice AOP to get annotation string value and lookup in application properties.
create AOP with #Pointcut, #AfterReturn or provided others to match #annotation, method etc and write your logic to lookup property for corresponding string.
Configure #EnableAspectJAutoProxy on main application or setting up by configuration class.
Add aop dependency: spring-boot-starter-aop
Create #Aspect with pointcut .
#Aspect
public class CustomAnnotationAOP {
#Pointcut("#annotation(it.federicogatti.annotationexample.annotationexample.annotation.CustomAnnotation)")
//define your method with logic to lookup application.properties
Look more in official guide : Aspect Oriented Programming with Spring
Make sure Annotated Class has #Component annotation along with #CustomAnnotation(foo = "${my.value}"), then Spring will recognize this class as Spring component and makes the necessary configurations to insert the value in.
You can use ConfigurableBeanFactory.resolveEmbeddedValue to resolve ${my.value} into the value in application.properties.
#CustomAnnotation(foo="${my.value}")
#lombok.extern.slf4j.Slf4j
#Service
public class AnnotatedClass {
#Autowired
private ConfigurableBeanFactory beanFactory;
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String fooValue = customAnnotation.foo().toString();
String value = beanFactory.resolveEmbeddedValue(fooValue);
log.info(value);
}
}
If you also want to resolve expressions you should consider using EmbeddedValueResolver.
EmbeddedValueResolver resolver = new EmbeddedValueResolver(beanFactory);
final String value = resolver.resolveStringValue(fooValue);
You can look at Spring's RequestMappingHandlerMapping to see how they do it, which is using a EmbeddedValueResolver. You can inject the bean factory into any spring component and then use it to build your own resolver:
#Autowired
public void setBeanFactory(ConfigurableBeanFactory beanFactory)
{
this.embeddedValueResolver = new EmbeddedValueResolver(beanFactory);
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String fooValue = customAnnotation.foo();
System.out.println("fooValue = " + fooValue);
String resolvedValue = embeddedValueResolver.resolveStringValue(fooValue);
System.out.println("resolvedValue = " + resolvedValue);
}
Assuming you set foo.value=hello in your properties, the output would look something like:
fooValue = ${foo.value}
resolvedValue = hello
I tested this with Spring Boot 2.0.2 and it worked as expected.
Keep in mind this is a minimal example. You would want to handle the error cases of missing annotations on the class and missing resolved value (if the value isn't set and there's no default).
To read property from application.propertie, one need to define PropertyPlaceholderConfigurer and map it with properties file.
XML based configuration:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations" value="classpath:application.properties" />
</bean>
For annotation based: one can use as below:
#Configuration
#PropertySource(
value{"classpath:properties/application.properties"},ignoreResourceNotFound=true)
public class Config {
/**
* Property placeholder configurer needed to process #Value annotations
*/
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Related
There is a usual configuration creating beans and performing an action using some of them.
The intention of initialAction is just execute print("Hello...") if certain conditions are in place (MyCondition.class). Obviously this return null is only required to keep the ceremony of beans creation and can be misleading for developers reading the code.
Is there a more elegant and convenient way to express the intention "run code after certain beans created if a condition is true"?
#Configuration
class App{
#Bean
#Conditional(MyCondition.class)
Object initialAction() {
var a = firstBean();
var b = secondBean();
print("Hello beans: $s, %s".formatted(a, b))
return null;
}
MyEntity firstBean(){
...
}
MyEntity secondBean(){
...
}
// other beans
}
There is an annotation #PostConstruct from javax or jakarta packages that are invoked once the dependency injection is done. Sadly, this cannot be combined with the #Conditional annotation.
You can, however, create a dedicated #Configuration + #Conditional class using #PostConstruct:
#Configuration
#Conditional(MyCondition.class)
public InitialActionConfiguration {
#Autowired
#Qualifier("firstBean")
private MyEntity firstBean;
#Autowired
#Qualifier("secondBean")
private MyEntity secondBean;
#PostConstruct
public void postConstruct() {
var a = firstBean;
var b = secondBean;
print("Hello beans: $s, %s".formatted(a, b));
return null;
}
// initializations in other #Configuration class
}
Once you do this, both MyEntity beans got initialized (if properly defined as beans) and this configuration class autowires them and prints them out only if the condition defined by MyCondition is satisfied.
I've been desperately trying to build an extension that requires information from both the JUnit5 extension model and the Spring-Boot Test framework. Specifically, I'd like to hook into the ApplicationContext creation process using a ApplicationContextInitializer and a custom annotation:
#Retention(RUNTIME)
#Target(TYPE)
#ContextConfiguration(initializers = CustomContextInitializer.class)
public #interface CustomAnnotation {
String someOption();
}
The test then looks like this:
#SpringBootTest
#CustomAnnotation(someOption = "Hello There")
public class SomeTest {
...
}
Now, how do I access the CustomAnnotation instance of the test class from within my CustomContextInitializer?
class CustomContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// How to access the Test Class here?
CustomAnnotation annotation = <Test Class>.getAnnotation(CustomAnnotation.class);
System.out.println(annotation.someOption());
}
}
Is it possible to somehow access the JUnit5 ExtensionContext during the creation of the ApplicationContext? It doesn't have to be from within a ApplicationContextInitializer. I just need a hook that is executed early enough so that I can inject some dynamically generated properties before the whole bean instantiation process actually starts.
Take a look at #DynamicPropertySource for injecting properties before the bean initialization. You can then use #RegisterExtension to register a custom extension that reads the annotation properties and makes them available through some method:
#CustomAnnotation(someOption = "Hello There")
public class SomeTest {
#RegisterExtension
static CustomExtension extension = new CustomExtension();
#DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("property.you.need",
() -> customExtension.getProperty());
}
}
public class CustomExtension implements BeforeAllCallback {
private String property;
public String getProperty() {
return property;
}
#Override
public void beforeAll(ExtensionContext context) throws Exception {
CustomAnnotation annotation = context.getRequiredTestClass()
.getAnnotation(CustomAnnotation.class);
property = annotation.someOption();
}
}
I know it doesn’t answer the question about hooking JUnit 5 with Spring initialization mechanism, but if dynamic properties is all you need, this solves exactly that.
You can implement your own TestExecutionListener and use it to access annotation you mentioned
#Retention(RUNTIME)
#Target(ElementType.TYPE)
#TestExecutionListeners(listeners = CustomTestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#interface CustomAnnotation {
String someOption();
}
static class CustomTestExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
final CustomAnnotation annotation = testContext.getTestClass().getAnnotation(CustomAnnotation.class);
System.out.println(annotation.someOption());
}
}
I am turning old xml/java configuration into pure java config. In xml I used injection of parameters into configuration file like this:
<bean class="com.project.SpringRestConfiguration">
<property name="parameters" ref="parameters" />
</bean>
#Configuration
public class SpringRestConfiguration {
private Parameters parameters;
public void setParameters(Parameters parameters) {
this.parameters = parameters;
}
// #Bean definitions
...
}
Is it possible to inject Parameters in javaconfig? (Without the need of using autowiring!)
#Configuration
#Import(SpringRestConfiguration.class)
EDIT:
With #Import I can't see any chance to inject Parameters into SpringRestConfiguration
Basically you would need to use #Autowired but you can still use a name and not type interpretation like this:
#Configuration
public class SpringRestConfiguration {
#Autowired
#Qualifier("parameters") // Somewhere in your context you should have a bean named 'parameters'. It doesn't matter if it was defined with XML, configuration class or with auto scanning. As long as such bean with the right type and name exists, you should be good.
private Parameters parameters;
// #Bean definitions
...
}
This solves the confusion problem you mentioned when using #Autowired - there's no question here which bean is injected, the bean that is named parameters.
You can even do a little test, leave the parameters bean defined in the XML as before, use #Autowired, see that it works. Only then migrate parameters to #Configuration class.
In my answer here you can find a complete explanation of how you should migrate XML to #Configuration step by step.
You can also skip the private member altogether and do something like this:
#Configuration
public class SpringRestConfiguration {
#Bean
public BeanThatNeedsParamters beanThatNeedsParamters (#Qualifier("parameters") Parameters parameters) {
return new BeanThatNeedsParamters(parameters)
}
}
If I have understood your question properly, this is what you are trying to do :
#Component
public class SomeConfiguration {
#Bean(name="parameters")
public Parameters getParameters(){
Parameters parameters = new Parameters();
// add your stuff
return parameters;
}
#Bean(name="springRestConfiguration")
public SpringRestConfiguration springRestConfiguration(){
SpringRestConfiguration springRestConfiguration = new SpringRestConfiguration();
springRestConfiguration.setParametere(getParameters());
return springRestConfiguration;
}
}
and use it like :
ApplicationContext appContext = new AnnotationConfigApplicationContext(SomeConfiguration.class);
SpringRestConfiguration springRestConfiguration = (SpringRestConfiguration) appContext.getBean("springRestConfiguration");
When writing integration tests with Spring 3.1, I usually define the request scope to be a SimpleThreadScope, with the following XML context configuration:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="request">
<bean class="org.springframework.context.support.SimpleThreadScope" />
</entry>
</map>
</property>
</bean>
To define the request scope to be backed by a prototype scope implementation, I thought to change the class to be an implementation of the prototype scope. However I was not able to locate any.
Looking at the Scope Interface Javadoc, in the section All Known Implementing Classes, I see listed: AbstractRequestAttributesScope, PortletContextScope, RequestScope, ServletContextScope, SessionScope, SimpleThreadScope... nothing that looks like a prototype scope.
How can I define the request scope as prototype for integration tests?
UPDATE: I've managed to make my integration tests pass by creating my own prototype scope, which I've defined as follow, so my question now is, whether the following implementation is correct, or it has to be fixed.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
public class PrototypeScope implements Scope {
private static final Log logger = LogFactory.getLog(PrototypeScope.class);
public Object get(String name, ObjectFactory objectFactory) {
return objectFactory.getObject();
}
public Object remove(String name) {
return null;
}
public void registerDestructionCallback(String name, Runnable callback) {
logger.warn("PrototypeScope does not support destruction callbacks. "
+ "Consider using a RequestScope in a Web environment.");
}
public Object resolveContextualObject(String key) {
return null;
}
public String getConversationId() {
return Thread.currentThread().getName();
}
}
UPDATE 2: I am using TestNG and my Integration tests look like this:
#Test
#ContextConfiguration(locations = { "classpath:META-INF/spring/test-context.xml" })
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class MyIntegrationTest extends AbstractTransactionalTestNGSpringContextTests {
#Resource
private MyBeanThatShouldBePrototype bean;
#Transactional
public void testCase() {
...
In fact it's working in a different way - Spring checks if bean is a prototype, and then clones its definition and just populate new bean, so there is no backing class for holding such beans. If you want to look at the implementation please visit:
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean and you will find:
if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
If you want to use prototype scope in tests you can just manually create this bean by invoking new MyObjectThatShouldBePrototype() and then configure it as a Spring bean by using AutowireCapableBeanFactory (injected/autowired into your test):
#Autowired
AutowireCapableBeanFactory beanFactory;
public MyObjectThatShouldBePrototype getBean() {
MyObjectThatShouldBePrototype bean = new MyObjectThatShouldBePrototype();
beanFactory.autowireBean(bean);
return bean;
}
Of course there are several ways to create beans - you can find then here http://www.kubrynski.com/2013/09/injecting-spring-dependencies-into-non.html
Something like this is not working for you?
#Test
#ContextConfiguration(locations = { "classpath:META-INF/spring/test-context.xml" },
classes = MyIntegrationTest.TestConfig.class)
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class MyIntegrationTest extends AbstractTransactionalTestNGSpringContextTests {
#Resource
private MyBeanThatShouldBePrototype bean; // protype bean produced by spring
#Transactional
public void testCase() {
...
}
#Configuration
public static class TestConfig {
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public MyBeanThatShouldBePrototype myBeanThatShouldBePrototype() {
return new MyBeanThatShouldBePrototype();
}
}
}
Probably you could go another way?
What about writing a beanfactorypostprocessor that changes the request scoped bean candidates to prototype instead?
I havent tried it myself but you should be able to apply this to any bean declared as request scoped and set the prototype flag.
In the spring context for your unit tests you define this processor and in the context for integration tests this postprocessor would not be around.
Is it possible to configure spring to instantiate a bean or not, depending on a boolean placeholder property? Or at least to exclude a package from annotation scanning based on such a property?
I think you should be using Spring profiles to configure different behaviours. However if you are using annotations you could create an #Configuration object and and a factory method to create a bean based on the property value
e.g.
#Configuration
class ExampleConfig {
private final String prop;
public ExampleConfig(#Value("${your.prop.name}" prop} {
this.prop = prop;
}
#Bean
public YourBeanClass create() {
if (prop.equals("someValue") {
return new YourBeanClass();
}
return new OtherClass(); // must be subclass/implementation of YBC
}
}
You can use ConditionalOnProperty:
#Bean
#ConditionalOnProperty(value = "property", havingValue = "value", matchIfMissing = true)
public MyBean myBean() ...
Also, check this answer.
This may not fit your needs, and I'm assuming that you have control over the class in question (i.e. not vendor code), but have you considered marking the the bean to be lazy loaded? At least, that way it won't get instantiated until it actually gets used, which may happen conditionally depending on the rest of your configuration.
You can also use #Conditional
Step 1 : Create a class that implements Condition
public class ProdDataSourceCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String dbname = context.getEnvironment().getProperty("database.name");
return dbname.equalsIgnoreCase("prod");
}}
Step 2 : Use the above class with #Conditional
#Configuration
public class EmployeeDataSourceConfig {
....
#Bean(name="dataSource")
#Conditional(ProdDataSourceCondition.class)
public DataSource getProdDataSource() {
return new ProductionDatabaseUtil();
}
}
http://javapapers.com/spring/spring-conditional-annotation/
We can use ConditionalOnProperty . Just define a property deployment.environemnt in application.properties file . And based on this property you can control the creation of objects.
#Bean(name = "prodDatasource")
#ConditionalOnProperty(prefix = "deployment" name = "environment"havingValue = "production")
public DataSource getProdDataSource() {
return new ProductionDatasource();
}
#Bean(name = "devDatasource")
#ConditionalOnProperty(prefix = "deployment" name = "environment"havingValue = "dev")
public DataSource getDevDataSource() {
return new devDatasource();
}