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

Plain FactoryBean declaration on @Bean method leads to early call (pre injection) [SPR-12141] #16755

Closed
spring-projects-issues opened this issue Sep 1, 2014 · 8 comments
Assignees
Labels
in: core status: backported type: bug
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Sep 1, 2014

Michał Jaśtak opened SPR-12141 and commented

Suppose that I have class annotated with @Configuration, in which I want to use Environment, injected by Spring Framework, something like this:

@Configuration
public class MyConfig {

@Inject
private Environment env;
...
}

When I declare bean using HttpInvokerProxyFactoryBean, and try to access injected environment to use one or the properties as for ex. service URL

@Bean
    public FactoryBean someService() {
        final HttpInvokerProxyFactoryBean factory = new HttpInvokerProxyFactoryBean();
        factory.setServiceInterface(SomeService.class);
        factory.setServiceUrl(env.getRequiredProperty("service.uri"));
        return factory;
    }

I get NPEx because Spring Framework is trying to instantiate the factory before Environment is injected.

The problem doesn't exist, if I declare returned type as FactoryBean<SomeService>, but because of current signature of HttpInvokerProxyFactoryBean, I'm unable to use it directly (it implements FactoryBean<Object>).


Affects: 3.2.10, 4.0.6, 4.1 RC2

Issue Links:

  • #16461 HttpInvokerProxyFactoryBean and co do not reliably expose correct type when declared via @Bean
  • #19119 StackOverflowError for advisor search against factory-bean reference to FactoryBean
  • #16922 Better error reporting for circular dependencies between JavaConfig classes

Referenced from: commits 4432c41, f4f7f40, 5da8a16, bff2bf2

Backported to: 4.0.7, 3.2.11

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 2, 2014

Juergen Hoeller commented

This turns out to happen during type matching for the "env" field, considering the local FactoryBean @Bean method as a potential candidate (due to its lack of declaration-level target type information). We've already had exclusion of currently created beans in that algorithm; now we're excluding beans where the target configuration bean is currently in creation as well.

Juergen

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 3, 2014

Juergen Hoeller commented

This is available in the latest 4.1 and 4.0.7 snapshots now, and soon in a 3.2.11 snapshot as well. If you have the chance, please give it a try today; the public releases are scheduled for tomorrow European morning!

Juergen

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 3, 2014

Michał Jaśtak commented

Checked with 4.1.0.BUILD-SNAPSHOT - nothing changed, I'll double check when 4.1 will be released, for now I'm just using workaround:

    @Inject
    private Environment env;

    @Bean
    protected FactoryBean<SomeService> someService() {
        return HttpInvokerProxyFactoryBean.getInstance(SomeService.class, env.getRequiredProperty("service.uri"));
    }

    private static class HttpInvokerProxyFactoryBean<T> extends HttpInvokerClientInterceptor implements FactoryBean<T> {

        private T serviceProxy;

        protected static <T> HttpInvokerProxyFactoryBean getInstance(final Class<T> serviceInterface, final String serviceUrl) {
            final HttpInvokerProxyFactoryBean<T> instance = new HttpInvokerProxyFactoryBean<>();
            instance.setServiceInterface(serviceInterface);
            instance.setServiceUrl(serviceUrl);
            return instance;
        }

        @Override
        public void afterPropertiesSet() {
            super.afterPropertiesSet();
            if (getServiceInterface() == null) {
                throw new IllegalArgumentException("Property 'serviceInterface' is required");
            }
            this.serviceProxy = (T) new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
        }


        @Override
        public T getObject() {
            return serviceProxy;
        }

        @Override
        public Class<? extends T> getObjectType() {
            return (Class<? extends T>) getServiceInterface();
        }

        @Override
        public boolean isSingleton() {
            return true;
        }

    }

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 3, 2014

Juergen Hoeller commented

That's strange... Could you please double-check that you've actually been using the latest snapshot? Sometimes older snapshots are locally cached... In any case, you should not be seeing such an early call to the @Bean method anymore and therefore no NPE. If you're still seeing it, something is wrong and it'd be great if we could sort this out by tomorrow.

Juergen

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 3, 2014

Juergen Hoeller commented

I've just double-checked on my side, with a specific test for HttpInvokerProxyFactoryBean analogous to yours, that the fix does solve the problem to the best of my understanding. Reverting the fix makes it break with an NPE again. So I do suspect a snapshot cache problem on your end - please retry, it'd be great to know the results on your end!

Juergen

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 3, 2014

Michał Jaśtak commented

My build is using 4.1.0.BUILD-20140903.110329-464 version of SF, and indeed I'm still getting NPEx :( - When I changed the code to:

    @Inject
    private Environment env;

    @Bean
    protected FactoryBean someService() {
        final HttpInvokerProxyFactoryBean instance = new HttpInvokerProxyFactoryBean();
        instance.setServiceInterface(SomeService.class);
        instance.setServiceUrl(env.getRequiredProperty("service.uri"));
        return instance;
    }

I had to add @Lazy on constructor of SF component using this service (because SF was complaining that there is no bean of type SomeService suitable for injecting):

@Service
class DefaultUserDetailsService implements UserDetailsService {

    @Inject
    @Lazy
    public DefaultUserDetailsService(@Nonnull final SomeService someService) {
        ...
    }
...
}

Maybe that will give you a clue what's going on ...

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 3, 2014

Juergen Hoeller commented

Hmm, whatever variant I try based on those code snippets, I can't reproduce a remaining NPE against the latest 4.1 snapshot. I suspect that there's some other factor involved :-(

Could you provide a minimal test case that does fail against the latest 4.1 snapshot for you?

Juergen

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 3, 2014

Michał Jaśtak commented

I agree with you, there must be something more in my SF configuration, which impacts this situation, I'll try to prepare the test case for it, but that will be probably over the weekend :( - thus don't wait with releasing 4.1 for me ;) - we will return to it in next days.

M.

@spring-projects-issues spring-projects-issues added type: bug status: backported in: core labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 4.1 GA milestone Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core status: backported type: bug
Projects
None yet
Development

No branches or pull requests

2 participants