Spring get bean only if already instanced for scope - spring

Hi i'm using Spring 3
i'm using
applicationContext.getBeansOfType
is there a possibility to get only the beans already instanciated for the current scope?
I don't want to instanciate Beans which have not already been used in the current request.

I think that not, but you could write one, something like:
public static List<Object> getBeansInScope(Class<?> type, AbstractApplicationContext ctx, int scope) {
List<Object> beans = new ArrayList<Object>();
String[] names = ctx.getBeanNamesForType(type);
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
for (String name : names) {
Object bean = attributes.getAttribute(name, scope);
if (bean != null)
beans.add(bean);
}
return beans;
}
That only work for request and session scopes.

Related

Getting Class annotation for a given Spring Bean

I have two custom annotation as described below.
CustomAnnotationMain is a Spring Component based annotation.
CustomAnnotationChild is a Spring Bean based annotation.
Below is the code snippet which uses the 2 custom annotations.
#CustomAnnotationMain(value = "parent")
public class MainClass{
#CustomAnnotationChild(value = "child1")
public ObjectBuilder getObject1() {
// logic
}
#CustomAnnotationChild(value = "child2")
public ObjectBuilder getObject2() {
// logic
}
}
Question: How can I get the list of all CustomAnnotationMain annotated classes and also all the beans + annotation infos that are available as part of the component?
I did the following to get all the beans annotated with #CustomAnnotationChild. But I am not sure how to access the class in which the bean is available. I need to access #CustomAnnotationMain for a given bean.
allBuilders = context.getBeansOfType(ObjectBuilder.class);
PS: This is not Spring Boot based project. I use only the spring core libs.
I did something similar. Introduced an interface Proxyable and need to find all the beans annotated with the interface or create proxy s for all defined interfaces.
https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/service/ProxyableScanRegistrar.java
In your case you should replace Proxyable with your CustomAnnotationMain.
The logic of ClassPathScanningCandidateComponentProvider definition can be changed to reflect your filter (I need there interfaces only).
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LOG.debug("Registering #Proxyable beans");
// Get the ProxyableScan annotation attributes
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ProxyableScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages.length == 0) {
// If value attribute is not set, fallback to the package of the annotated class
basePackages = new String[]{((StandardAnnotationMetadata) metadata).getIntrospectedClass().getPackage().getName()};
}
// using these packages, scan for interface annotated with Proxyable
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment) {
// Override isCandidateComponent to only scan for interface
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent() && metadata.isInterface();
}
};
provider.addIncludeFilter(new AnnotationTypeFilter(Proxyable.class));
ControllerFactory factory = getControllerFactory((DefaultListableBeanFactory) registry);
// Scan all packages
for (String basePackage : basePackages) {
for (BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage)) {
try {
Class c = this.getClass().getClassLoader().loadClass(beanDefinition.getBeanClassName());
if (!hasImplementingClass(c, basePackages)) {
//creating missing beans logic is skipped
}
} catch (ClassNotFoundException e) {
throw new SOAControllerCreationException("cannot create proxy for " + beanDefinition.getBeanClassName());
}
}
}
}
}
Hope it helps

Howto inject Picocli parsed parameters into Spring Bean definitions?

