user9669681
user9669681

Reputation:

Best way to dynamically resolve dependencies in Java Spring?

Let's say I have this code structure:

public class NotificationService {
     public void send(Notification notification) {
         // call other services and send the notification
     }
}

public class OrderNotification implements Notification {

    @Autowired
    public TranslationService translationService;

    private String orderNumber;

    public OrderNotification(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    public String getMessage() {
        return translationService.trans('notification.order', new Object[]{orderNumber});
    }
} 

So, my goal is to use the NotificationService in this way:

notificationService.send(new OrderNotification(orderNumber));

But I know that code above won't work, because of the translationService won't be resolved.

My goal is to pass custom parameters to my Notification classes and being able to use services inside that class. What is the best way to do it in the Spring?

Upvotes: 0

Views: 614

Answers (2)

kkflf
kkflf

Reputation: 2570

I know that below is not the correct answer to your question. It is however a bad design pattern to combine Entities and Services. An Entity should only contain information about the object and not business logic. A Service contains all the business logic.

You need to separate your Service from your Entity.

OrderNotification looks like a regular entity. The entity should not contain business logic. You need a specific service for the business logic.

public class OrderNotification implements Notification {

    private String orderNumber;

    public OrderNotification(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    public String getMessage() {
        return "Order number: " + orderNumber;
    }

    //Getter & Setters
    ...
} 

@Service
public class NotificationService {

    @Autowired
    public TranslationService translationService;

    public void send(Notification notification) {
        //I do not know what trans accepts, so I assume it can accept Notification
        translationService.trans(notification.getMessage());
    }
}

If you really need to combine the entity and service - Then I recommend this approach:

@Service
public class Master{

    @Autowired
    NotificationService notificationService

    public void testMethod(){
        Notification notification = notificationService.createOrder("order1");
        notificationService.send(notification);
    }

}

@Service
public class NotificationService {

    @Autowired
    public TranslationService translationService;

    public Notification createOrder(String orderNumber){
        return new OrderNotification(orderNumber, translationService);
    }

    public void send(Notification notification) {
        // call other services and send the notification
        notification.getMessage();
    }
}


public class OrderNotification implements Notification {

    private TranslationService translationService;

    private String orderNumber;

    //I have changed this constructor to accept TranslationService.
    public OrderNotification(String orderNumber, TranslationService translationService) {
        this.orderNumber = orderNumber;
        this.translationService = translationService;
    }

    public String getMessage() {
        return translationService.trans('notification.order', new Object[]{orderNumber});
    }
} 

Upvotes: 2

Karol Dowbecki
Karol Dowbecki

Reputation: 44952

You have few options available:

  1. Configure AOP and load time weaving to process Spring annotations on objects created with new keyword. This is explained in the docs 5.8.1. Using AspectJ to dependency inject domain objects with Spring.

  2. Declare OrderNotification as a prototype scoped bean and obtain each instance from the context using BeanFactory.getBean(Class<T> requiredType, Object... args) method.

    String orderNumber = "123";
    OrderNotificaton = factory.getBean(OrderNotificaton.class, orderNumber);
    
  3. Drop the @Autowired and use plain constructor injection.

    public OrderNotification(TranslationService translationService, String orderNumber) {
        this.translationService = Objects.requireNonNull(translationService);
        this.orderNumber = Objects.requireNonNull(orderNumber);
    }
    

If you only require simple @Autowired I'd go with option 3. It's the simplest approach and makes writing unit tests easier as you don't have to depend on Spring.

Upvotes: 0

Related Questions