Yana
Yana

Reputation: 313

Autowire on new-ing up an object

I've a scenario where a bean I'm using has some fields populated from properties file while others need to be populated dynamically (from a api call). Here is my bean:

@Configuration
@ConfigurationProperties(prefix="studio")
public class Studio {

   private String areaCode; // loads from application.properties

   private String hours; // loads from application.properties

   private String groupCode; // loads from application.properties

   private Address address; // loads from a api

   private String id; // loads from a api

   public Studio(String id, String city, String subdivision,
            String addressLine1, String postalCode) {

         Address address = Address.builder()
               .street(addressLine1)
               .city(city)
               .postalCode(postalCode)
               .state(subdivision)
               .build();
     this.id = id;
         this.address = address;
   }

 }

Now the method that populates the dynamic fields is like this:

 private List<Studio> getStudioDataFromApi(ResponseEntity<String> exchange)
       throws Exception {

    List<Studio> infoList = $(exchange.getBody())
          .xpath("Area[TypeCode=\"CA\"]")
          .map(
          Area -> new Studio(
                $(Area).child("Id").text(String.class), 
                $(Area).child("Address").child("City").text(String.class), 
                $(Area).child("Address").child("Subdivision").text(String.class), 
                $(Area).child("Address").child("AddressLine1").text(String.class), 
                $(Area).child("Address").child("PostalCode").text(String.class))
          );
    return infoList;
}

I Autowire Studio in that class. Now whenever I run this, I get the fields that are populated from the properties file as null. I can see the reason, which is, new doesn't know anything about the autowired bean. My question is how can I use both? i.e. use a bean that has always some fields populated from a config when its new-ed up. Context xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

      <bean class="org.springframework.batch.core.scope.StepScope" />
      <bean id="ItemReader" class="com.sdm.studio.reader.StudioReader" scope="step">
           <property name="studio" ref="Studio" />
      </bean>     
      <bean id="Studio" class="com.sdm.studio.domain.Studio" />     
</bean>

Upvotes: 2

Views: 128

Answers (1)

Gary
Gary

Reputation: 6667

[edit: full code example shown here is also on github ]

Try this:

//This class contains read-only properties, loaded from Spring Externalized Configuration
@Component
@ConfigurationProperties(prefix="studio") 
public class Studio {

    private String areacode; // loads from application.properties

    //... also add other read-only properties and getters/setters...

    public String getAreacode() {
        return areacode;
    }

    public Studio setAreacode(String areacode) {
        this.areacode = areacode;
        return this;
    }

}

//Just a POJO
class FullStudio {
    private String id;
    private Address address;

    FullStudio(String id, String city, String areaCode) {
        this.id = id;
        this.address = new Address(id, city, areaCode);
    }

    @Override
    public String toString() {
        return "FullStudio{" +
                "id='" + id + '\'' +
                ", address=" + address +
                '}';
    }
}
class Address{
    String id;
    String city;
    String areaCode;

    public Address(String id, String city, String areaCode) {
        this.id = id;
        this.city = city;
        this.areaCode = areaCode;
    }

    @Override
    public String toString() {
        return "Address{" +
                "id='" + id + '\'' +
                ", city='" + city + '\'' +
                ", areaCode='" + areaCode + '\'' +
                '}';
    }
}

What we are doing here is allowing Spring to control the lifecycle of the Studio class. You don't need to create a new Studio yourself. Spring does that when it starts up. Since it is also a @ConfigurationProperties class it will also populate values from Spring Externalized Configuration Note: you also need public getters and setters so that Spring can populate the values for you.

FullStudio is not a Spring managed class. You create your own FullStudio with values from Studio and any other api.

And here is a class that is not configured with Java Config @Configuration but instead is managed by an xml configuration:

public class StudioReader {

    private Studio wiredstudio;

    public String getMessage(){
        return wiredstudio.getAreacode();
    }

    public StudioReader setWiredstudio(Studio studio) {
        this.wiredstudio = studio;
        return this;
    }
}

And we use this mycontext.xml file to create this bean with the reference to wiredstudio. The Studio that Spring wires in here comes from our Studio instance configured with JavaConfig. The ref attribute of studio is the name that Spring automatically chose for us based on the name of the Studio class when it instantiated it into our spring application context:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <bean id="studioReaderReader" class="com.example.StudioReader" >
        <property name="wiredstudio" ref="studio" />
    </bean>
</beans>

Personally, I think it is more trouble than its worth for new projects to combine xml and Java Configuration for Spring beans, but it can be done.

Here is a test class that shows how our Studio class can be used from classes created with Spring Java Config and XML config:

@RunWith(SpringRunner.class)
@SpringBootTest
public class StartAppTest {

    @Autowired private Studio studio;
    @Autowired private StudioReader studioReader;

    @Test
    public void contextok() throws Exception {
    }

    @Test
    public void fullStudio() throws Exception {
        FullStudio fs = new FullStudio("1", "Denver", studio.getAreacode());
        System.out.println("stdio is: " + fs);
        assertEquals("303", studio.getAreacode());
    }

    @Test
    public void loadstudioreader() throws Exception {
        assertEquals("303",studioReader.getMessage());
    }
}

In order to run this test, you will need 2 more files:

@SpringBootApplication
@ImportResource("classpath:mycontext.xml")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

and our application.properties file:

studio.areacode=303

Upvotes: 1

Related Questions