javax.inject.Named with dynamic name - java

I have a Spring 4 based application and using #Named annotation.
#Named("test")
public class DocumentMapper implements Mapper<Test> {
Here, the name test for the Named annotation is hardcoded. Is it possible to make it property driven as opposed to hardcoding the name?
I tried the below:
public class NameGenerator implements BeanNameGenerator {
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if(definition.getBeanClassName().contains("DocumentMapper")) {
return "test";
}
}
}
Here, I want to return the bean name based on property. How can I inject my property config class?

If your goal is to register bean (with a custom bean name) programmatically
You can use the BeanDefinitionRegistryPostProcessor interface which is :
Extension to the standard BeanFactoryPostProcessor SPI, allowing for
the registration of further bean definitions before regular
BeanFactoryPostProcessor detection kicks in. In particular,
BeanDefinitionRegistryPostProcessor may register further bean
definitions which in turn define BeanFactoryPostProcessor instances.
And example would be (here I want to register MyBean class with a name from bean.name property):
#Component
public class BeanDefinitionRegistryPP implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {
private Environment env;
#Override
public void setEnvironment(Environment environment) {
this.env=environment;
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
GenericBeanDefinition beanDefinition= new GenericBeanDefinition();
beanDefinition.setBeanClass(MyBean.class);
registry.registerBeanDefinition(env.getProperty("bean.name"), beanDefinition);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// no code
}
}

Related

How do you make a spring bean Primary only if another Primary bean does not exist

I am trying to create multiple beans that implement the same interface. I have a bean that I want to use as the "default" #Primary bean; however, since it acts as a default, I want another bean to be able to use #Primary (or something similar) to make a primary bean. In other words, this default one should be like... "#PrimaryIfNoPrimaryAlreadyExist" kind of thing. For example, I have this:
#Configuration
public class DefaultCustomObjectMapperConfiguration {
#Bean
#Primary
public ICustomObjectMapper defaultCustomObjectMapper(#Value("${objectmapper.serialize.defaultFormat:JSON}") String defaultMapperFormat,
#Qualifier(DEFAULT_XML_MAPPER_KEY) ICustomObjectMapper xmlMapper,
#Qualifier(DEFAULT_JSON_MAPPER_KEY) ICustomObjectMapper jsonMapper) {
return "XML".equals(defaultMapperFormat) ? xmlMapper : jsonMapper;
}
#Bean
#Qualifier(DEFAULT_JSON_MAPPER_KEY)
public ICustomObjectMapper defaultJSONCustomObjectMapper() {
return new DefaultCustomObjectMapper(new ObjectMapper());
}
#Bean
#Qualifier(DEFAULT_XML_MAPPER_KEY)
public ICustomObjectMapper defaultXMLCustomObjectMapper() {
return new DefaultCustomObjectMapper(new XmlMapper());
}
}
I always want to create the defaultJSONCustomObjectMapper() bean and the defaultXMLCustomObjectMapper() bean, but the defaultCustomObjectMapper() should only be created if there is not another bean in the Spring context that is defined as #Primary. For example, it should not be created (or at least not used as Primary) if someone else defines this in the same context:
#Primary
#Bean
ICustomObjectMapper anotherCustomObjectMapper() {
return new AnotherCustomObjectMapper();
}
I believe you can override a bean by calling it the same name, but I don't want to do it that way because then it requires the service pulling this in to know that it has to call the bean something special.
Is this possible? Or is there a way to do it that is better than this?
Edit:
Looking at the Spring annotations, there's the ConditionalOnSingleCandidate. It would be more accurate for me to say I want the opposite of that, i.e. ConditionalOnMultipleCandidates
Adding #ConditionalOnMissingBean(annotation = Primary.class) to your "default" #Primary bean should do the trick. This will only register your default primary bean if another primary bean of the same type is not already registered.
#Bean
#Primary
#ConditionalOnMissingBean(annotation = Primary.class)
public ICustomObjectMapper defaultCustomObjectMapper(#Value("${objectmapper.serialize.defaultFormat:JSON}") String defaultMapperFormat,
#Qualifier(DEFAULT_XML_MAPPER_KEY) ICustomObjectMapper xmlMapper,
#Qualifier(DEFAULT_JSON_MAPPER_KEY) ICustomObjectMapper jsonMapper) {
// ...
}
Note that depending on the potential order of your other bean creation, you may want to ensure that this default bean is processed last. For example, by adding #Order(Integer.MAX_INT).
Update: Sean correctly points out that #ConditionalOnMissingBean doesn't work in this case because it looks for ANY bean with Primary annotation, not just beans of our type.
A somewhat ugly alternative is to programmatically set a bean to primary after bean creation if no other primary bean of that type was found. This can be done by implementing BeanDefinitionRegistryPostProcessor and BeanFactoryAware. Note that bean name is used, not qualifier.
#Configuration
public class DefaultCustomObjectMapperConfiguration
implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
private BeanFactory beanFactory;
#Value("${objectmapper.serialize.defaultFormat:JSON}")
private String defaultMapperFormat;
#Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// unused
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
String[] beansOfType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) beanFactory, ICustomObjectMapper.class);
for(String beanName : beansOfType) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if(beanDef.isPrimary()) {
// found an existing primary bean of same type
return;
}
}
// note that getBeanDefinition retrieves by bean name, which is not necessarily equal to qualifier
BeanDefinition defaultPrimaryBeanDef =
registry.getBeanDefinition("XML".equals(defaultMapperFormat) ? DEFAULT_XML_MAPPER_KEY : DEFAULT_JSON_MAPPER_KEY);
defaultPrimaryBeanDef.setPrimary(true);
}
#Bean(DEFAULT_JSON_MAPPER_KEY)
#Qualifier(DEFAULT_JSON_MAPPER_KEY)
public ICustomObjectMapper defaultJSONCustomObjectMapper() {
return new DefaultCustomObjectMapper(new ObjectMapper());
}
#Bean(DEFAULT_XML_MAPPER_KEY)
#Qualifier(DEFAULT_XML_MAPPER_KEY)
public ICustomObjectMapper defaultXMLCustomObjectMapper() {
return new DefaultCustomObjectMapper(new XmlMapper());
}
}

