简体   繁体   中英

Get a specific bean in Java Spring boot based on RequestBody parameter

In my spring boot application, I want to get an object/bean of a specific class based on the @RequestBody value that is received. Is there a way to do the same

Interface

public interface Vehicle{
  public String drive();
}

Implementation of The interface

@Component
public class Car implements Vehicle {
  public String drive(){
    return "Driving a car";
  }
}

@Component
public class Bike implements Vehicle {
    return "Riding a Bike";
}

Now in my controller based on the request body I want to get the bean of Either CAR or Bike .

Is there a way to do the same

Controller

@RestController
@RestMapping('type-of-vehicle')
public class VehicleSelectController{
   @PostMapping
   public String identify_Vehicle_Selected(@RequestBody vehicletype vehicletype_data){

     /* ## Code to get the bean of vehicle type sent in the RequestBody */
    // example vehicletype_data selected vehicle is car
    // we get the bean of the 'CAR' class
    // Returns the correct implementation of the type of vehicle selected by user
     return vehicle.drive();

   }
}

Are there any annotations that can be used to achieve the same more of I am trying to avoid making a config class that returns the correct object based on the vehicle type received

I was thinking something along this line

Wrong-way of Implementation

@RestController
@RestMapping('type-of-vehicle')
public class VehicleSelectController{
   @PostMapping
   public String identify_Vehicle_Selected(@RequestBody vehicletype vehicletype_data){

     @Autowired
     @Qualifiyer('${vehicletype_data}')
     Vehicle vehicle_object
     return vehicle_object.drive();

   }
}

Is there a method to do something similar to the incorrect implementation

You can use Factory Pattern. Create a bean factory for the getting the Vehicle bean.

@Component
public class VehicleFactory{

    @Autowired
    @Qualifier("bike") // bean name is same as class name with the first letter being lowercase
    private Vehicle bike;

    @Autowired
    @Qualifier("car")
    private Vehicle car;

    public Vehicle getVehicle(VehicleType vehicleType){
        if(vehicleType == VehicleType.CAR){ // Assuming VehicleType is an enum
            return car;    
        } else if (vehicleType == VehicleType.BIKE){
            return bike;
        } else {
            throw new IllegalArgumentException("No bean available for the type " + vehicleType);
        }
    }
}

Now in your controller,

@RestController
@RestMapping('type-of-vehicle')
public class VehicleSelectController{
    @Autowired
    VehicleFactory vehicleFactory;

    @PostMapping
    public String identify_Vehicle_Selected(@RequestBody vehicletype vehicletype_data){
        return vehicleFactory.getVehicle(vehicletype_data).drive(); // handle the exception as needed
    }
}

I found similar question: Get bean from ApplicationContext by qualifier

If it does not solve your problem, maybe try something like:

  1. Add method to Vehicle interface that check whether it is target vehicle by given data from request:
boolean isTargetVehicle(final string valueFromRequest);

Example for car vehicle implementation:

public boolean isTargetVehicle(final string valueFromRequest) {
    return "car".equals(valueFromRequest);
}
  1. Then inject collection with vehicles in your controller/service:
@Autowired
Collection<Vehicle> vehicles;
  1. Add implementation in controller that invokes drive method only on target vehicles. It returns value from drive method, otherwise throws exception if vehicle support is not found:
vehicles.stream().filter(v -> v.isTargetVehivle(dataFromRequest)).map(Vehicle::drive).findFirst().orElseThrow(() -> new Exception("vehicle not supported"));

In this approach you can easily add new vehicle without modifying existing code. And in addition it is better to use constructor injection.

You can do it like this:

@Component
@Qualifier("Car")
public class Car implements Vehicle {
    @Override
    public String drive() {
        return "Driving a car";
    }
}

@Component
@Qualifier("Bike")
public class Bike implements Vehicle {
    @Override
    public String drive() {
        return "Riding a Bike";
    }
}
public class VehicleSelectController {

    private Map<String, Vehicle> vehicles = new HashMap<>();

    @Autowired
    private void setVehicles(List<Vehicle> vehicles) {
        for (Vehicle vehicle : vehicles) {
            Qualifier qualifier = vehicle.getClass().getAnnotation(Qualifier.class);
            if (qualifier != null)
                this.vehicles.put(qualifier, vehicle);
        }
    }

    @PostMapping
    public String identify_Vehicle_Selected(@RequestBody vehicletype vehicletype_data) {
        Vehicle vehicle = this.vehicles.get(vehicletype_data.getType());
        if (vehicle == null)
            throw new IllegalArgumentException("Unknown type: " + vehicletype_data.getType());
        return vehicle.drive();
    }
}

There are ways to do it with Spring boot tools. However, when I dealt with the same issue I didn't like some of the details of the solution provided by Spring out of the box. So, I wrote my own future called Self-populating Factory that does exactly what you ask. This feature is published as part of my MgntUtils Open-source Library. Here is the link to Javadoc page that explains how it works and how to use it: http://michaelgantman.github.io/Mgnt/docs/com/mgnt/lifecycle/management/package-summary.html . Also here is an article that explains the idea and the implementation and also the reason why I thought it would be a good idea to write such a feature: Non-intrusive access to "Orphaned" Beans in Spring framework . The library itself could be obtained as Maven artifact here and downloaded with source code and Javadoc included from Github here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM