What is best practice for binding dynamic beans at runtime [closed] - java

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I have some configuration property defined as
#Data
#ConfigurationProperties(prefix = RedisClientConfigProperties.CONFIG_PROP_NAME)
public class RedisClientConfigProperties {
public static final String CONFIG_PROP_NAME = "dao.redis";
private int database = 0;
private String host = "127.0.0.1";
private int port = 6379;
private boolean enabled = false;
}
and the following java classes:
public interface IDao {
Object get(UUID id);
void put(UUID id, Object item);
}
public class NoOpDao implements IDao {
#Override
public Object get(UUID id) {
return new Object();
}
#Override
public void put(UUID id, Object item) {
// no-op
}
}
public class RedisDao implements IDao {
#Autowired
private RedisClient client;
#Override
public Object get(UUID id) {
return client.get(id);
}
#Override
public void put(UUID id, Object item) {
client.put(id, item);
}
}
What I want is to be able to autowire an IDao object into another bean, and the implementation is decided at run time based on the RedisClientConfigProperties#enabled field. In Guice, i can do something like the following in a module:
class DaoModule extends PrivateModule {
private final RedisClientConfigProperties configProps;
#Inject
public DaoModule(RedisClientConfigProperties configProps) {
this.configProps = configProps;
}
#Override
protected void configure() {
if (configProps.isEnabled()) {
bind(IDao.class).to(RedisDao.class);
} else {
bind(IDao.class).to(NoOpDao.class);
}
}
}
In Spring, it is a little unclear of how to go about doing this. I have found a few different ways to do it, but I'm not sure what is the "Spring way" to do it:
Use a #Configuration class with an #Bean method that returns IDao with conditional logic inside the method
#Bean
public IDao getDao(RedisClientConfigProperties config) {
if (config.isEnabled()) return new RedisDao();
else return new NoOpDao;
}
Use the service locator pattern to create a factory which will return the bean you want
annotate the implementations with #ConditionalOnProperty annotations
I'm curious about which of these ways is recommended as I have some reservations about each of them:
I would rather not have to manually instantiate the beans and have Springs IOC deal with instantiating the beans as necessary
This still creates al of the interface implementation beans and requires any consumer of the bean to know what bean they are asking for
Potentially error prone given that it doesn't use the Config properties object, but instead looks directly in your config properties files.
Out of these three solutions, I think I prefer #1 but would love feedback / recommendations.

Your code seems that its per application, so what I recommend is have all of your properties in the configuration path, and not in a RedisClientConfigProperties.
keeping your class but modify it like so:
RedisClientConfig.java
#Data
#ConfigurationProperties(prefix = "dao.redis")
public class RedisClientConfigProperties {
private int database;
private String host;
private int port;
private boolean enabled;
// public getters and setters
.
.
}
And in your configuration file:
application.yml
dao:
redis:
enabled: true
database: 0
host: 127.0.0.1
port: 6379
And have your dao be only injected in the context based on the property
You can have it check if property is defined if you only use prefix to avoid passing boolean
RedisDao.java
#Component
#Primary
#ConditionalOnProperty(prefix = "dao.redis", name = "enabled", havingValue = "true")
public class RedisDao implements IDao {
.
.
.
}
This way you will have have your NoOpDao be always injected,
Unless the dao.redis.enabled is true, then the Redis will override the NoOpDao implementation because its annotated with #Primary.

Related

How can an ContextCustomizer access Springs environment properties?

I am implementing a custom Spring context customizer, as I have to perform some operations during startup of the application. The result of the operation is need to configure the datasource I need in my application.
My problem is now, that I need for those operations access to my configuration properties (from application.yaml), as they are the base for my operations.
My, simplified, implementation looks currently like this. Nothing special.
public class MyContextCustomizerFactory
implements ContextCustomizerFactory {
#Target(TYPE) #Retention(RUNTIME)
#Documented #Inherited
public #interface EnabledPostgresTestContainer {
}
#Override
public ContextCustomizer createContextCustomizer(Class<?> c,
List<ContextConfigurationAttributes> a) {
}
static class MyContextCustomizer implements ContextCustomizer {
#Override
public void customizeContext(ConfigurableApplicationContext c,
MergedContextConfiguration mc) {
}
}
}
Of is there an alternatvie approach. Using Springs DynamicPropertySource is currently not an option.
Not sure ContextCustomizerFactory is what you are looking for, because you are talking about "application" but ContextCustomizerFactory is designed for running tests, anyway...
What exactly has confused you?
public class MyContextCustomizerFactory implements ContextCustomizerFactory {
#Override
public ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
return new MyContextCustomizer();
}
}
public class MyContextCustomizer implements ContextCustomizer {
#Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
ConfigurableEnvironment environment = context.getEnvironment();
// reading properties
String applicationName = environment.getProperty("application.name");
// enriching properties
Properties jdbcProperties = new Properties();
jdbcProperties.put("spring.datasource.url", "jdbc://....");
environment.getPropertySources().addFirst(
new PropertiesPropertySource("customizerProperties", jdbcProperties)
);
}
}
UPD.
If the goal is to modify env/properties after Spring has parsed #Configuration classes with #PropertySource we may use BeanFactoryPostProcessor, below are some examples from spring:
EmbeddedDataSourceBeanFactoryPostProcessor - very similar to what TC needs
PropertySourceOrderingPostProcessor - reorders property sources
PropertyOverrideConfigurer