How to programmatically mark a spring bean as #Primary on startup?

I have multiple DataSources in my application.
The standard org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration is annotated with #ConditionalOnSingleCandidate(DataSource.class)
I am attempting to select a #Primary DataSource programmatically.
I have tried a BeanFactoryPostProcessor that naively selects the first DataSource and marks as primary):
#Bean
public BeanFactoryPostProcessor beanFactoryPostProcessor() {
return this::setPrimaryDataSource;
}
public void setPrimaryDataSource(ConfigurableListableBeanFactory beanFactory) {
// Get all DataSource bean names
String[] dataSourceBeanNames = beanFactory.getBeanNamesForType(DataSource.class);
// Find primaryBeanName
String primaryBeanName = dataSourceBeanNames.length > 0 ? dataSourceBeanNames[0] : null;
// Return appropriate bean
assert primaryBeanName != null;
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(primaryBeanName);
beanDefinition.setPrimary(true);
LOGGER.info("Primary DataSource: {}", primaryBeanName);
}
However, this does not appear to work - the #ConditionalOnSingleCandidate(DataSource.class) check on HibernateJpaConfiguration still fails.
Is there anywhere else I can put this code such that it will be executed before the check for #ConditionalOnSingleCandidate?
BeanFactoryPostProcessor worked for me:
#Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// logic to retrieve your bean name
String beanName = beanFactory.getBeanNamesForType(MyService.class)[0];
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
bd.setPrimary(true);
}
}
If your code is in a class with #Configuration, the method need to be static in order to update the bean definition before any bean creation.
Here is a sample for PropertySourcesPlaceholderConfigurer.
#Configuration
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
In the documentation:
You may declare #Bean methods as static, allowing for them to be called without creating their containing configuration class as an instance. This makes particular sense when defining post-processor beans (for example, of type BeanFactoryPostProcessor or BeanPostProcessor), since such beans get initialized early in the container lifecycle and should avoid triggering other parts of the configuration at that point.

Spring Boot Custom Bean Loader

