Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@LookupMethod annotation for use with component scanning [SPR-5192] #9865

Closed
spring-issuemaster opened this issue Oct 1, 2008 · 20 comments

Comments

@spring-issuemaster
Copy link
Collaborator

commented Oct 1, 2008

Casey Butterworth opened SPR-5192 and commented

I have recently started some work on an @LookupMethod annotation to be used in the following situation:

  • A prototype scoped bean needs to be used within a singleton
  • The singleton was created using component scanning

Currently the most obvious solution would be to forgo component scanning for the singleton and define the lookup-method in the ApplicationContext XML or using spring-java-config, e.g:

<!-- this could also be created using component scanning -->
<bean id="myPrototype" class="sample.MyPrototype" scope="prototype"/>

<bean id="mySingleton" class="sample.MySingleton">
  <lookup-method name="createMyPrototype" bean="myPrototype"/>
</bean>

However, since I've been using component scanning with 2.5, it doesn't feel right defining the wiring outside of the components, and it would be ideal if we could do something like the following:

@Component
public abstract class MySingleton {
   @LookupMethod(beanRef = "myPrototype")
   protected abstract MyPrototype createMyPrototype();
}

@Component
public class MyPrototype {}

It would be even better if @LookupMethod could work in conjunction with autowiring (by type), but that can be a subsequent feature request.

I've started to implement a solution and will attach shortly.


Attachments:

Issue Links:

  • #12089 Passing lookup-method arguments to created bean constructor
  • #15860 BeanFactory lacks method for getting bean by type with specified constructor arguments

Referenced from: commits eb0ab84

37 votes, 33 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 1, 2008

Casey Butterworth commented

I am attaching 2 files:

  • LookupMethod.java - This is the actual annotation and pretty much speaks for itself.
  • LookupMethodAnnotationBeanPostProcessor.java - MergedBeanDefinitionPostProcessor that adds the LookupMethod to the RootBeanDefinition

Note that this solution does not work yet for reasons I will outline in an additional comment.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 1, 2008

Casey Butterworth commented