I'm trying to use Picocli with Spring Boot 2.2 to pass command line parameters to a Spring Bean, but not sure how to structure this. For example, I have the following #Command to specify a connection username and password from the command line, however, want to use those params to define a Bean:
#Component
#CommandLine.Command
public class ClearJdoCommand extends HelpAwarePicocliCommand {
#CommandLine.Option(names={"-u", "--username"}, description = "Username to connect to MQ")
String username;
#CommandLine.Option(names={"-p", "--password"}, description = "Password to connect to MQ")
String password;
#Autowired
JMSMessagePublisherBean jmsMessagePublisher;
#Override
public void run() {
super.run();
jmsMessagePublisher.publishMessage( "Test Message");
}
}
#Configuration
public class Config {
#Bean
public InitialContext getJndiContext() throws NamingException {
// Set up the namingContext for the JNDI lookup
final Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password);
return new InitialContext(env);
}
#Bean
public JMSPublisherBean getJmsPublisher(InitialContext ctx){
return new JMSPublisherBean(ctx);
}
}
I'm stuck in a bit of a circular loop here. I need the command-line username/password to instantiate my JMSPublisherBean, but these are only available at runtime and not available at startup.
I have managed to get around the issue by using Lazy intialization, injecting the ClearJdoCommand bean into the Configuration bean and retrieving the JMSPublisherBean in my run() from the Spring context, but that seems like an ugly hack. Additionally, it forces all my beans to be Lazy, which is not my preference.
Is there another/better approach to accomplish this?
Second option might be to use pure PicoCli (not PicoCli spring boot starter) and let it run command; command will not be Spring bean and will only be used to validate parameters.
In its call method, Command would create SpringApplication, populate it with properties (via setDefaultProperties or using JVM System.setProperty - difference is that environment variables will overwrite default properties while system properties have higher priority).
#Override
public Integer call() {
var application = new SpringApplication(MySpringConfiguration.class);
application.setBannerMode(Mode.OFF);
System.setProperty("my.property.first", propertyFirst);
System.setProperty("my.property.second", propertySecond);
try (var context = application.run()) {
var myBean = context.getBean(MyBean.class);
myBean.run(propertyThird);
}
return 0;
}
This way, PicoCli will validate input, provide help etc. but you can control configuration of Spring Boot application. You can even use different Spring configurations for different commands. I believe this approach is more natural then passing all properties to CommandLineRunner in Spring container
One idea that may be useful is to parse the command line in 2 passes:
the first pass is just to pick up the information needed for configuration/initialization
in the second pass we pick up additional options and execute the application
To implement this, I would create a separate class that "duplicates" the options that are needed for configuration. This class would have an #Unmatched field for the remaining args, so they are ignored by picocli. For example:
class Security {
#Option(names={"-u", "--username"})
static String username;
#Option(names={"-p", "--password"}, interactive = true, arity = "0..1")
static String password;
#Unmatched List<String> ignored;
}
In the first pass, we just want to extract the username/password info, we don't want to execute the application just yet. We can use the CommandLine.parseArgs or CommandLine.populateCommand methods for that.
So, our main method can look something like this:
public static void main(String[] args) throws Exception {
// use either populateCommand or parseArgs
Security security = CommandLine.populateCommand(new Security(), args);
if (security.username == null || security.password == null) {
System.err.println("Missing required user name or password");
new CommandLine(new ClearJdoCommand()).usage(System.err);
System.exit(CommandLine.ExitCode.USAGE);
}
// remainder of your normal main method here, something like this?
System.exit(SpringApplication.exit(SpringApplication.run(MySpringApp.class, args)));
}
I would still keep (duplicate) the usage and password options in the ClearJdoCommand class, so the application can print a nice usage help message when needed.
Note that I made the fields in the Security class static.
This is a workaround (hack?) that allows us to pass information to the getJndiContext method.
#Bean
public InitialContext getJndiContext() throws NamingException {
// Set up the namingContext for the JNDI lookup
final Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
env.put(Context.SECURITY_PRINCIPAL, Security.username); // use info from 1st pass
env.put(Context.SECURITY_CREDENTIALS, Security.password);
return new InitialContext(env);
}
There is probably a better way to pass information to this method.
Any Spring experts willing to jump in and show us a nicer alternative?

Adding Spring bean asynchronously

In my Grails 3.1.14 I app need to add a bean, created in a callback of the asynchronous method call:
Vertx.clusteredVertx( [:] ){ AsyncResult<Vertx> res ->
if( res.succeeded() ){
Vertx vertx = res.result() // << this should be injected into the appContext
}
}
so that the instance can be autowired into other artefacts across the whole application.
What's the proper way to achieve this?
Shall I do it with StaticApplicationContext or would it break anything?
Another way would be to use a "container-bean" and set it's propery upon completion of the async method, but it's kind of ugly.
You can create a wrapper bean that configures its properties based on application context events, using InitializingBean interface.
class VertxWrapper implements InitializingBean {
Vertx vertx
void afterPropertiesSet() throws Exception {
Vertx.clusteredVertx( [:] ){ AsyncResult<Vertx> res ->
this.vertx = res.result() // << this should be injected into
}
}
}
}
And then simply access vertxWrapper.vertx from any bean that has vertxWrapper injected into it.

Can I programmatically add a qualifier to a bean?