I am using JDBI in tandem with Spring Boot. I followed this guide which results in having to create a class: JdbiConfig in which, for every dao wanted in the application context, you must add:
#Bean
public SomeDao someDao(Jdbi jdbi) {
return jdbi.onDemand(SomeDao.class);
}
I was wondering if there is some way within Spring Boot to create a custom processor to create beans and put them in the application context. I have two ideas on how this could work:
Annotate the DAOs with a custom annotation #JdbiDao and write something to pick those up. I have tried just manually injecting these into the application start up, but the problem is they may not load in time to be injected as they are not recognized during the class scan.
Create a class JdbiDao that every repository interface could extend. Then annotate the interfaces with the standard #Repository and create a custom processor to load them by way of Jdbi#onDemand
Those are my two ideas, but I don't know of any way to accomplish that. I am stuck with manually creating a bean? Has this been solved before?
The strategy is to scan your classpath for dao interface, then register them as bean.
We need: BeanDefinitionRegistryPostProcessor to register additional bean definition and a FactoryBean to create the jdbi dao bean instance.
Mark your dao intercface with #JdbiDao
#JdbiDao
public interface SomeDao {
}
Define a FactoryBean to create jdbi dao
public class JdbiDaoBeanFactory implements FactoryBean<Object>, InitializingBean {
private final Jdbi jdbi;
private final Class<?> jdbiDaoClass;
private volatile Object jdbiDaoBean;
public JdbiDaoBeanFactory(Jdbi jdbi, Class<?> jdbiDaoClass) {
this.jdbi = jdbi;
this.jdbiDaoClass = jdbiDaoClass;
}
#Override
public Object getObject() throws Exception {
return jdbiDaoBean;
}
#Override
public Class<?> getObjectType() {
return jdbiDaoClass;
}
#Override
public void afterPropertiesSet() throws Exception {
jdbiDaoBean = jdbi.onDemand(jdbiDaoClass);
}
}
Scan classpath for #JdbiDao annotated interfaces:
public class JdbiBeanFactoryPostProcessor
implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, EnvironmentAware, BeanClassLoaderAware, BeanFactoryAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
private Environment environment;
private ClassLoader classLoader;
#Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// By default, scanner does not accept regular interface without #Lookup method, bypass this
return true;
}
};
scanner.setEnvironment(environment);
scanner.setResourceLoader(resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(JdbiDao.class));
List<String> basePackages = AutoConfigurationPackages.get(beanFactory);
basePackages.stream()
.map(scanner::findCandidateComponents)
.flatMap(Collection::stream)
.forEach(bd -> registerJdbiDaoBeanFactory(registry, bd));
}
private void registerJdbiDaoBeanFactory(BeanDefinitionRegistry registry, BeanDefinition bd) {
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) bd;
Class<?> jdbiDaoClass;
try {
jdbiDaoClass = beanDefinition.resolveBeanClass(classLoader);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
beanDefinition.setBeanClass(JdbiDaoBeanFactory.class);
// Add dependency to your `Jdbi` bean by name
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("jdbi"));
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(jdbiDaoClass));
registry.registerBeanDefinition(jdbiDaoClass.getName(), beanDefinition);
}
}
Import our JdbiBeanFactoryPostProcessor
#SpringBootApplication
#Import(JdbiBeanFactoryPostProcessor.class)
public class Application {
}

Using Autowired bean inside Preauthorize expression in Spring

I have the following class for a resource in my Spring Application
#RestController
#RequestMapping("/whatever")
public class SomeResource {
#Autowired
private CoolService coolService;
#RequestMapping(
path = "",
method = RequestMethod.GET)
#PreAuthorize("hasPerm(#coolService.resolve(#attribute))")
public void resource(#PathVariable("attribute") int attribute) {
...
}
And I want to call the bean implementing CoolService that has been autowired by the Spring context, because for CoolService I have two beans that get activated depending on the profile at startup.
public interface CoolService {
resolve(int attribute);
}
#Service
#Profile("super")
public interface SuperCoolService implements CoolService {
public Object resolve(int attribute){...}
}
#Service
#Profile("ultra")
public interface UltraCoolService implements CoolService {
public Object resolve(int attribute){...}
}
However it seems that Spring does not know which bean to use because there is no single bean just named CoolService, and inside the Preauthorize I can't write #superCoolService or #ultraCoolService because it is profile-dependant.
How can I achieve this?
If you want to define 2 bean implement same interface, then you can user annotation #Qualifier.
For example:
#Service
#Qualifier("service1")
public interface SuperCoolService implements CoolService {
public Object resolve(int attribute){...}
}
#Service
#Qualifier("service1")
public interface UltraCoolService implements CoolService {
public Object resolve(int attribute){...}
}

Spring #Configuration runtime injection

In my non-Boot Spring 5 project I need to manually register and initialize some beans. After that I want to add a #Configuration class to context, that imports a config from an external lib:
#Configuration
#Import(ExtLibConfig.class)
public class MyExtLibConfig {
#Bean
public ExtLibBean extLibBean() {
return ExtLibBean.builder().build();
}
}
ExtLibConfig has many of its own #ComponentScan and #Import, and I wish them all to be configured automatically, including my ExtLibBean.
Is it possible to do so in runtime? External lib scans ApplicationContext, and I need it to do so, when my manually registered beans are added.
UPD:
The problem is not actual about beans register order. The ext lib is scanning ApplicationContext after its refresh, so I need my beans to be there at this time
The solution was to implement BeanDefinitionRegistryPostProcessor
public class MyMockBeanDefinitioRegistrynPostProcessor implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// registry.registerBeanDefinition(mockBeanClass, mockBeanDefinition);...
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// nothing to do
}
Then declare it as a Bean:
#Configuration
public class MockBeanConfig {
#Bean
public MyMockBeanDefinitioRegistrynPostProcessor mockBeanDefinitionPp() {
return new MyMockBeanDefinitioRegistrynPostProcessor();
}
}
And add it to context:
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(MockBeanConfig.class);
context.register(MyExtLibConfig.class);
context.refresh();

Categories

Resources