how to get bean scope on spring? - spring

I have the following class:
public class ServiceFactory {
private ServiceFactory() {
}
public static <T extends XXXX> T loadService(Class<T> klass) {
ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
return applicationContext.getBean(klass);
}
}
It loads beans at runtime (I have a specific reason to do it like this).
I need to check if the bean is annotated with #Scope(BeanDefinition.SCOPE_PROTOTYPE) or just enforce it to be a prototype.
How would I do this?

First you need to find a bean name for your class. Then you may look for BeanDefinition using that name and get scope.
public <T> String findScope(ConfigurableApplicationContext applicationContext, Class<T> type) {
String[] names = applicationContext.getBeanFactory().getBeanNamesForType(type);
if(names.length != 1){
throw new IllegalArgumentException("Could not find bean of type" + type.getCanonicalName());
}
return applicationContext.getBeanFactory().getBeanDefinition(names[0]).getScope();
}

Related

Is it safe to return "this" from a Spring bean method?

I want to create a chain api on a prototype bean.
#Component
#Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ReportGenerator {
List lines = new ArrayList();
public ReportGenerator append(String line){
lines.add(line);
return this;
}
}
public class SomeSingletonBean{
#Autowire
ReportGenerator rg;
void doSomeWork(){
rg.append("a").append("b");
}
}
The above seems to work fine.
But is this safe in all cases?
Also, can I safely do the same in a bean scoped "session"?

#Bean-method providing a list is ignored, if there is a #Bean-method proving a single bean

I have a configuration providing a single bean and a configuration providing a list of beans. All these beans have the same type.
When I start up an application context with these configurations, I see that an autowired list of the bean type only contains the single bean. I want it to include all beans of that type. I use Spring 5.2.0.
I have boiled it down to one configuration: if I provide a single bean and a list of beans, only the single bean will be used.
This is reproduced in the following test. It fails, because the list only contains "A" and "D" (which shows it did not autowire the list of beans):
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = { TestConfiguration.class })
class AutowiringListsTest {
#Autowired
private List<TestBean> testBeanList;
#Test
void testThatBothConfigurationsContributeToBeanList() {
final List<String> idList = testBeanList.stream().map(TestBean::getId).sorted().collect(Collectors.toList());
assertThat(idList, hasItems("A", "B", "C", "D"));
}
#Configuration
public static class TestConfiguration {
#Bean
public TestBean someBean() {
return new TestBean("A");
}
#Bean
public List<TestBean> someMoreBeans() {
return Arrays.asList(new TestBean("B"), new TestBean("C"));
}
#Bean
public TestBean anotherBean() {
return new TestBean("D");
}
}
public static class TestBean {
private final String id;
public TestBean(final String id) {
this.id = id;
}
private String getId() {
return id;
}
}
}
I want to get this to run so that multiple modules can provide beans of a certain type.
Some modules want to provide multiple beans and their number depends on a property.
Some modules will always provide one bean.
The module using the beans (autowiring them as list) should autowire all beans.
How can I get this to run? In what scenario does Spring's behavior make sense?
I can work around the issue by introducing a TestBeanFactory. Each configuration that wants to contribute to the list of TestBeans instead provides a factory.
#Configuration
public static class TestConfiguration {
/** Implemented once in the configuration that defines <code>TestBean</code>. */
#Bean
public List<TestBean> testBeansFromFactory(Collection<TestBeanFactory> factories) {
return factories.stream().map(TestBeanFactory::createTestBeans).flatMap(Collection::stream)
.collect(toList());
}
// Further methods can be defined in various configurations that want to add to the list of TestBeans.
#Bean
public TestBeanFactory someBean() {
return () -> Arrays.asList(new TestBean("A"));
}
#Bean
public TestBeanFactory someMoreBeans() {
return () -> Arrays.asList(new TestBean("B"), new TestBean("C"));
}
#Bean
public TestBeanFactory anotherBean() {
return () -> Arrays.asList(new TestBean("D"));
}
}
public static class TestBean { ... }
public static interface TestBeanFactory {
public Collection<TestBean> createTestBeans();
}
That works and is only slightly more code.
M.Deinum makes a point in the comments for Spring's behavior being consistent:
As you defined a bean of type List it will be used. Autowiring is based on type, it will try to detect the bean of the certain type. The collection (and map as well) is a special one looking up all dependencies of the given type.

