1

In Spring XML, I can define a bean that instantiates a class annotated with @Configuration. When I do, that bean is post-processed. Any methods inside that class with @Bean are also added to the container. How do I perform a similar post-processing in JavaConfig?

Here's the XML version:

<bean id="test" class="com.so.Test">
    <property name="prop" value="set before instantiating @Beans defined in Test"/>
</bean>

The associated Test class:

@Configuration
class Test {
    private String prop;

    void setProp(final String prop) {
        this.prop = prop;
    }

    @Bean
    NeedThisBean needThisBeanToo() {
        return new NeedThisBean(prop);
    }
}

If I use Spring XML Config, both test and needThisBeanToo are available in the container. needThisBeanToo is added via a BeanPostProcessor, though I can't recall which one. If I use JavaConfig, only test is available in the container. How do I make needThisBeanToo available to the container? @Import would work, except that prop being set is required for needThisBeanToo to be initialized correctly.

The part that makes all of this complicated is that Test is vended from a library I'm consuming. I don't control Test, nor can I change it. If I drive it from JavaConfig, it would look like this:

@Configuration
class MyConfiguration
{
    @Bean
    Test test() {
        Test test = new Test();
        test.setProp("needed to init `needThisBeanToo` and others");
        return test;
    }
}

The JavaConfig example does not instantiate needThisBeanToo despite it being defined in Test. I need to get needThisBeanToo defined, preferably without doing it myself, since I don't want to copy code I don't own. Delegation isn't attractive, since there are a number of subsequent annotations/scopes defined on needThisBeanToo (and others defined inside Test).

Steven Hood
  • 678
  • 5
  • 18
  • I would very much like to help you but I don't really understand your problem from the way you explained. What is it that you're missing exactly? The property? Anyway - I can point you to a question and answer of mine that explains in details everything you need to know for converting XML to @Configuration class: http://stackoverflow.com/q/24014919/2083523 If this still doesn't help, please try to explain what exactly you're missing and I'll do my best to help. – Avi Jul 30 '15 at 18:25
  • Edited, let me know if it clarifies. – Steven Hood Jul 30 '15 at 18:29
  • DId my answer help? Or was it something else you're looking for? – Avi Jul 30 '15 at 18:31
  • Oh, I think I got you now. Do you mean that there is no bean of type `NeedThisBean` loaded to the spring container? – Avi Jul 30 '15 at 18:32
  • Correct. I added the JavaConfig-driven approach as well to help explain. JavaConfig doesn't "recursively" look for @Beans, and I need to do that. – Steven Hood Jul 30 '15 at 18:35
  • Are you importing the @Configuration class? Do you have a root spring configuration XML or don't you have XMLs at all? It's important to understand where the root spring context is loaded from. – Avi Jul 30 '15 at 18:36
  • Ok, I understand your problem - answer is on the way. – Avi Jul 30 '15 at 18:39
  • Have a look at my answer now. I think that's what you need. Hope I explained the problem clearly enough. – Avi Jul 30 '15 at 18:48

2 Answers2

0

Your problem is is that you're ignoring the @Configuration annotation completely. Why is that?

When code reaches this line Test test = new Test(); it just doesn't do anything with @Configuration. Why? Because annotation is not something that a constructor is aware of. Annotation only marks some meta-data for the class. When spring loads classes it searches for annotations, when you call a constructor of a class, you don't. So the @Configuration is just ignored because you instantiate Test with new Test() and not through spring.

What you need to do is to import Test as a spring bean. Either via XML as you showed in your question OR using @Import. You problem with prop is that the setter isn't called because that's just not the way to do it. What you need to be doing is either do something like that:

@Configuration
class Test {
  private String prop = "set before instantiating @Beans defined in Test";

  @Bean
  NeedThisBean needThisBeanToo() {
      return new NeedThisBean(prop);
  }
}

Or to create a property in spring (this is a different subject) and inject the value:

@Configuration
class Test {
  @Autowired
  @Value("${some.property.to.inject}") // You can also use SPeL syntax with #{... expression ...}
  private String prop;

  @Bean
  NeedThisBean needThisBeanToo() {
      return new NeedThisBean(prop);
  }
}

You can also create a bean of type String and inject it as follows:

@Configuration
class Test {
  @Autowired
  @Qualifer("nameOfBeanToInject")
  private String prop;

  @Bean
  NeedThisBean needThisBeanToo() {
      return new NeedThisBean(prop);
  }
}

In the last case you can define your original MyConfiguration with this bean:

@Configuration
@Import(Test.class)
class MyConfiguration
{
    @Bean(name = "nameOfBeanToInject")
    String test() {
        return "needed to init `needThisBeanToo` and others";
    }
}

In any case you have to import Test either using @Import or as a normal XML bean. It won't work by calling the constructor explicitly.

