Setter Dependency Injection in Spring

In the post dependency injection in Spring we have already gone through the concept of dependency injection, in this post we’ll see in details one of the type of dependency injection- Setter dependency injection in Spring.

For another type of dependency injection, Constructor dependency injection check this post- Constructor Dependency Injection in Spring

Spring Setter dependency injection

In setter based DI Spring container calls setter methods on your beans after invoking a no-argument constructor or no-argument static factory method to instantiate your bean.

For configuring setter based dependencies you can use XML configuration as well as annotations. We’ll see examples of doing it using both of these ways.

Spring Setter dependency injection Example

In the example there is a class to place orders called Order and purchase can be done from an online store or a retail store. In Order class dependencies for the properties are injected using setter dependency injection.

public interface IStore {
  public void doPurchase(int items);
}
public class OnlineStore implements IStore {
  public void doPurchase(int items) {
    System.out.println("Doing online purchase of " + items + " Items");
  }
}
public class RetailStore implements IStore {
  public void doPurchase(int items) {
    System.out.println("Doing purchase of " + items + " Items from a brick and mortar store");
  }
}
public class Order {
  private IStore store;
  private int items;

  public void setStore(IStore store) {
    this.store = store;
  }
  public void setItems(int items) {
    this.items = items;
  }

  public void buyItems() {
    store.doPurchase(items);
  }
}

In the Order class these are two properties one reference to Istore type and another an int. There are setter methods for those properties which will be called by the Spring container to set the configured values.

If you are using XML configuration then beans are defined as given in the following 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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
  <!-- Store bean --> 
  <bean id="store" class="com.knpcode.SpringProject.RetailStore" />           
  <!-- Order bean with dependencies -->
  <bean id="orderBean" class="com.knpcode.SpringProject.Order">
    <property name="store" ref="store" />
    <property name="items" value="20" />
  </bean>
</beans>

For providing setter dependencies <property> tag is used.

  • When dependency is for another bean “ref” attribute is used.
  • For any primitive type or String “value” attribute is used.

You can use the following class with main method to read the configuration and call the bean method.

public class App {
  public static void main( String[] args ){
    // create context using configuration
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("appcontext.xml");
    Order order = (Order) context.getBean("orderBean");
    order.buyItems();
    // close the context
    context.close();
  }
}

If you want to configure setter dependencies in Spring using annotations then you will have to use @Service or @Component annotation with the classes to signify that these are Spring managed component and will be automatically discovered when component scanning is done.

Annotate setter methods with @autowired to inject the setter dependencies automatically.

@Service
public class OnlineStore implements IStore {
  public void doPurchase(int items) {
    System.out.println("Doing online purchase of " + items + " Items");
  }
}
@Service
public class RetailStore implements IStore {
  public void doPurchase(int items) {
    System.out.println("Doing purchase of " + items + " Items from a brick and mortar store");
  }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class Order {
  private IStore store;
  @Value("20")
  private int items;
  @Autowired
  @Qualifier("onlineStore")
  public void setStore(IStore store) {
    this.store = store;
  }
  public void buyItems() {
    store.doPurchase(items);
  }
}

Since there are two objects of type store so @Qualifier annotation has been used to tell which bean has to be wired otherwise you will get an error "No qualifying bean of type 'com.knpcode.SpringProject.IStore' available"

Autowiring works with references only so primitive value is provided using @Value annotation.

If you want to use XML to set up component scanning for automatically discovering beans then it can be done using following 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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
          
  <context:component-scan base-package="com.knpcode.SpringProject" />
</beans>

XML configuration won’t have any dependencies now only <context:component-scan> tag.

Constructor-based or setter-based Dependency injection

As per Spring documentation constructor based DI is preferred over setter-based DI.

"Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency."

That's all for the topic Setter Dependency Injection in Spring. If something is missing or you have something to share about the topic please write a comment.


You may also like

Summarizing Collectors in Java Stream

In this tutorial you’ll learn about the different summarizing collectors in Java Stream API that returns a summary statistics Collectors.

Summarizing collectors in Collectors class

There are three summarizing collector methods in Collectors class which are as following-