I am registering transaction managers in my code, I would normally use annotation based configuration but as I don't know until runtime how many data sources (and hence transaction managers) there will be, I have to programmatically register these, as follows:
private final void registerTransactionManagerBean(final DataSource dataSource, ConfigurableApplicationContext context) {
String transactionManagerName = this.getName() + "-transactionManager";
context.getBeanFactory().registerSingleton(transactionManagerName, new DataSourceTransactionManager(dataSource));
LOG.info("Registering transaction manager under name : " + transactionManagerName);
}
Assuming this.getName() returned 'mydb', I originally expected to be able to qualify a transaction manager like this:
#Transactional("mydb-transactionManager")
What I've realised however is the value of that annotation refers to the qualifier and not the name. I did a quick test by declaring a bean as below and it works:
#Bean
#Qualifier("mydb-transactionManager")
public PlatformTransactionManager test() {
return new DataSourceTransactionManager(new EmbeddedDatabaseBuilder().build());
}
My question is, is there a way I can programmatically add a qualifier when registering a bean?
UPDATE
I've worked this out, I'm falling foul of this problem (in BeanFactoryAnnotationUtils:isQualifierMatch):
catch (NoSuchBeanDefinitionException ex) {
// ignore - can't compare qualifiers for a manually registered singleton object
}
I am manually registering my transaction manager bean so I presume this is why I'm stuck. I'm not really sure what options that gives me apart from to not programmatically register transaction managers as a runtime thing sadly.
I've worked this out, I'm falling foul of this problem:
catch (NoSuchBeanDefinitionException ex) {
// ignore - can't compare qualifiers for a manually registered singleton object
}
I am manually registering my transaction manager bean so I presume this is why I'm stuck. I'm not really sure what options that gives me apart from to not programatically register transaction managers as a runtime thing sadly.
Raised as a JIRA issue - https://jira.spring.io/browse/SPR-11915
public class RuntimeRegistrationWithQualifierTest {
private AnnotationConfigApplicationContext context;
#Test
public void beanWithQualifier() {
final GenericBeanDefinition helloBeanDefinition = new GenericBeanDefinition();
helloBeanDefinition.addQualifier(new AutowireCandidateQualifier(Hello.class));
final GenericBeanDefinition worldBeanDefinition = new GenericBeanDefinition();
worldBeanDefinition.addQualifier(new AutowireCandidateQualifier(World.class));
final DefaultListableBeanFactory factory = context.getDefaultListableBeanFactory();
factory.registerBeanDefinition("helloBean", helloBeanDefinition);
factory.registerSingleton("helloBean", "hello");
factory.registerBeanDefinition("worldBean", worldBeanDefinition);
factory.registerSingleton("worldBean", "world");
context.register(Foo.class);
context.refresh();
final Foo foo = context.getBean(Foo.class);
assertThat(foo.hello).isEqualTo("hello");
assertThat(foo.world).isEqualTo("world");
}
#Before
public void newContext() {
context = new AnnotationConfigApplicationContext();
}
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, PARAMETER})
#interface Hello {}
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, PARAMETER})
#interface World {}
static class Foo {
final String hello;
final String world;
Foo(#Hello final String hello, #World final String world) {
this.hello = hello;
this.world = world;
}
}
}

How do I override a scoped bean for tests?

I have this bean in my Spring Java config:
#Bean
#Scope( proxyMode=ScopedProxyMode.TARGET_CLASS, value=SpringScopes.DESKTOP )
public BirtSession birtSession() {
return new BirtSession();
}
For tests, I need a mock without a scope (there is no "Desktop" scope in the test). But when I create a configuration for my test which imports the above configuration and contains:
#Bean
public BirtSession birtSession() {
return new MockSession();
}
I get a "Desktop" scoped mocked bean :-(
How do I make Spring "forget" the #Scope annotation?
PS: It works when I don't use #Import and use copy&paste but I don't want to do that.
The problem seems to be in ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod() that uses ScopedProxyCreator.createScopedProxy() static method to create the scoped bean definition:
// replace the original bean definition with the target one, if necessary
BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
beanDefToRegister = proxyDef.getBeanDefinition();
}
As the BeanDefinitionHolder returns a RootBeanDefinition instead of ConfiguratioClassBeanDenition the scoped proxy bean definition (ie, the ScopedProxyFactoryBean) cannot be overriden by another Java Configuration class.
A workaround could be declaring the scoped beans to override in a xml configuration file and importing it with #ImportResource.
The problem isn't Spring keeping the annotation, the problem is that Spring first tries to parse the "productive" config and in order to do that, it checks whether the scope is available. Spring checks scopes eagerly. So it never gets to the second/overriding bean definition.
Create a dummy scope:
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
public class MockSpringScope implements org.springframework.beans.factory.config.Scope {
private Map<String, Object> objects = new HashMap<String, Object>();
#Override
public Object get( String name, ObjectFactory<?> objectFactory ) {
Object result = objects.get( name );
if( null == result ) {
result = objectFactory.getObject();
objects.put( name, result );
}
return result;
}
#Override
public Object remove( String name ) {
return objects.remove( name );
}
#Override
public void registerDestructionCallback( String name, Runnable callback ) {
// NOP
}
#Override
public Object resolveContextualObject( String key ) {
// NOP
return null;
}
#Override
public String getConversationId() {
// NOP
return null;
}
}
and register that under as "Desktop" scope. That will Spring allow to successfully parse the production config.

Resources