As you might expect, this change isn't quite this straightforward otherwise I assume it would already have happened, but hopefully the following can be resolved in a future spring version:

  • The ClassPathScanningCandidateComponentProvider rejects beans that are not concrete but this kind of Method Injection will only ever occur on abstract classes. For my testing I have simply removed this rule (see ClassPathScanningCandidateComponentProvider#isCandidateComponent) but I'm not sure what the overall implications of this might be. Perhaps this could also scan for the presence of @LookupMethod on all non-concrete methods in the class and allow the bean to be registered if that condition is satisfied?

  • More significantly, for reasons I can't yet understand, all registered MergedBeanDefinitionPostProcessors are called after the bean has been instantiated. This is obviously a problem for us as any attempt to instantiate the bean defintion prior to registering the LookupOverrides fails, as (a) the class is abstract and cannot be instantiated, and (b) the InstantiationStrategy is not yet aware that the bean will need to be proxied.

I can think of a few solutions to this:

  1. Find a way to access the RootBeanDefinition from the InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation method (I had a quick look but couldn't figure it out)
  2. Call the MergedBeanDefintionPostProcessors prior to instantiation
  3. Provide a new PostProcessor type (invoked around the same time as InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation) that provides the caller with access to the RootBeanDefinition (much like the existing MergedBeanDefintionPostProcessors).

Personally my preference is option 2, but i guess this will need some impact analysis from someone who understands the lifecycle in more depth than me. If anyone knows how to go about Option 1 I am very open to ideas!

Anyway, hopefully this provides enough information for someone to provide some feedback and ideas.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 1, 2008

Casey Butterworth commented

Sorry, accidentally attached the annotation twice (can an admin delete?)

In any event, we really only need the 0.3 kB version.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Sep 16, 2009

Benjamin Lerman commented

Example project for LookupMathodBeanFactoryPostProcessors

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Sep 16, 2009

Benjamin Lerman commented

Hi,

I just attach an other try at this one.

I did not start from an AnnotationBeanPostProcessor but from an BeanFactoryPostProcessor because one need to be able to change the bean before it is instanciated.

I also tried to implements some of the Qualifier mechanism. It is far from complete, but as long a @Qualifier won't be usable on methods, I do not see how to do something more complete.

The other limitation is that I cannot do anything for abstract class, because those are not registered by ClassPathScanningCandidateComponentProvider. That means that bean that are proxied must still have a default implementation of the factory method.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 19, 2010

Cuneyt Tuna commented

Is there any progress on this issue?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 4, 2011

Martin Green commented

Is there any chance we could get this annotation - it would be extremely usefull and make the code much more self documenting.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 13, 2011

Marko Topolnik commented

I would just like to point out that the bean class does not need to be abstract -- in fact, I have never used it that way. This is the way I do it:

public class MySingleton {
  protected MyPrototype createMyPrototype() {
    throw new UnsupportedOperationException("Spring should override this lookup method");
  }
}
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 21, 2011

Alexis Torres commented

Hello,

I was really interested in that option till I understood @ScopedProxy annotation.
The elegant work around is making the singleton autowire a ScopeProxy which has a prototype behavior.

Not saying that this shouldn't be implemented but maybe priority is not as important.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented May 21, 2012

Dmitry Katsubo commented

I would vote for @Autowired to work for such lookup methods naturally, so there is no need to introduce new annotation (@LookupMethod):

@Autowired
protected MyPrototype createMyPrototype() { ... }
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 14, 2012

Alexey Fansky commented

So are there any solutions for this?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 12, 2012

Phil Webb commented

Generally the preferred solution with this type of problem is to use the javax.inject.Provider interface from JSR-330. It tends to result in clearer code and removes the need to throw the UnsupportedOperationException.

public class MySingleton {

  @Autowired
  private Provider<MyPrototype> myPrototype;

  public void operation() {
    MyPrototype instance = myPrototype.get();
    // do something with the instance
  }

}
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 13, 2014

Juergen Hoeller commented

I'm considering this for 4.1 still, following up on the work in #15860 and #12089. It is actually quite straightforward to implement, even nicer with a by-type lookup option if no bean name has been specified... and can easily support arguments for factory methods or constructors as well.

Juergen

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 14, 2014

Juergen Hoeller commented

Finally introduced as @Lookup annotation, matching by the method's return type or by a specified bean name (if any), and supporting arguments as well.

Juergen

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 25, 2016

mirak commented

for prototypes, why can't we just call the @Bean method from the @Configuration class ?

@Configuration
public class MyConfig implements HouseFactory {

@Override
@Bean
@Scope("prototype")
public House house(String name, int windows){
    return new House(name,windows);
}
}

public class Street{
   @Autowired
   private HouseFactory houseFactory;

    public void buildSomeHouses(){
        House house1 = houseFactory.house("blue house",5);
        House house2 = houseFactory.house("red house",9);
    }

} 

public interface HouseFactory {

     House house(String name, int windows);
}

Actually I tried this on spring 3.2.16 which I am stuck on until we migrate servlet versions, and the issue is that it fails because it tries to autowire a String bean and Integer bean, which it can't find of course.
But since @Configuration files are kind of factories, what we are doing actually is to implement HouseFactory, that will inject ApplicationContext and call applicationContext.getBean( ... ) wich feels like duplicating code.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 25, 2016

Juergen Hoeller commented

I'm afraid @Bean methods are not meant to be called as factory methods from outside. They are rather designed as a factory method callback mechanism within the container, with the container calling them when a particular bean needs to be created. BeanFactory.getBean is indeed the primary entry point from outside.

Since you mentioned it, why specifically do you feel stuck on Spring 3.2.x? Spring 4.x is still runtime-compatible with Servlet 2.5 containers, so does that mean you're on Servlet 2.4? Which application server is it in your case?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 25, 2016

mirak commented

This is a Jonas 4 something based on tomcat5, which supports servlet 2.4 only. I realised that when I tried to used spring 4.x . We are supposed to migrate to Websphere 8.5.5.4 someday ...

Regarding the BeanFactory.getBean what we do actually is something like

	@Bean
	public House houseFactory() {
		return new HouseFactory() {

			@Override
			public HouseFactory House house(String name, int windows) {
				return ctx.getBean("house", name, windows );
			}

		};
	}

We don't expose the spring api in our business objects, and if a constructor changes, we know the impacts at compile time.
This is what looked interesting in being able to directly call the @Bean methods.
At first I though it was a bug because getBean manage to call this @Bean method in some way.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 26, 2016

mirak commented

I think I will emulate the spring 4.x @Lookup method by create a @Lookup annotation, and use an around aspect, on the will call BeanFactory.getBean( ... ) with the appropriate parameters.
This way we will be ready for spring 4.x, and just remove the aspect declaration.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 26, 2016

mirak commented

By the way I realised it's possible to also use an interface that implements the @Lookup method and the @Bean method in the @Configuration file, to be sure to have the same parameters.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 29, 2016

mirak commented

I'm afraid @Bean methods are not meant to be called as factory methods from outside. They are rather designed as a factory method callback mechanism within the container, with the container calling them when a particular bean needs to be created.

It seems this JIRA #17048 fixes my issue in Spring 4.1.3 .
Maybe you had time to reflect on this, but what you say now about user call of @Bean methods, seems a bit more cautious than what you commented back then here #17048

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.