  • summarizingInt(ToIntFunction<? super T> mapper)- Returns a Collector which applies an int-producing mapping function to each input element.
  • summarizingLong(ToLongFunction<? super T> mapper)- Returns a Collector which applies an long-producing mapping function to each input element.
  • summarizingDouble(ToDoubleFunction<? super T> mapper)- Returns a Collector which applies an double-producing mapping function to each input element.

Argument to these methods is a corresponding functional interface ToIntFunction, ToLongFunction and ToDoubleFunction that represents a function that produces respectively a int, long or double valued result.

Return type of the method used is also as per type and the Collectors returned are of type IntSummaryStatistics, LongSummaryStatistics and DoubleSummaryStatistics.

Summary statistics object that you can get by using these methods encapsulates attributes like-

  • count
  • min
  • max
  • sum
  • average

These summarizing collectors are great utility methods to compute all the above values with in a single pass.

Collectors.summarizingInt() Java example

For the examples we’ll use the Person class objects.

public class Person {
  private String name;
  private int dependents;
  private long age;
  private double weight;
  Person(String name, int dependents, long age, double weight){
    this.name = name;
    this.dependents = dependents;
    this.age = age;
    this.weight = weight;
  }
  public String getName() {
    return name;
  }
  public int getDependents() {
    return dependents;
  }

  public long getAge() {
    return age;
  }
  public double getWeight() {
    return weight;
  }
}

1. To get the summary statistics for the weight field which is of type int.

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;

public class SummarizingCollectorDemo {
  public static void main(String[] args) {
    List<Person> personList = Arrays.asList(new Person("Peter", 2, 45, 75.6),
              new Person("Ram", 3, 34, 80),
              new Person("Priscilla", 1, 26, 68),
              new Person("Ajay", 4, 35, 71.5),
              new Person("Dan", 0, 58, 77.8));
    IntSummaryStatistics stats = personList.stream()
                    .collect(Collectors.summarizingInt(Person::getDependents));
    System.out.println("Total count of person- " + stats.getCount());
    System.out.println("Max dependents- " + stats.getMax());
    System.out.println("Min dependents- " + stats.getMin());
    System.out.println("Total number of dependents- " + stats.getSum());
    System.out.println("Average number of dependents- " + stats.getAverage());
  }
}
Output
Total count of person- 5
Max dependents- 4
Min dependents- 0
Total number of dependents- 10
Average number of dependents- 2.0

Collectors.summarizingLong() and Collectors.summarizingDouble() Java example

import java.util.Arrays;
import java.util.DoubleSummaryStatistics;
import java.util.List;
import java.util.LongSummaryStatistics;
import java.util.stream.Collectors;

public class SummarizingCollectorDemo {
  public static void main(String[] args) {
    List<Person> personList = Arrays.asList(new Person("Peter", 2, 45, 75.6),
              new Person("Ram", 3, 34, 80),
              new Person("Priscilla", 1, 26, 68),
              new Person("Ajay", 4, 35, 71.5),
              new Person("Dan", 0, 58, 77.8));

    LongSummaryStatistics longSummaryStats = personList.stream()
        .collect(Collectors.summarizingLong(Person::getAge));
    
    DoubleSummaryStatistics doubleSummaryStats = personList.stream()
        .collect(Collectors.summarizingDouble(Person::getWeight));
    System.out.println("Summary statistics for age- " + longSummaryStats);
    System.out.println("Summary statistics for weight- " + doubleSummaryStats);
  }
}
Output
Summary statistics for age- LongSummaryStatistics{count=5, sum=198, min=26, average=39.600000, max=58}
Summary statistics for weight- DoubleSummaryStatistics{count=5, sum=372.900000, min=68.000000, average=74.580000, max=80.000000}

That's all for the topic Summarizing Collectors in Java Stream. If something is missing or you have something to share about the topic please write a comment.


You may also like

Generate Random Numbers Java Stream

If you are writing a Java program to generate random numbers two tried and tested ways are to either use Math.random() method or methods of Random class to get a random number but the limitation with these methods is that they generate a single random number and you would need to use them in a loop in case you need a list of random numbers. With the introduction of Java Stream API in Java 8, Random class has also been expanded to include methods that can return a stream of ints, longs or doubles which means a return type of IntStream, LongStream or DoubleStream.

Random class methods for generating streams

In Random class there are following methods for generating infinite streams-

1. ints()- Returns an effectively unlimited stream of int values.

2. ints(int randomNumberOrigin, int randomNumberBound)- Returns an effectively unlimited stream of int values within the given bounds.

Following methods restrict the generated random numbers.

1. ints(long streamSize)- Returns a stream producing the given streamSize number of int values.

2. ints(long streamSize, int randomNumberOrigin, int randomNumberBound)- Returns a stream producing the given streamSize number of int values within the given bounds.

Same set of methods also exist for producing long and double random numbers-