find package name - Spring Boot library

I'm making a library to be used with Spring Boot. This lib define some annotations that can be used in methods.
How can I find (at runtime) the package of the application where the library is being used?
I need this in order to scan for the annotated methods.
You can implement BeanFactoryPostProcessor:
1. Using ConfigurableListableBeanFactory you can iterate over BeanDefinition
2. Determine if bean's class has your annotation
3. Get the package from the bean's class name
Example:
#Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Set<String> packages = findAnnotationUsagePackages(MyAnnotation.class, beanFactory);
...
}
private Set<String> findAnnotationUsagePackages(Class<? extends Annotation> annotationClass,
ConfigurableListableBeanFactory beanFactory) {
Set<String> annotationUsagePackages = new HashSet<>();
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
if (beanDefinition instanceof ScannedGenericBeanDefinition) {
ScannedGenericBeanDefinition genericBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition;
if (AnnotationUtils.isCandidateClass(genericBeanDefinition.getBeanClass(), annotationClass)) {
String beanClassName = genericBeanDefinition.getBeanClassName();
if (beanClassName != null) {
annotationUsagePackages.add(ClassUtils.getPackageName(beanClassName));
}
}
}
}
return annotationUsagePackages;
}
}
About the AnnotationUtils.isCandidateClass():
Determine whether the given class is a candidate for carrying the specified annotation (at type, method or field level)
Also pay attention to the AbstractBeanDefinition.getBeanClass():
Throws: IllegalStateException - if the bean definition does not define a bean class, or a specified bean class name has not been resolved into an actual Class yet
P.S. also you can collect classes or meta-information inside AnnotationUtils.isCandidateClass condition block

Spring Create Prototype Bean on demand and reference the bean created later

I am newbie to spring and I am trying to modify my app to implement spring framework.
My request is to create a new bean for every new request and then refer that bean later in the code, for setting the values to it from a singleton bean.
I am trying to declare the bean as prototype and refer that bean in my singleton bean using lookup method.
But my problem was when trying to get the created prototype bean later for setting the values, I see its creating new bean again when getting the bean.
#Component
public class PersonTransaction {
#Autowired
PersonContext btContext;
#Autowired
PersonMapper personMapper;
public void setPersonMapper(PersonViewMapper personMapper) {
this.personMapper = personMapper;
}
public PersonBTContext createContext() throws ContextException {
return btContext = getInitializedPersonBTInstance();
}
private PersonBTContext getContext() throws ContextException{
return this.btContext;
}
public void populateView(UserProfileBean userProfile) throws ContextException {
personMapper.populateView(userProfile,getContext());
}
#Lookup(value="personContext")
public PersonBTContext getInitializedPersonBTInstance(){
return null;
}
}
below is my prototype class
#Component("personContext")
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PersonContext extends ReporterAdapterContext {
private List<Person> persons = null;
private Person person = null;
private List<String> attributes = null;
private boolean multiplePersons = false;
private boolean attributeSelected = false;
public boolean isMultiple() {
return multiplePersons;
}
public boolean isAttributeSelected() {
return attributeSelected;
}
private void setAttributeSelected(boolean attributeSelected) {
this.attributeSelected = attributeSelected;
}
// remaining getters/setters
}
when i call createContext from singleton PersonTransaction class, it should create new prototype bean and how can get the created prototype bean later by calling getContext() method (what i am doing by this.btContext is returning new bean again, I guess !!)..
Need help in getting the created prototype bean later for setting the values.
appreciate ur help..
You want to create a request scoped bean, not a prototype scoped bean. Take a look at Quick Guide to Spring Bean Scopes which describes different bean scopes, including the request scope:
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public PersonContext personContext() {
return new PersonContext();
}
This should simplify your logic as long as you can discard the bean after the request is processed.

Spring #Required properties when creating #Bean annotated beans