Spring create generic service multiple times using generic in constructor

I have a service that uses some object as a generic
#Component
#RequiredArgsConstructor
public class SomeGenericService<T extends Base> {
private final T base;
public void someWork(String info) {
base.someAction(info);
}
}
I also have 3 Base implementations marked with #Component(Base1, Base2, Base3)
I want spring itself to create a service with the generic it needs, for the following example
#Component
#RequiredArgsConstructor
public class Runner implements CommandLineRunner {
private final SomeGenericService<Base1> s1;
private final SomeGenericService<Base2> s2;
private final SomeGenericService<Base3> s3;
#Override
public void run(String... args) throws Exception {
String someString = "text";
s1.someWork(someString);
s2.someWork(someString);
s3.someWork(someString);
}
}
But after the launch, the spring does not understand what I want from it.
Parameter 0 of constructor in SomeGenericService required a single bean, but 3 were found:
- base1: defined in file [Base1.class]
- base2: defined in file [Base2.class]
- base3: defined in file [Base3.class]
Is it possible to set this to automatic, without manually configuring it via the #Bean annotation for each service?
You need to define how those beans should be injected. It's a good practice to have some #Configurations for this purpose. Something like:
#Configuration
#Import({
Base1.class,
Base2.class,
Base3.class
})
public class SomeConfig {
#Bean
SomeGenericService<Base1> someGenericService1() {
return new SomeGenericService(new Base1());
}
#Bean
SomeGenericService<Base2> someGenericService2() {
return new SomeGenericService(new Base2());
}
#Bean
SomeGenericService<Base3> someGenericService3() {
return new SomeGenericService(new Base3());
}
}