  • longs()
  • longs(long streamSize)
  • longs(long randomNumberOrigin, long randomNumberBound)
  • longs(long streamSize, long randomNumberOrigin, long randomNumberBound)
and
  • doubles()
  • doubles(double randomNumberOrigin, double randomNumberBound)
  • doubles(long streamSize), doubles(long streamSize, double randomNumberOrigin
  • double randomNumberBound)

Generating stream of random number examples

1. An infinite stream of integers using ints() method of the Random class. Here limit() method of Java Stream is used to limit the stream.

import java.util.Random;

public class RandomNumberGeneration {
  public static void main(String[] args) {
    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
  }
}
Output
164843204
-1469424678
1335628408
29431696
267957743
-944667359
228878324
672262783
1504662080
-262691321

2. In this example we’ll generate 10 random numbers in between the lower and upper bounds of 10 and 50.

public class RandomNumberGeneration {
  public static void main(String[] args) {
    Random random = new Random();
    random.ints(10, 10, 51).forEach(System.out::println);
  }
}
Output
39
29
14
49
26
29
37
50
31
48

Note that with in the passed bounds lower bound is inclusive where as upper bound is exclusive that’s why to make upper bound till 50, 51 is passed as the argument.

3. If you want 10 random doubles with in the range 0 and 1.

public class RandomNumberGeneration {
  public static void main(String[] args) {
    Random random = new Random();
    random.doubles(10, 0, 1).forEach(System.out::println);
  }
}
Output
0.6099718485028252
0.3440097793096719
0.31985736196344106
0.6028702735888255
0.8780031623608885
0.09055972507136933
0.8280686637964826
0.7917602864784455
0.7277181639918716
0.8424139111003316

That's all for the topic Generate Random Numbers Java Stream. If something is missing or you have something to share about the topic please write a comment.


You may also like

Java HashMap merge() With Examples

The Java HashMap merge() method is used to insert a new (key,value) pair into the HashMap or to modify value for an already existing key.

Syntax of merge() method

merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)

The parameters are-

  1. key- Key with which the resulting value has to be mapped.
  2. value- The non-null value which replaces the existing value associated with the key or inserts this new value.
  3. remappingFunction- It is an expression of type BiFunction functional interface used to recompute a value if present

merge method has the following scenarios-

  1. If the specified key is not already associated with a value associates it with the given non-null value and the (key, value) pair is inserted into the HashMap.
  2. If the specified key is associated with null value, associates it with the given non-null value.
  3. If the key already exists then replaces (or merges) the associated value with the results of the given remapping function.
  4. If the result of the given remapping function is null then removes the (key, value) pair from the HashMap.

Merge() Java examples

1. In this example a value is recomputed for the specified key. For the example a HashMap is created with product name as key and price as value. Then compute() is used to change the price (value) for the specified key.

public class MapMergeDemo {

  public static void main(String[] args) {
    Map<String, Double> products = new HashMap<>();
    products.put("Laptop", 1200.0);
    products.put("RAM", 60.50);
    products.put("USB", 10.45);
    products.put("Mouse", 15.0);
    System.out.println("*** Initial Values ***");
    System.out.println(products);
    // recomputing price
    products.merge("Laptop", 1200.0, (oldValue, newValue)-> oldValue- (oldValue*20/100));
    System.out.println("*** After Merge ***");
    System.out.println(products);
  }
}
Output
*** Initial Values ***
{Laptop=1200.0, Mouse=15.0, USB=10.45, RAM=60.5}
*** After Merge ***
{Laptop=960.0, Mouse=15.0, USB=10.45, RAM=60.5}

2. Inserting a new entry into the HashMap using merge method.

