How to dynamically order the implementation list of a interface with default value in Spring Boot?

346 Views Asked by At

I have an interface which extends java.util.function.Function, there are 2 further implementation of the interface lets say class A, B which implements the interface Base;

interface Base extends Function<Object, Object> {

  @Override
  default Object apply(Object object) {
    return object;     // simply do nothing
  }

  default boolean accept(String str) {
    return true;     // for choosing the right instance
  }
}
@Service
class A implements Base {

  String validationString = "some-random-string";

  @Override
  public Object apply(Object object) {
    // update the object ....
    return object;
  }

  @Override
  public boolean accept(String str) {
    return str.equals(validationString);
  }
}
@Service
class B implements Base {

  String validationString = "some-random-string-2";

  @Override
  public Object apply(Object object) {
    // update the object ....
    return object;
  }

  @Override
  public boolean accept(String str) {
    return str.equals(validationString);
  }
}
@Service
@RequiredArgsConstructor
class SomeService {

  List<Base> baseList;

  public void callingClass(String str) {

    // in below 
    // I want to order it in such a way that 
    // if str matches A.validationString then give back A's instance
    // else if str matches B.validationString then give back B's instance
    // else return Base's instance
    Base base = baseList.stream()
        .filter(baseImpl -> baseImpl.accept(str))
        .findFirst().orElseThrow();

  }
}

So, what I am trying to achieve, if there is any way from which I don't have to manually create a Factory Class (Factory Pattern) and dynamically Filter the instance A, B using accept() and if none find then return instance of Base (as default).

Can anybody help with this?

3

There are 3 best solutions below

2
Loading On BEST ANSWER

Combining @k314159 and @atish.s solution and improving on that:

Expose the DefaultBase class as another bean, but add @Order annotation to put it last in list

@Component
@Order(Precedence.LAST)
class DefaultBase implements Base {
    default accepts(Object obj){
        return true;
    }
}

Then changing the Service method to this:

public Base callingClass(String str) { 
    return baseList.stream()
        .filter(baseImpl -> baseImpl.accept(str))
        .findFirst()
        .orElseThrow();
}
0
atish.s On

You can do so by providing a default implementation for Base as such;

// no need to annotate it with @service
class DefaultBase implements Base {

}

Then in your factory method,

private static final Base DEFAULT_BASE = new DefaultBase();

public void callingClass(String str) {
  Base base = baseList.stream()
      .filter(baseImpl -> baseImpl.accept(str))
      .findFirst()
      .orElseGet(DEFAULT_BASE);
}
3
k314159 On

Get the list of service classes implementing your interface by autowiring.

Then, create a single default instance (it can be an anonymous class, or a named class as in atish's answer). It must not be a Spring service as you don't want it to be included in your autowired list. In your method, if no service class is found, return this default instance.

@Service
@RequiredArgsConstructor
class SomeService {
  private static final Base DEFAULT_BASE = new Base() {};

  @Autowired
  private List<Base> baseList;

  public void callingClass(String str) {

    Base base = baseList.stream()
        .filter(baseImpl -> baseImpl.accept(str))
        .findFirst()
        .orElse(DEFAULT_BASE);

  }
}