Need to create two objects of one interface in one Java class [duplicate]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
Is it auto detected with #Autowired?
Is it dependency injection by name when #Qualifier is used?
How can we do setter and constructor injection using these annotations?
You can use #Qualifier along with #Autowired. In fact spring will ask you explicitly select the bean if ambiguous bean type are found, in which case you should provide the qualifier
For Example in following case it is necessary provide a qualifier
#Component
#Qualifier("staff")
public Staff implements Person {}
#Component
#Qualifier("employee")
public Manager implements Person {}
#Component
public Payroll {
private Person person;
#Autowired
public Payroll(#Qualifier("employee") Person person){
this.person = person;
}
}
EDIT:
In Lombok 1.18.4 it is finally possible to avoid the boilerplate on constructor injection when you have #Qualifier, so now it is possible to do the following:
#Component
#Qualifier("staff")
public Staff implements Person {}
#Component
#Qualifier("employee")
public Manager implements Person {}
#Component
#RequiredArgsConstructor
public Payroll {
#Qualifier("employee") private final Person person;
}
provided you are using the new lombok.config rule copyableAnnotations (by placing the following in lombok.config in the root of your project):
# Copy the Qualifier annotation from the instance variables to the constructor
# see https://github.com/rzwitserloot/lombok/issues/745
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
This was recently introduced in latest lombok 1.18.4.
The blog post where the issue is discussed in detail
The original issue on github
And a small github project to see it in action
NOTE
If you are using field or setter injection then you have to place the #Autowired and #Qualifier on top of the field or setter function like below(any one of them will work)
public Payroll {
#Autowired #Qualifier("employee") private final Person person;
}
or
public Payroll {
private final Person person;
#Autowired
#Qualifier("employee")
public void setPerson(Person person) {
this.person = person;
}
}
If you are using constructor injection then the annotations should be placed on constructor, else the code would not work. Use it like below -
public Payroll {
private Person person;
#Autowired
public Payroll(#Qualifier("employee") Person person){
this.person = person;
}
}
The #Qualifier annotation is used to resolve the autowiring conflict, when there are multiple beans of same type.
The #Qualifier annotation can be used on any class annotated with #Component or on methods annotated with #Bean. This annotation can also be applied on constructor arguments or method parameters.
Ex:-
public interface Vehicle {
public void start();
public void stop();
}
There are two beans, Car and Bike implements Vehicle interface
#Component(value="car")
public class Car implements Vehicle {
#Override
public void start() {
System.out.println("Car started");
}
#Override
public void stop() {
System.out.println("Car stopped");
}
}
#Component(value="bike")
public class Bike implements Vehicle {
#Override
public void start() {
System.out.println("Bike started");
}
#Override
public void stop() {
System.out.println("Bike stopped");
}
}
Injecting Bike bean in VehicleService using #Autowired with #Qualifier annotation. If you didn't use #Qualifier, it will throw NoUniqueBeanDefinitionException.
#Component
public class VehicleService {
#Autowired
#Qualifier("bike")
private Vehicle vehicle;
public void service() {
vehicle.start();
vehicle.stop();
}
}
Reference:- #Qualifier annotation example
#Autowired to autowire(or search) by-type
#Qualifier to autowire(or search) by-name
Other alternate option for #Qualifier is #Primary
#Component
#Qualifier("beanname")
public class A{}
public class B{
//Constructor
#Autowired
public B(#Qualifier("beanname")A a){...} // you need to add #autowire also
//property
#Autowired
#Qualifier("beanname")
private A a;
}
//If you don't want to add the two annotations, we can use #Resource
public class B{
//property
#Resource(name="beanname")
private A a;
//Importing properties is very similar
#Value("${property.name}") //#Value know how to interpret ${}
private String name;
}
more about #value

SimpUserRegistry doesnot contain any session objects

Iam new to Websockets. I have been trying to use SimpUserRegistry to find session object by Principal. I wrote a custom handshake handler to convert Anonymous users to authenticated users and Iam able to access the Principal name from Websocket session object.
The code for custom handshake handler is shown below
import java.security.Principal;
public class StompPrincipal implements Principal {
private String name;
public StompPrincipal(String name) {
this.name = name;
}
#Override
public String getName() {
return name;
}
}
Handler
class CustomHandshakeHandlerTwo extends DefaultHandshakeHandler {
// Custom class for storing principal
#Override
protected Principal determineUser(
ServerHttpRequest request,
WebSocketHandler wsHandler,
Map<String, Object> attributes
) {
// Generate principal with UUID as name
return new StompPrincipal(UUID.randomUUID().toString());
}
}
But as specified in many questions like this I'am not able to inject the SimpUserRegistry directly.
It throws error
Field simpUserRegistry required a bean of type 'org.springframework.messaging.simp.user.SimpUserRegistry' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.springframework.messaging.simp.user.SimpUserRegistry' in your configuration.
So I created a configuration class as shown below.
#Configuration
public class UsersConfig {
final private SimpUserRegistry userRegistry = new DefaultSimpUserRegistry();
#Bean
#Primary
public SimpUserRegistry userRegistry() {
return userRegistry;
}
}
Now I can autowire and use it but everytime I try to acess the SimpUserRegistry it is empty.
What could be the cause of this problem?
EDIT:
Showing websocket config
#Configuration
#EnableWebSocket
#Controller
#Slf4j
public class WebSocketConfig implements WebSocketConfigurer {
#Autowired
EventTextHandler2 handler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
log.info("Registering websocket handler SocketTextHandler");
registry.addHandler(handler, "/event").setHandshakeHandler(new CustomHandshakeHandlerTwo());
}
}
SimpUserRegistry is an "infrastructure bean" registered/provided by Spring WebSocket, you should not instantiate it directly.
Is your WebSocket Spring configuration correct?
Make sure your application is well configured (ie. your configuration class is being scanned).
SimpUserRegistry is imported by spring-messaging dependency: make sure your configuration class is annotated with #EnableWebSocketMessageBroker.
Official documentation: https://docs.spring.io/spring-framework/docs/5.3.6/reference/html/web.html#websocket-stomp-enable
To back the connected users in Redis, you may want to create a new SimpUserRegistry implementation:
public class RedisSimpUserRegistry implements SimpUserRegistry, SmartApplicationListener {
private final RedisTemplate redisTemplate;
public RedisSimpUserRegistry(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
[...]
#Override
public void onApplicationEvent(ApplicationEvent event) {
// Maintain Redis collection on event type
// ie. SessionConnectedEvent / SessionDisconnectEvent
}
[...]
}
PS: The #Controller annotation on your config class is not necessary unless you have an endpoint defined in it.
Edit after new comments:
You can see the DefaultSimpUserRegistry implementation to get an idea of how to do it.
To intercept an application event, you have to implement the ApplicationListener interface (in this case SmartApplicationListener).
The supportsEventType method is important to define which event types you want to intercept:
#Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return AbstractSubProtocolEvent.class.isAssignableFrom(eventType);
}
The AbstractSubProtocolEvent have multiple implementations. The most important ones are SessionConnectEvent, SessionDisconnectEvent.
Intercepting (see onApplicationEvent method) these event types will allow your implementation to maintain the desired state in your Redis cache. You could then store users (ids, etc.).