public class MapMergeDemo {
  public static void main(String[] args) {
    Map<String, Double> products = new HashMap<>();
    products.put("Laptop", 1200.0);
    products.put("RAM", 60.50);
    products.put("USB", 10.45);
    products.put("Mouse", 15.0);
    System.out.println("*** Initial Values ***");
    System.out.println(products);
    // Adding new key
    products.merge("Adapter", 5.0, (oldValue, newValue)-> oldValue + newValue);
    System.out.println("*** After Merge ***");
    System.out.println(products);
  }
}
Output
*** Initial Values ***
{Laptop=1200.0, Mouse=15.0, USB=10.45, RAM=60.5}
*** After Merge ***
{Laptop=1200.0, Mouse=15.0, USB=10.45, Adapter=5.0, RAM=60.5}

3. If key exists but is associated with null value then using merge method value can be changed to a new value. In the example HashMap has one key with associated value as null.

public class MapMergeDemo {
  public static void main(String[] args) {
    Map<String, Double> products = new HashMap<>();
    products.put("Laptop", 1200.0);
    products.put("RAM", 60.50);
    products.put("USB", 10.45);
    products.put("Mouse", null);
    System.out.println("*** Initial Values ***");
    System.out.println(products);
    // remappingFunction returns null
    products.merge("Mouse", 12.50, (oldValue, newValue)-> oldValue+newValue);
    System.out.println("*** After Merge ***");
    System.out.println(products);
  }
}
Output
*** Initial Values ***
{Laptop=1200.0, Mouse=null, USB=10.45, RAM=60.5}
*** After Merge ***
{Laptop=1200.0, Mouse=12.5, USB=10.45, RAM=60.5}

4. In this example we’ll see the scenario where remappingFunction of the merge() method returns null. In that case the (key, value) pair should be removed. To verify that the function explicitly returns null in the code.

public class MapMergeDemo {

  public static void main(String[] args) {
    Map<String, Double> products = new HashMap<>();
    products.put("Laptop", 1200.0);
    products.put("RAM", 60.50);
    products.put("USB", 10.45);
    products.put("Mouse", 8.0);
    System.out.println("*** Initial Values ***");
    System.out.println(products);
    // remapping function returns null
    products.merge("Laptop", 1200.0, (oldValue, newValue)-> null);
    System.out.println("*** After Merge ***");
    System.out.println(products);
  }
}
Output
*** Initial Values ***
{Laptop=1200.0, Mouse=8.0, USB=10.45, RAM=60.5}
*** After Merge ***
{Mouse=8.0, USB=10.45, RAM=60.5}

5. In this example we’ll see the merging of old value and new value. Since the remapping function gets both old and new value as arguments so we can have logic to compute value using both old and new value. In the product HashMap if price of Laptop has to be increased by 20.

public class MapMergeDemo {

  public static void main(String[] args) {
    Map<String, Double> products = new HashMap<>();
    products.put("Laptop", 1200.0);
    products.put("RAM", 60.50);
    products.put("USB", 10.45);
    products.put("Mouse", null);
    System.out.println("*** Initial Values ***");
    System.out.println(products);
    products.merge("Laptop", 20.0, (oldValue, newValue)-> oldValue+newValue);
    System.out.println("*** After Merge ***");
    System.out.println(products);
  }
}
Output
*** Initial Values ***
{Laptop=1200.0, Mouse=null, USB=10.45, RAM=60.5}
*** After Merge ***
{Laptop=1220.0, Mouse=null, USB=10.45, RAM=60.5}

6. Using merge() method to recompute all values in the HashMap. In the products HashMap if you want to increase price by 10% for all the products.

public class MapMergeDemo {

  public static void main(String[] args) {
    Map<String, Double> products = new HashMap<>();
    products.put("Laptop", 1200.0);
    products.put("RAM", 60.50);
    products.put("USB", 10.45);
    products.put("Mouse", 8.0);
    System.out.println("*** Initial Values ***");
    System.out.println(products);
    products.forEach((k,v) -> products.merge(k, v, (oldValue, newValue)-> oldValue+ (oldValue * 10/100)));
    System.out.println("*** After Merge ***");
    System.out.println(products);
  }
}
Output
*** Initial Values ***
{Laptop=1200.0, Mouse=8.0, USB=10.45, RAM=60.5}
*** After Merge ***
{Laptop=1320.0, Mouse=8.8, USB=11.495, RAM=66.55}

Here forEach is used to iterate the Map and then merge() method is used to increase value by 10%.

That's all for the topic Java HashMap merge() With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like