Avi
  • 21,182
  • 26
  • 82
  • 121
  • I don't think I'm ignoring the annotation. When I use XML, I don't do anything with the annotation. Spring is handling the annotation when it's defined in XML, but that's separate from calling the constructor. What I want is for Spring to handle the annotation once the @Bean method (which includes the `new Test()` call) returns. – Steven Hood Jul 30 '15 at 18:51
  • 1
    It just doesn't work that way. When you call `new Test()` the annotation is ignored. When you do it via a spring XML, spring handles the instantiation of the object and thus before creating the new bean, it searches for annotations of interest to it. – Avi Jul 30 '15 at 18:55
  • Want an easy way to understand better what I'm saying - run your code in debug and check the spring container for the `test` bean when you define it with XML and when you define it the way you showed in your example. You'll see that in one case it's just a plain `Test` object and in the second it will be a spring object (not of type `Test`. I don't remember the exact type but it's easy to debug). You would see that the latter contains `Test` as an inner property but it's not of type `Test` – Avi Jul 30 '15 at 18:56
  • Anyway, try the last example I gave, I think it's the most straight-forward one for doing what you're trying to accomplish without defining a property interpretation infra. – Avi Jul 30 '15 at 18:59
  • I'm definitely familiar with what's created. I've been through the debugger step-by-step. XML does some post-processing, JavaConfig doesn't. What I'm trying to figure out is how to have JavaConfig do the same post-processing as the XML approach does. If I could setter-inject the `Test` created by @Import (in your last example) prior to Spring reading all the @Beans it contains, I think that would work. Is that possible? – Steven Hood Jul 30 '15 at 19:02
  • I have to admit I haven't tried but it might cause some problematic loop. I'm not sure though. Anyway, that's not the way to do it. You should define `@Configuration` bean as beans that are not used by the application. If you find yourself in need to inject a `@Configuration` class, it means your doing something the wrong way. If `Test` is an application bean (a bean that is used by your business logic) don't make it also a `@Configuration` bean. – Avi Jul 30 '15 at 19:05
  • The @Configuration bean "Test" is **not** used by my application. It is vended by a library I need, but cannot control. That's **the only way** (without significant copy/paste, essentially forcing me to maintain a fork) I can configure the library. `@Import` on Test **does not** let me configure it. From your examples: 1) doesn't work, I can't change Test, 2) same reason, 3) same reason, and 4) won't work because I can't get the property into Test. I'm not claiming this library vended its configuration correctly, but it's what I have to work with. – Steven Hood Aug 04 '15 at 14:01
0

Here's a way to handle vended @Configuration classes that require some properties to be set prior to creating their @Beans:

Vended @Configuration class:

@Configuration
class Test {

    private String property;

    public setProperty(final String property) {
        this.property = property;
    }

    @Bean
    PropertyUser propertyUser() {
        return new PropertyUser(property);
    }

    @Bean
    SomeBean someBean() {
        // other instantiation logic
        return new SomeBeanImpl();
    }
}

Here's the consuming @Configuration class:

@Configuration
class MyConfig {

    @Bean
    static String myProperty() {
        // Create myProperty
    }

    /**
     * Extending Test allows Spring JavaConfig to create
     * the beans provided by Test.  Declaring
     * Test as a @Bean does not provide the @Beans defined
     * within it.
     */
    @Configuration
    static class ModifiedTest extends Test {

        ModifiedTest() {
            this.setProperty(myProperty());
        }

        @Override
        @Bean
        SomeBean someBean() {
            return new SomeBeanCustomImpl(this.propertyUser());
    }
}
Steven Hood
  • 678
  • 5
  • 18
  • 1
    It feels like you're trying to force it. That's not how it meant to be used and you're creating spaghetti configuration. That's fine of course if your goal is just to use the setter, but there are ways to do what you wanted in a much simpler way. If you would like to understand how to do it, I'd love to explain my answer a bit further. I just love clean code and hate to see you doing something that is so complexed to achieve something so simple. – Avi Aug 02 '15 at 16:21
  • @Avi, you haven't given me a way that actually works, yet. See my comment on your answer. To be clear, I don't like my solution. I agree that clean code is better, and I wish there was a simpler way. It isn't my goal to use the setter, it's just **the only way I have to configure** the `VendedConfiguration` class. I don't own `VendedConfiguration`; I can't change `VendedConfiguration`. It was designed to be used via Spring XML, and I'd prefer not to use Spring XML. Unfortunately, `VendedConfiguration` does quite a bit of self-referencing of its `@Beans`, and `property` controls it all. – Steven Hood Aug 04 '15 at 14:06
  • Given all that, I think you're right, I am forcing it. I disagree with the 'spaghetti configuration' because it's all encapsulated in a single @Configuration class I own, and therefore isn't spread across lots of configuration. – Steven Hood Aug 04 '15 at 14:08
  • I think that either I haven't understand your question right, which is then why my answer doesn't help or the other way around. Because the way I understood your question, you should be able to use my solution. You haven't mentioned `VendedConfiguration` in the original question so that may be the reason why. Also, you haven't mentioned that you can't change it which is a very critical factor for your question and of course I would have either offer a different solution or not answering at all if I knew this. – Avi Aug 04 '15 at 14:21
  • I apologize for the name change. Consider `Test` == `VendedConfiguration` (I've fixed that). From the initial question: "I don't control Test, nor can I change it." – Steven Hood Aug 04 '15 at 14:23
  • Ok, have to admit I missed this line. I'll try to think if I have a still better idea to solve this – Avi Aug 04 '15 at 14:26
  • Now I understand why you have need for this complex solution and still you can do something else that would be clearer. I'll edit my answer to reflect it. – Avi Aug 04 '15 at 14:29
  • Actually I would it mostly the same but you don't need the inner class, you can still extract `ModifiedTest` to an independent class and use `@Inject` or `@Autowired` to inject the parameters bean to the constructor. – Avi Aug 04 '15 at 14:34