Improve explicitness of spring library for extendable config-objects

I am currently working on a spring-library that allows user-defined config-classes (has nothing to to with #Configuration) to be adjusted from another part of the application before they are used:
interface ConfigAdjuster<T extends Config<T>> {
void adjust(T t);
}
abstract class Config<T extends Config<T>> {
#Autowired
Optional<ConfigAdjuster<T>> adjuster;
#PostConstruct
private void init() {
//i know this cast is somewhat unsafe, just ignore it for this question
adjuster.ifPresent(a -> a.adjust((T)this));
}
}
This can be used as follows:
class MyConfig extends Config<MyConfig> {
//imagine many fields of more complex types
public String myData;
}
#Configuration
class MyConfigDefaults {
#Profile("dev")
#Bean
public MyConfig devDefaults() {
//imagine setting defaults values here
return new MyConfig();
}
}
Now a consumer of the library that uses MyConfig can do the following somewhere in his application:
#Bean
public ConfigAdjuster<MyConfig> adjustDefaults() {
return cfg -> {
cfg.myData = "something_other_than_default";
}
}
The biggest problem I see with this approach is that the whole "adjust the config"-part is somewhat hidden for the user. You can not easily tell you are able to change the default-configuration by using a ConfigAdjuster. In the worst case the user tries to autowire the config object and tries to modify it that way which results in undefined behaviour because other components could already have been initialized with the defaults.
Is there an easy way to make this approach more "telling" than what it is right now? The whole idea is to not copy&paste the whole default-config + adjustment parts across multiple projects.
One way to make all of this more explicit would be to require the adjuster in the constructor of Config, but this pollutes every constructor and usage of the inherting classes.
Any thoughts on this?
Edit: Do note that this is a simplified version of the library and I do know about the implications of a private #PostConstruct etc. If you have another way of achieving all of this without the #PostConstruct please do share :)
Edit2:
Let me outline the main goals of this library again:
Allow the definition of default config-objects for the library-user
Allow the enduser (consuming a depedency using this library) to overwrite certain parts of the default configuration before it is used
Save the library-user from boilerplate (e.g. define 2. on their own)
There is two solution for your problem:
1- define a generic Customizer something like:
public interface Customizer<T> {
T customize(T t);
boolean supports(Class target);
}
in your lib you have a config:
public class MyConfig {
private String property;
public MyConfig() {
}
public void setProperty(String property) {
this.property = property;
}
}
so your Default configuration should look something like this:
#Configuration
public class DefaultConfiguration {
#Autowired(required = false)
private List<Customizer> customizers;
#Bean
public MyConfig myConfig() {
MyConfig myConfig = new MyConfig();
myConfig.setProperty("default value");
if (customizers != null) {
for (Customizer c : customizers) {
if (c.supports(MyConfig.class)) {
return (MyConfig) c.customize(myConfig);
}
}
}
return myConfig;
}
}
this way, the only thing the user should do whenever he wants to customize you bean is to implement Customizer, and then declare it as a bean.
public class MyConfigCustomizer implements Customizer<MyConfig> {
#Override
public MyConfig customize(MyConfig myConfig) {
//customization here
return myConfig;
}
#Override
public boolean supports(Class<?> target) {
return MyConfig.class.isAssignableFrom(target);
}
}
and he should declare it:
#Bean
public Customizer<MyConfig> customizer(){
return new MyConfigCustomizer ();
}
I think this answers your question, but it's ugly (uncheched warnings and a List ...) not the best, as everything seems to the user customizable even it's not.
2- I suggest you expose interfaces for Beans that can be adjusted by the user, something like:
public interface MyConfigCustomizer{
MyConfig customize(MyConfig config);
}
your Default Configuration:
#Configuration
public class DefaultConfiguration {
#Autowired(required = false)
private MyConfigCustomizer customizer;
#Bean
public MyConfig myConfig() {
MyConfig myConfig = new MyConfig();
myConfig.setProperty("default value");
if (customizer != null) {
return customizer.customize(myconfig);
}
return myConfig;
}
}
this way the user knows that MyConfig can be adjusted (and not all the beans).

Categories

Resources