I know it's possible to inject a request scoped bean into a singleton bean in Spring so I know what I'm trying to do will work, I'm just wondering if there is a way to express it more concisely without so many extra unnecessary class definitions. I'm new to Spring annotations so maybe there's an annotation I don't know about.
I have an abstract class that will be extended maybe 100 times in my application as different singleton spring beans. Take this class definition for an example:
/** The abstract class with a field that needs to be request-specific **/
public abstract class AbstractSingletonBean {
private SampleState state;
public SampleState getState() { return state; }
public void setState(SampleState state) { this.state = state; }
// Other fields that are just singleton here
}
And an example of what one of the bean definitions might look like:
#Component
public class SampleSingletonBean extends AbstractSingletonBean {
#Resource(name="sampleState")
public void setState(SampleState state) { super.setState(state); }
}
Now of course we need a bean called sampleState. So I have to create two classes: a base class to define the fields in SampleState and then a request-scoped bean definition. This is because each extension of AbstractSingletonBean will need it's own request-scoped instance of the state field.
Here might be the base class:
public class SampleState {
private String fieldOne;
public String getFieldOne() { return fieldOne }
public void setFieldOne() { this.fieldOne = fieldOne }
}
And here is this silly bean definition:
#Component ("sampleState")
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SampleStateBean extends SampleState {}
The thing that bothers me is that if I have 100 extensions of AbstractSingletonBean, I'll need 100 extensions of SampleStateBean with just boilerplate code to make it request-scoped. Is there a way to just override setState() in the extensions of AbstractSingletonBean and indicate to spring that it should create a new request scoped bean on the fly and inject it here? So my SampleSingletonBean could look like this:
#Component
public class SampleSingletonBean extends AbstractSingletonBean {
#Resource
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public void setState(SampleState state) { super.setState(state); }
}
Of course this doesn't work because #Resource needs to refer to a bean that already exists. Is there another annotation to accomplish this without creating a new class for every SampleState bean?
Spring can inject into abstract classes too. So you can move the injection of the SampleState to the abstract class, if each AbstractSingletonBean descendant needs just a SampleState (as in your example).
It doesn't look like this was available out of the box so I created an annotation I call #AnonymousRequest that I put on the field I want, and a BeanDefinitionRegistryPostProcessor to do the work of creating the bean. It basically goes like this:
for each bean in the BeanFactory
if bean class has AnonymousRequest annotation
create request scoped bean from field class
create singleton bean to be request scoped bean wrapper
set the annotated property value to the singleton wrapper
This took a lot of work to figure out how Spring registers request scoped beans. You create the bean definition you want as a request scoped bean. Then you create a singleton bean of type RootBeanDefinition that acts as a wrapper to the request scope bean and set a property on the wrapper called "targetBeanName" to whatever you named the request scoped bean ("scopedTarget." + the singleton bean name by convention).
So this could probably be improved by someone who actually knows this stuff but here's what I came up with:
public void createRequestBeanFromSetterMethod(String containingBeanName, BeanDefinition containingBean, Method method, BeanDefinitionRegistry registry)
{
String fieldName = ReflectionUtil.getFieldNameFromSetter(method.getName());
String singletonBeanName = containingBeanName+"_"+fieldName;
String requestBeanName = "scopedTarget."+singletonBeanName;
BeanDefinition requestBean = createAnonymousRequestBean(ReflectionUtil.getFieldTypeFromSetter(method), containingBean);
RootBeanDefinition singletonBean = new RootBeanDefinition();
singletonBean.setBeanClass(ScopedProxyFactoryBean.class);
singletonBean.getPropertyValues().addPropertyValue("targetBeanName", requestBeanName);
registry.registerBeanDefinition(singletonBeanName, singletonBean);
registry.registerBeanDefinition(requestBeanName, requestBean);
beanDefinition.getPropertyValues().addPropertyValue(fieldName, new RuntimeBeanReference(singletonBeanName));
}
private BeanDefinition createAnonymousRequestBean(Class<?> beanType, BeanDefinition parentBean)
{
BeanDefinition newBean = null;
if (parentBean != null)
{
newBean = new GenericBeanDefinition(parentBean);
}
else
{
newBean = new GenericBeanDefinition();
}
if (beanType != null)
{
newBean.setBeanClassName(beanType.getName());
}
newBean.setScope("request");
newBean.setAutowireCandidate(false);
// This would have come from the Proxy annotation...could add support for different values
String proxyValue = "org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass";
BeanMetadataAttribute attr = new BeanMetadataAttribute(proxyValue, true);
newBean.setAttribute(proxyValue, attr);
return newBean;
}
It seems to work! I effectively have now a request scoped bean created just before the context initialization that is localized to this one containing bean. It's a request-scoped property, more or less.
You can try defining single SampleState request scope bean and then use spring's lookup method to inject this bean wherever you want to.That's works just fine with prototype-scope beans. Fingers crosses it would work with request scope as well.
AFAIK, there is no annotation support for lookup method as of now, so either use it's xml vis-a-vis
or have a look at javax.inject.Provider relevant question here
Related
I am using an ObjectProvider to create instances of a prototype scope bean using the getObject() method. Something like this
#Configuration
class Config {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
SomeType typeOne() {
return new SomeType();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
SomeType typeTwo(String param) {
return new SomeType(param);
}
}
#Service
class Service {
private ObjectProvider<SomeType> objectProvider;
public Service(
ObjectProvider<SomeType> objectProvider) {
this.objectProvider = objectProvider;
}
#Override
public String performAction() {
return getSomeType().doAction();
}
private SomeType getSomeType() {
return objectProvider.getObject();
}
}
But since there are two beans of the type that the ObjectProvider is trying to get (SomeType), I get a NoUniqueBeanDefinitionException. (And I do need the other bean of the same type, because that one I need to provide parameters using objectProvider.getObject(Object... params) )
Playing around and debugging Spring I saw that if you name your ObjectProvider exactly like your bean then it works, something like:
private ObjectProvider<SomeType> typeOne;
My question is, are there other ways to use an ObjectProvider and manage to resolve ambiguity, or is this approach the way to go?
Short answer is you just need to properly qualify the ObjectProvider you want injected, like this:
public Service(#Qualifier("typeOne") ObjectProvider<SomeType> objectProvider) {
this.objectProvider = objectProvider;
}
With Spring configuration, when you specify a bean via a method, and don't specify it's name with #Bean("NAME"), Spring uses the method name as the bean name.
Similarly, when injecting a bean that is not specified by #Qualifier("NAME"), Spring takes the injected variable as the name, if that don't exists or is not unique, you might get some exceptions informing you about this (like the NoUniqueBeanDefinitionException you facing).
So, if you match the bean name and the injected variable name you don't need to be more specific, but if you don't, #Qualifier is there to your rescue :D
The following example shows explicit wiring of dependencies using spring java config that results in a different bean being wired in while using and interface for a spring configuration class.
This seems like it shouldn't occur or at least give the normal warning that there are two beans as candidates for autowiring and it doesn't know which to select.
Any thoughts on this issue? My guess is there is no real name spacing between configuration classes as is implied by the syntax "this.iConfig.a()" Could this be considered a bug (if only for not warning about the 2 candidate beans)?
public class Main
{
public static void main( final String[] args )
{
final ApplicationContext context = new AnnotationConfigApplicationContext( IConfigImpl.class, ServiceConfig.class );
final Test test = context.getBean( Test.class );
System.out.println( test );
}
}
public class Test
{
private final String string;
public Test( final String param )
{
this.string = param;
}
public String toString()
{
return this.string;
}
}
#Configuration
public interface IConfig
{
#Bean
public String a();
}
#Configuration
public class IConfigImpl implements IConfig
{
#Bean
public String a()
{
return "GOOD String";
}
}
#Configuration
public class ServiceConfig
{
#Autowired
IConfig iConfig;
#Bean
Test test()
{
return new Test( this.iConfig.a() );
}
#Bean
String a()
{
return "BAD String";
}
}
In this case, I would expect to have "GOOD String" to be always be wired in the Test object, but flipping the order of IConfigImpl.class, ServiceConfig.class in the context loader changes which string is loaded.
Tested with Spring 4.0.7
EDIT: Further testing shows this has nothing to to with inherented configs. Same thing results if you drop the IConfig interface.
I believe this was a behavior of Spring for years.
If you redefine a bean, the one that is being loaded as last wins.
Another question would be how to control the order of bean loading when java configs are used. Check out this article http://www.java-allandsundry.com/2013/04/spring-beans-with-same-name-and.html which shows you how to do the ordering by using #Import of the other Spring java config.
The solution is actually simple - if you need to override a previously
defined bean(without say the flexibility of autowiring with a
different bean name), either use the XML bean configuration for both
the bean being overridden and the overriding bean or use the
#Configuration. XML bean configuration is the first example in this
entry, the one with #Configuration would be something like this:
#Configuration
public class Context1JavaConfig {
#Bean
public MemberService memberService() {
return new MemberSvcImpl1();
}
}
#Configuration
#Import(Context1JavaConfig.class)
public class Context2JavaConfig {
#Bean
public MemberService memberService() {
return new MemberSvcImpl2();
}
}
Stepan has mentioned the issue of order. The following is about your comment on their answer
Overriding beans of the same name makes sense, but in this case, I'm
specifically referencing the bean as specified in the iConfig
configuration. I would expect to get the one specified there.
In order to implement #Configuration and the caching of beans so that calls like
#Configuration
class Example {
#Bean
public UncaughtExceptionHandler uncaughtExceptionHandler() {
return (thread, throwable) -> System.out.println(thread + " => " + throwable.getMessage());
}
#Bean
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Thread newThread() {
Thread thread = new Thread();
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler()); // <<<<<< allowing this
return thread;
}
}
Spring actually uses CGLIB to create a proxy subtype of the #Configuration annotated class. This proxy maintains a reference to the backing ApplicationContext and uses that to resolve a bean.
So the call in your example
return new Test(this.iConfig.a());
isn't really invoking IConfigImpl#a(). It invokes this code (as of 4.2) from the proxy interceptor. The code uses the corresponding Method to determine the target bean name and uses the ApplicationContext's BeanFactory to resolve the bean. Since the bean definition for a bean named a has already been overriden, that new bean definition gets used. That bean definition is using the ServiceConfig#a() method as its factory method.
This is described in the documentation, here
All #Configuration classes are subclassed at startup-time with CGLIB.
In the subclass, the child method checks the container first for any
cached (scoped) beans before it calls the parent method and creates a
new instance.
Could this be considered a bug [...]?
I don't believe so. The behavior is documented.
I have a list of annotations which I have retrieved using
java.lang.reflect.Method.getDeclaredAnnotations()
Using these annotations, how can I retrieve spring beans which are tagged
with these annotations?
Pseudo code:
public interface Work{...}
#Component
#A1
public class SpringBean1 implements Work{}
#Component
#A2
public class SpringBean2 implements Work{}
public class Test
{
public void doSomething()
{
Annotation[] annotations = getAnnotationsListUsingReflection();
for(Annotation annotation: annotations)
{
Work work = /* Retrieve bean using annotation, how to do this? */
work.xyz();
......
}
}
}
Assuming your annotations are annotating the bean types and not their methods, you can use ListableBeanFactory#getBeanNamesForAnnotation(Class).
Find all names of beans whose Class has the supplied Annotation type,
without creating any bean instances yet.
AnnotationConfigApplicationContext is one implementation of that interface.
Given such an instance
AnnotationConfigApplicationContext ctx = ...;
String[] beanNames = ctx.getBeanNamesForAnnotation(Annotation.class);
Then iterate through the bean names and get each bean
for (String beanName : beanNames) {
Object bean = ctx.getBean(beanName);
// use it
}
Alternatively, ListableBeanFactory#getBeansWithAnnotation(Class) can do the work for you:
Find all beans whose Class has the supplied Annotation type, returning
a Map of bean names with corresponding bean instances.
I have a class which I want to be a bean
public class SomeBean{
public SomeBean(){
//default constructor
}
public SomeBean(String someStr){
//constructor with arguments.
}
}
In order to create manually CDI bean I do the following
Bean<?> bean = (Bean<?>) beanManager.resolve(beanManager.getBeans(SomeBean.class));
SomeBean someBean =(SomeBean) beanManager.getReference(bean, bean.getBeanClass(), beanManager.createCreationalContext(bean));
However the above method will create SomeBean instance wth default constructor. How can I create bean and pass String argument to construcot?
P.S. CDI - WELD
The standard way to define beans with given constructor arguments is via a producer method, e.g.
#Produces #ApplicationScoped #MyQualifier
public SomeBean myBean() {
return new SomeBean("foo");
}
Application code should not normally have to use the BeanManager, unless you want to create a CDI extension.
I have made a web application. In my first version I used one #ManagedBean with #SessionScoped annotation. But after I put some features to the web application I had too much methods in my managed bean. So I shared the Bean in six Beans with a #SessionScope.
The problem is, if I used the first managed bean call LoginBean. I have no problems. But after I changed on the next page called SampleForDB. For this page I used the SampleBean. In this manged bean (SampleBean) the values of the classes I set in the loginBean are null.
How can I made it to use the values in all #MangedBean classes?
#ManagedBean
#SessionScoped
public class Example{
private TestClass test = new TestClass();
public next(){
test.setmyVar(5);
}
}
#ManagedBean
#SessionScoped
public class Example2{
int testInt;
private TestClass test = new TestClass();
public after(){
int testInt = test.getmyVar(); // I get null
}
}
public class TestClass{
private int myVar;
public void setMyVar(int var){
this.myVar = var;
}
public int getMyVar(){
return myVar;
}
}
This is an example for my Problem. My variable myVar is empty when I want to get it work in another Managed Bean.
There's no SampleBean or LoginBean in your code sample, but your problem is caused by the new operator in any case.
Using new to get #ManagedBean instances tells JSF "hey, I don't need to manage those objects". Each time JSF constructs a new java object from Example2 it'll also create a new TestClass instance. To access your existing bean, use something like this:
#ManagedBean #SessionScoped
public class LoginBean implements Serializable {
private String userLogin;
public void doLogin() { /* assign userLogin */ }
}
#ManagedBean #SessionScoped
public class SampleBean implements Serializable {
#ManagedProperty("#{loginBean.userLogin}")
private String login;
private String greeting;
public void sayHello() {
greeting = "Hello " + login;
}
}
You typically don't want a full bean injected somewhere, concentrate on the attributes you really need to get the job done.
Recommended reading:
JavaEE tutorial
How to choose the right bean scope?
You probably don't need to use so many managed beans. Just use one managed bean and include other classes in it. One managed bean per JSF page.
As you provided the code for you question: the problem is that you have two different instances of your TestClass. So when you set value in first instance it doesn't set in the second. That's why you get null there. What you should do is to share the same object instance between different managed beans. It can be done different ways:
Put your TestClass in session context in first managed bean and retrieve it from context in the second managed bean.
Apply singleton pattern and get the same instance with .getInstance() method.
When you created the instance of TestClass in the first managed bean, set the same value for the second managed bean from the first one.
Use dependency injection mechanism in your application (CDI or Spring).
Mark your TestClass as #ManagedBean with #SessionScoped or #ApplicationScoped and then use in other of your beans by injecting it as managed property #ManagedProperty("#{testClass}") TestClass testClass; Instead of #ManagedProperty you can directly use method FacesContext.getCurrentInstance().getExternalContext().getApplicationMap().get("testClass"); to retrieve your application scoped managed bean (for session scoped bean use .getSessionMap(). method instead). If you wish so you can additionally initialize TestClass with #PostConstruct annotation.