15

I am working on a Spring 3.1 MVC application and for one of my scenarios, I had to write two implementations of a DAO. I would like to know how to autowire this in a service layer based on another object's attribute.

For example,

    class Vehicle {
        private name;
        private type;
        ..
        ..
        ..
    } 

    @Service
    class VehicleServiceImpl implements VehicleService {

            // There are two implementations to this DAO
            // if Vehicle.type == "CAR", inject CarDAO
            // if Vehicle.type == "TRAIN", inject TrainDAO 
            @Autowired
            private VehicleDAO vehicleDAO ;

    }

    @Repository
    class CarDAO implements VehicleDAO {

    }

    @Repository
    class TrainDAO implements VehicleDAO {

    }

If my Vehicle is a Car, I need to autowire CarDAO and if it's a train, I need to autowire TrainDAO

What is the best way to implement this in spring 3.1.

I was hoping to use either context property place holders or @Qualifier annotation but both these are kind of restricted to lookup based on some property. I am not sure how to do this at runtime based on the property of another object.

aprajitha
  • 345
  • 1
  • 7
  • 18

4 Answers4

22

My solution would be as follows:

a method isResponsibleFor in the VehicleDao interface:

interface VehicleDao {
    public boolean isResponsibleFor(Vehicle vehicle);
}

example implementation:

@Repository
class CarDAO implements VehicleDAO {
    public boolean isResponsibleFor(Vehicle vehicle) {
        return "CAR".equals(vehicle.getType());
    }
}

then autowire a list of all VehicleDao-implementations in the VehicleService:

public class VehicleServiceImpl implements VehicleService {

    @Autowired
    private List<VehicleDao> vehicleDaos;

    private VehicleDao daoForVehicle(Vehicle vehicle) {
         foreach(VehicleDao vehicleDao : vehicleDaos) {
              if(vehicleDao.isResponsibleFor(vehicle) {
                   return vehicleDao;
              }
         }

         throw new UnsupportedOperationException("unsupported vehicleType");
    }

    @Transactional
    public void save(Vehicle vehicle) {
         daoForVehicle(vehicle).save(vehicle);
    }
}

This has the advantage that you don't need to modify the service when you are adding a new vehicleType at a later time - you just need to add a new VehicleDao-implementation.

Felix Ebert
  • 1,103
  • 11
  • 30
  • 1
    Clean solution from my point of view. This is the approach we took also. Just in the 'isResponsible' method do not use string but class comparison. You can even have a fallback implementation if you use one autowired field with a concrete implementation instead of throwing an exception. – Martin Frey Mar 08 '13 at 06:03
  • Thanks @Felix. I was not aware we could wire a list of beans of a particular type – aprajitha Mar 08 '13 at 06:23
18

There is an cleaner option to accomplish this strategy. We know Spring is smart enough to inject where it sees a bean that it has control over, so when it sees your VehicleDAO with an @Autowire or @Resource annotation, it will correctly inject that concrete implementation.

Keeping that in mind, this will work anywhere you ask Spring to perform dependency injection, using a Map<> or a List<> for instance.

// Injects the map will all VehicleDAO implementations where key = the
// concrete implementation (The default name is the class name, otherwise you 
// can specify the name explicitly).
@Autowired
private Map<String, VehicleDAO> vehicleDAO;

// When you want to use a specific implementaion of VehicleDAO, just call the  
// 'get()' method on the map as in:
...
vehicleDAO.get("carDAO").yourMethod();
...
potame
  • 7,597
  • 4
  • 26
  • 33
Prancer
  • 3,336
  • 2
  • 32
  • 38
0

Since Vehicle is a @Model it is a runtime value, you won't be able to use it for autowiring values.

The vehicle has to be passed to the service methods as an argument.

public interface VehicleService { void doSomething(Vehicle vehicle); }

Assuming both CarDao and TrainDAO are implementing the same VehicleDao Service, other wise it won't make much sense.

So in your VehicleServiceImpl I would recommend you to write a method like getVehicleDao(Vehicle v){} to get the correct instance of the dao.

public class VehicleServiceImpl implements VehicleService {

    @Resource(name="carDAO")
    private VehicleDao carDAO ;


    @Resource(name="trainDAO")
    private VehicleDao trainDAO ;

    private VehicleDao getDao(Vehicle v){
        if(v instanceof Train){
            return trainDao;
        } else if(v instanceof Car) {
            return carDao;
        } else {
            throw new RuntimeException("unsupported vehicle type");
        }
    }

    void doSomething(Vehicle vehicle){
        VehicleDao dao = getDao(vehicle);
        dao.doSomethind(vehicle);
    }
}
Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
0

Please one note: previous solution is great and simple, but spring is creating each instance of each bean.

So my solution is to create abstract class VehicleService instead of interface

then in appContext

@Bean
@Primary
VehicleService paymentProviderService()
{
    return ...new (your requested instance by your own)...
}
PavelP
  • 119
  • 4