I'm developing a Spring Boot application and am trying out using Java annotation-based bean creation (using #Configuration and #Bean) rather than the familiar old XML-based bean creation. I'm puzzled though. If I attempt to create a bean in XML but fail to set an #Required property I get a BeanInitializationException when the application context is created. In my trials so far with annotation-based bean creation though this does not seem to be the case.
For example:
public class MyClass {
...
#Required
public void setSomeProp(String val){
}
}
Then in Spring XML:
<bean class="MyClass"/>
This will blow up during application startup (and IntelliJ flags it) because the required property is not set. But the same does not seem to be true of this:
#Configuration
public class MyConfig {
#Bean
public MyClass myClass() {
return new MyClass();
}
}
This application starts up just fine even though the required property is not ever set. I must be missing something here, because this seems like a pretty key feature in Spring.
UPDATE
I did some digging & debugging and it turns out that the bean definition is somehow being flagged to skip checking that #Required fields are set. In the Spring class 'RequiredAnnotationBeanPostProcessor' the boolean method 'shouldSkip()' is returning true for beans created this way. When I used the debugger to force that method to return false bean creation did indeed blow up with the expected exception.
Seeing as I'm making a pretty basic Spring Boot application I'm inclined (as Zergleb suggests) to submit this as a bug.
UPDATE 2
Some further debugging has revealed that even if the field is getting set forcing the check still throws the same exception, as if it hadn't been set. So perhaps dunni is correct and there is no way for this to work with #Bean notation.
As you said I also could not get #Required to run as expected this may be a bug and needs to be reported. I have a few other suggestions that did work for me.
Class annotated with #Configuration
//With the bean set up as usual These all worked
#Bean
public MyClass myClass() {
return new MyClass();
}
When you annotate the class #Component and load using component scanning works as expected.(The component scanning part is important you either need your #Configuration class to either have #ComponentScan or perhaps remove #Configuration and replace with #SpringBootApplication and this will enable scanning for components without needing to wire them up using #Bean configs)
#Component // Added this
public class MyClass {
...
#Required //Failed as expected
public void setSomeProp(String val){
}
}
Use #Autowired(required=true) //Fails with BeanCreationException //No qualifying bean of type [java.lang.String] found for dependency
//No more #Component
public class MyClass {
...
#Autowired(required=true) //Fails
public void setSomeProp(String val){
}
}
#Autowired required=false //Does not crash
public class MyClass {
...
#Autowired(required=false) //Simply never gets called if missing
public void setSomeProp(String val){
}
}
#Value //Does not work if test.property is missing // Could not resolve placeholder 'test.property' in string value "${test.property}
public class MyClass {
#Value("${test.property}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp;
}
}
#Value with default value//Does not crash // When getSomeProp is called it returns "My Default Value"(Unless you have test.property=Anything in your application.properties file then it returns "Anything"
public class MyClass {
#Value("${test.property:My Default Value}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp; //Returns "My Default Value"
}
}
Inside your #Configuration file also fails if it cannot find anything to populate String someProp in the myClass method
#Bean
public MyClass myClass(String someProp) { //Fails being unable to populate this arg
MyClass myObj = new MyClass();
myObj.setSomeProp(someProp);
return ;
}
If course this won't work, since you create the object of MyClass yourself (new MyClass()), thus the annotations are not evaluated. If you create a bean with a #Bean method, the container will only make sure, that all dependencies are there (method parameters) and that the bean scope is adhered to, meaning if it's a singleton bean, only one bean is created per application context. The creation of the bean/object itself is solely the responsibility of the developer.
The equivalent of the xml <bean> tag is annotating the class with #Component, where the bean is created completely by the container, thus the annotations are evaluated.
As it is being said that when you are having your own #Configuration class where you are creating the bean by itself, #Required doesn't apply there.
When you already have a #Component, let Spring Boot do the component scan and at the required setter property you can add #Autowired and it will work fine.
Found this link on web- https://www.boraji.com/spring-required-annotation-example
For example:
I have a Component called Employee having Id and Name.
#Component
public class Employee {
int id;
String name;
public int getId() {
return id;
}
#Autowired
#Required
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I have a Configuration class called AppConfig.java
#Configuration
public class AppConfig {
#Bean
public int getId() {
return 1;
}
}
So now we see, that component Employee needs an Id property for binding during startup, so I wrote bean method of type Integer, which will get autowired during runtime. If you do not write a bean of type Integer, it will result a BeanCreationException.
And here is my main class file.
#SpringBootApplication
public class SingletonApplication {
public static void main(String[] args) {
ApplicationContext ctx =
SpringApplication.run(SingletonApplication.class, args);
Employee emp = (Employee)ctx.getBean(Employee.class);
System.out.println(emp.getId());
}
}

Resources