I would like to use polymorphic configuration properties on Spring, using Spring's #ConfigurationProperties annotation.
Suppose we have the following POJO classes.
public class Base {
private String sharedProperty;
public String getSharedProperty() {
return sharedProperty;
}
public String setSharedProperty(String sharedProperty) {
this.sharedProperty = sharedProperty;
}
}
public class Foo extends Base {
private String fooProperty;
public String getFooProperty() {
return fooProperty;
}
public String setFooProperty(String sharedProperty) {
this. fooProperty = fooProperty;
}
}
public class Bar extends Base {
private String barProperty;
public String getSharedProperty() {
return sharedProperty;
}
public String setBarProperty(String barProperty) {
this.barProperty = barProperty;
}
}
And the configuration properties class,
#Component
#ConfigurationProperties(prefix = "playground")
public class SomeConfigurationProperties {
private List<Base> mixed;
public List<Base> getMixed() {
return mixed;
}
public void setMixed(List<Base> mixed) {
this.mixed = mixed;
}
}
And the application.yml file,
playground:
mixed:
- shared-property: "shared prop"
foo-property: "foo prop"
- shared-property: "shared prop"
bar-property: "bar prop"
However, with this configuration, Spring initializes the #ConfigurationProperties-annotated class with the list of Base objects, instead of their subclasses. That is, actually, an expected behavior (due to security concerns).
Is there a way to enforce the behavior of SnakeYAML to use subclasses, or implement any kind of custom deserialization provider?
Although it is possible to implement custom PropertySources and/or ConversionService, a custom deserialization provider is not necessary.
Spring has no issues binding the same properties to multiple beans. The reason your implementation is not working is because you are only registering one bean with the ApplicationContext with the #Component annotation on the base class. This is telling the component scanner that there is only one singleton of type Base. Because Foo and Bar are not registered as beans, they won't be bound to.
If the only reason you are looking at making these polymorphic is to share property name prefixes in SnakeYAML based config, then you actually do not need to introduce the polymorphic relationship, and can bind to shared properties by a common field name in different classes.
There are many ways to implement what you are asking for however in a polymorphic way, here are a few of the most straight forward simple ones:
Self declaring Polymorphic ConfigurationProperties singleton beans
Instead of applying the #ConfigurationProperties and #Component annotations on the base class, apply them on the concrete classes, with the same property name prefix. This wouldn't be my preferred approach, as each bean would not be conditional on their properties being set, however it may suit your needs. Depending on if your Spring Configuration allows properties to be reloaded, Spring will maintain the bindings on all of the beans.
Note: As of IntelliJ Idea 2018.3, an inspection profile was added to identify duplicate prefix keys as an error. You may want to ignore this, or suppress the warnings.
I tested the following successfully:
Base.java
package sample;
public class Base {
private String sharedProperty;
public String getSharedProperty() {
return sharedProperty;
}
public void setSharedProperty(String sharedProperty) {
this.sharedProperty = sharedProperty;
}
}
Foo.java
package sample;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
#Component
#ConfigurationProperties("playground")
public class Foo extends Base {
private String fooProperty;
public String getFooProperty() {
return fooProperty;
}
public void setFooProperty(String fooProperty) {
this.fooProperty = fooProperty;
}
}
Bar.java
package sample;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
#Component
#ConfigurationProperties("playground")
public class Bar extends Base {
private String barProperty;
public String getBarProperty() {
return barProperty;
}
public void setBarProperty(String barProperty) {
this.barProperty = barProperty;
}
}
application.yml
playground:
shared-property: "shared prop"
foo-property: "foo prop"
bar-property: "bar prop"
SampleAppTest.java
package sample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
public class SampleAppTest {
#Autowired
public Environment environment;
#Test
public void test(#Autowired Bar bar, #Autowired Foo foo) {
assertEquals("shared prop", bar.getSharedProperty());
assertEquals("shared prop", foo.getSharedProperty());
assertEquals("bar prop", bar.getBarProperty());
assertEquals("foo prop", foo.getFooProperty());
}
#Test
public void testSuper(#Autowired List<Base> props) {
assertEquals(2, props.size());
}
}
Polymorphic ConfigurationProperties beans conditional on properties
You may not want certain concrete implementations to be instantiated if their specific properties are missing. Furthermore, you may not want to couple the #ConfigurationProperties and #Component annotations to each concrete class. This implementation constructs the ConfigurationProperties beans via a Spring #Configuration bean. The configuration bean ensures they are only constructed conditionally via a property existence check. This implementation also creates a bean of concrete type Base if none of the other Base beans meet conditions and the shared properties exist. The same unit test from the previous example is used here and passes:
Base.java
package sample;
public class Base {
private String sharedProperty;
public String getSharedProperty() {
return sharedProperty;
}
public void setSharedProperty(String sharedProperty) {
this.sharedProperty = sharedProperty;
}
}
Foo.java
package sample;
public class Foo extends Base {
private String fooProperty;
public String getFooProperty() {
return fooProperty;
}
public void setFooProperty(String fooProperty) {
this.fooProperty = fooProperty;
}
}
Bar.java
package sample;
public class Bar extends Base {
private String barProperty;
public String getBarProperty() {
return barProperty;
}
public void setBarProperty(String barProperty) {
this.barProperty = barProperty;
}
}
SampleConfiguration.java
package sample;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class SampleConfiguration {
#Bean
#ConfigurationProperties("playground")
#ConditionalOnProperty("playground.foo-property")
public Foo foo() {
return new Foo();
}
#Bean
#ConfigurationProperties("playground")
#ConditionalOnProperty("playground.bar-property")
public Bar bar() {
return new Bar();
}
#Bean
#ConfigurationProperties("playground")
#ConditionalOnProperty("playground.shared-property")
#ConditionalOnMissingBean(Base.class)
public Base base() {
return new Base();
}
}
application.yml
playground:
shared-property: "shared prop"
foo-property: "foo prop"
bar-property: "bar prop"
SampleAppTest.java
package sample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
public class SampleAppTest {
#Autowired
public Environment environment;
#Test
public void test(#Autowired Bar bar, #Autowired Foo foo) {
assertEquals("shared prop", bar.getSharedProperty());
assertEquals("shared prop", foo.getSharedProperty());
assertEquals("bar prop", bar.getBarProperty());
assertEquals("foo prop", foo.getFooProperty());
}
#Test
public void testSuper(#Autowired List<Base> props) {
assertEquals(2, props.size());
}
}
Related
I know it's a problem that is been posted 100 times, but unfortunately I am getting a Defining Bean error in my Spring Boot Application and I really do not know why. I do not see my error from launch to finish since I am defining a bean.
I would appreciate any help.
I'm sure it's a stupid mistake which I just don't see
My error Code
Description:
Parameter 0 of constructor in com.example.demo.jwt.JwtSecretKey required a bean of type 'com.example.demo.jwt.JwtConfig' that could not be found.
Action:
Consider defining a bean of type 'com.example.demo.jwt.JwtConfig' in your configuration.
JwtSecretKey class
package com.example.demo.jwt;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.crypto.SecretKey;
#Configuration
public class JwtSecretKey {
private final JwtConfig jwtConfig;
#Autowired
public JwtSecretKey(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
#Bean
public SecretKey secretKey() {
return Keys.hmacShaKeyFor(jwtConfig.getSecretKey().getBytes());
}
}
JwtConfig class
package com.example.demo.jwt;
import com.google.common.net.HttpHeaders;
import org.springframework.boot.context.properties.ConfigurationProperties;
#ConfigurationProperties(prefix = "application.jwt")
public class JwtConfig {
private String secretKey;
private String tokenPrefix;
private Integer tokenExpirationAfterDays;
public JwtConfig() {}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getTokenPrefix() {
return tokenPrefix;
}
public void setTokenPrefix(String tokenPrefix) {
this.tokenPrefix = tokenPrefix;
}
public Integer getTokenExpirationAfterDays() {
return tokenExpirationAfterDays;
}
public void setTokenExpirationAfterDays(Integer tokenExpirationAfterDays) {
this.tokenExpirationAfterDays = tokenExpirationAfterDays;
}
Annotate your JwtConfig class with #Configuration
#Configuration
#ConfigurationProperties(prefix = "application.jwt")
public class JwtConfig {
See in Javadocs:
Annotation for externalized configuration. Add this to a class
definition or a #Bean method in a #Configuration class if you want to
bind and validate some external Properties (e.g. from a .properties
file).
Reference: https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/ConfigurationProperties.html
Given the following classes in the same package:
CarOne:
import org.springframework.stereotype.Component;
#Component
public class CarOne {
private String name;
public CarOne() {
System.out.println("Car One instantiated");
}
public String getName() {
return name = "Toyota";
}
}
CarTwo:
import lombok.Data;
import org.springframework.stereotype.Component;
#Component
#Data
public class CarTwo {
public CarTwo() {
System.out.println("Car Two instantiated");
}
private String name = "Ford";
}
And the following test:
#SpringBootTest
public class CarTest {
#Autowired
private CarOne carOne;
#Autowired
private CarTwo carTwo;
#Test
void getCarOneName(){
System.out.println(carOne.getName());
}
#Test
void getCarTwoName(){
System.out.println(carTwo.getName());
}
}
...the second test throws a compilation error in IntelliJ: java: cannot find symbol, symbol: method getName()
I am just starting out learning Spring Boot. I have lots of classes where I'm using Lombok who's data I want to wire up using Spring Boot - it's probably obvious, but where am I going wrong?
I have a random class in a random package that is loaded through reflection after the app launches, is there a way for it to be registered as a component under springboot and have annotations such as #Autowired and #Value etc work for that class.
It works when it is in the same package at launch time, but if introduce it thorough another jar at runtime (same package or not) it doesn't work.
Below are samples that don't work even if it is in the same jar. I can't change the app's configuration - it would defeat the "random package/random class" objective.
Code in Spring boot application package
package sample.app
#SpringBootApplication
public class Application {
public static void main(String[] args) {
// Code that starts app
//
//
try {
Thread.sleep(7000);
Class test = Class.forName("test.Test", true, Application.class.getClassLoader());
System.out.println(test.getMethod("getName").invoke(null)); //NPE
System.out.println(test.getMethod("getProfiles").invoke(null)); //NPE
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Test.java
package test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DependsOn;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
#DependsOn("blaaaaaaaah")
#ComponentScan
public class Test {
#DependsOn("blaaaaaaaah")
public static String getName() {
return SpringGetter.instance.getApplicationName();
}
#DependsOn("blaaaaaaaah")
public static String[] getProfiles() {
String[] profiles = SpringGetter.instance.getEnv().getActiveProfiles();
if (profiles == null || profiles.length == 0) {
profiles = SpringGetter.instance.getEnv().getDefaultProfiles();
}
return profiles;
}
}
SpringGetter.java
package test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
#Component("blaaaaaaaah")
public class SpringGetter implements InitializingBean {
public static SpringGetter instance;
#Value("${spring.application.name}")
private String applicationName;
#Autowired
private Environment env;
public SpringGetter() {
System.out.println("consASFJEFWEFJWDNFWJVNJSBVJWNCJWBVJNVJNVJSNJSNCSDJVNSVJtruct");
}
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public Environment getEnv() {
return env;
}
public void setEnv(Environment env) {
this.env = env;
}
#PostConstruct
public void setInstance() {
instance = this;
}
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
}
EDIT:
I managed to dynamically create the SpringGetter class as part of the same package as the Application class(the one with the #SpringBootApplication). I got Test.java to point to that dynamic class and yet no luck.
To simply inject fields into a POJO as if it were a Spring-managed bean, you can use something like the following:
#Component
public class BeanInitializer implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
public void initializeObject(Object pojo) {
beanFactory.autowireBean(pojo);
}
}
Note, however, that this only injects fields marked as #Autowired or #Injected. It does not create proxies that honor method interception strategies based on e.g. #Transactional, #Async, etc.
If you're using Spring 5, have a look at the registerBean() method from GenericApplicationContext. You can find an example here: https://www.baeldung.com/spring-5-functional-beans
The issue in your Test class may also be that you're not loading the Spring Boot context from the main class. You can use the SpringBootTest annotation for this.
I want to create a DAO object by dependency injection (#Autowire) but unfortunately, this DAO object is never created and hence a Nullpointer exception is thrown.
This is my DAO implementation:
package com.sample.dao.service;
#Component
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
#Autowired
OrderServiceImpl(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
#Override
public void save(Order order) {
return orderRepository.save(order);
}
The class where the Nullpointer exception is caused:
package com.sample.dispatcher;
#Component
public class OrderDispatcher {
private final OrderServiceImpl orderServiceImpl;
#Autowired
public OrderDispatcher(OrderServiceImpl orderServiceImpl) {
this.orderServiceImpl = orderServiceImpl;
}
public void createOrder(Order order) {
orderServiceImpl.save(order)); // --> Nullpointer
My entry class:
package com.sample;
#SpringBootApplication
#ComponentScan(basePackages = { "com.sample" , "com.webservice"})
#EnableJpaRepositories(basePackages = "com.sample.dao.repository")
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
I think you should change your constructor to have an argument type of interface rather than a concrete implementation. So something like this -
#Component
public class OrderDispatcher {
private final OrderService orderServiceImpl;
#Autowired
public OrderDispatcher(OrderService orderServiceImpl) {
this.orderServiceImpl = orderServiceImpl;
}
When you add the #component notation on OrderServiceImpl, Spring creates proxy for that class and it can be autowired by interface.
Maybe you forgot the #annotation configuration. Try adding this class and also you scan your entities: EntityScan
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#Configuration
#EntityScan("com.sample.model") // Your model package
#ComponentScan(basePackages = { "com.sample" , "com.webservice"})
#EnableJpaRepositories(basePackages = "com.sample.dao.repository")
public class RepositoryConfig {
}
I have the following definition of an aspect and other classes that are co-working.
package concert;
public aspect CriticAspect {
public CriticAspect() {}
pointcut performance(): execution(* perform(..));
afterReturning() : performance() {
System.out.println(criticismEngine.getCriticism());
}
private CriticismEngine criticismEngine;
public void setCriticismEngine(CriticismEngine criticismEngine) {
this.criticismEngine = criticismEngine;
}
}
CriticismEngine
package concert;
public interface CriticismEngine {
String getCriticism();
}
CriticismEngineImpl
package concert;
public class CriticismEngineImpl implements CriticismEngine {
public CriticismEngineImpl() {}
public String getCriticism() {
int i = (int) (Math.random() * criticismPool.length);
return criticismPool[i];
}
// injected
private String[] criticismPool;
public void setCriticismPool(String[] criticismPool) {
this.criticismPool = criticismPool;
}
}
Performance
package concert;
public interface Performance {
void perform();
}
PerformanceImple
package concert;
public class Concert implements Performance {
#Override
public void perform() {
System.out.println("Playing a concert!");
}
}
Configuration
package concert.config;
import concert.Concert;
import concert.CriticAspect;
import concert.CriticismEngine;
import concert.CriticismEngineImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy()
public class ApplicationConfiguration {
#Bean
public CriticAspect criticAspect() {
return CriticAspect.aspectOf();
}
#Bean
public CriticismEngine criticismEngine() {
CriticismEngineImpl criticismEngine = new CriticismEngineImpl();
String[] criticisms = { "Worst performance ever!",
"I laughed, I cried, then I realized I was at the wrong show.",
"A must see show!" };
criticismEngine.setCriticismPool(criticisms);
return criticismEngine;
}
#Bean
public Concert concert() {
return new Concert();
}
}
Main
package concert;
import concert.config.ApplicationConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
Performance concert = context.getBean("concert", Performance.class);
concert.perform();
}
}
Dependencies
compile "org.springframework:spring-context:${springVersion}"
compile "org.springframework:spring-aop:${springAopVersion}"
compile "org.aspectj:aspectjrt:${aspectJRuntimeVersion}"
compile "org.aspectj:aspectjweaver:${aspectJWeaverVersion}"
But intellij says it cannot find CriticAspect. How I can run this example ? Or I'm doing something wrong ?
IMO #EnableAspectJAutoProxy does not make sense for a native AspectJ aspect. It only works for Spring AOP aspects. So you have two options:
Either you compile your native AspectJ aspect right into your Spring application via Ajc (AspectJ compiler). In this case you do not need any annotations to get the aspects running, only aspectjrt.jar on the classpath during runtime.
Or you go the canonical way described in the Spring manual (chapter 9.8) and use LTW (load-time weaving) for native AspectJ aspects. This can be enabled in your configuration via #EnableLoadTimeWeaving or <context:load-time-weaver/>, respectively. See chapter 9.8.4, Load-time weaving with AspectJ in the Spring Framework.