Dyamically choose service implementation in Spring

2019-04-09 06:53发布

I am using spring 3.2 and would like to dynamically choose a service implementation in my controller depending on a condition. Consider I have an interface and two implementations as follows :

public interface DevService {
   public void add(Device device);
}

public class DevServiceImpl implements DevService {
    public void add(Device device) {
    }
}

public class RemoteDevServiceImpl implements DevService {
    public void add(Device device) {
    }
}

So in my controller, depending on whether the action is to be executed on the local site or remote site, I need to either execute it locally or send a command to the remote site to execute it. Essentially the site on which the user clicks determines which service impl to call. Can anybody suggest a clean way to achieve this ?

4条回答
神经病院院长
2楼-- · 2019-04-09 07:05

Your question implies that by "dynamically" you mean that you'd like to be able to select at startup time, but that you don't need to be able to change it midrun. If that's the case, I very strongly recommend using @Profile. My usual practice, using annotation-based configuration, is to have a separate @Configuration class for each profile that defines the profile-specific @Beans that need to be available there. It's even easy to define development-only @Controllers that are completely disabled in production mode. Define a class with String constants for each profile to avoid typos.

查看更多
Evening l夕情丶
3楼-- · 2019-04-09 07:14

You can use the @Named annotation, and get the bean by name.

In this way, you can define a base name "ServiceName" and add some suffix, like "Remote" or "Local" depending on the condition you explained. You will also need to annotate your beans (service implementations) like

@Named("ServiceNameRemote") and @Named("ServiceNameLocal") respectively

Finally on your controller, you can use (DevService)BeanFactory.getBean("beanName") to get it by its dynamically generated name.

查看更多
萌系小妹纸
4楼-- · 2019-04-09 07:24

This is untested, but if you are using Spring-MVC, you should be able to handle it by checking your headers in the controller. Though, I'm not sure how your code is being deployed. The following approach has a few downfalls, but should work:

package <PACKAGE NAME>;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/device")
public class DeviceController {

    @RequestMapping(headers={"Host=*.local.domain.com"})
    public void localDevice(
        @ModelAttribute("device") Device device
    ) {
        ...
    }

    @RequestMapping(headers={"Host!=*.local.domain.com"})
    public void remoteDevice(
        @ModelAttribute("device") Device device
    ) {
        ...
    }

}

Namely, it relies on your host staying the same, while you can inject a list for your headers, I'm not sure if you can still get the exclusion map for the remote host using that approach. Also, I think it matters greatly on how the request is routed into the controller class.

查看更多
混吃等死
5楼-- · 2019-04-09 07:27

Assuming you need both implementations in production environment (if not - use Spring profiles to clearly split beans between environments). Simple approach would be:

interface DevService
{
   void add(Device d);
   String getName();
}

@Service("devServiceLocal")
class DevServiceLocalImpl implements DevService
{
   void add(Device d) {...}
   String getName() {return "local";}
}

class Controller
{
   @Autowired
   Collection<DevService> services;

   void doSomethingWithService()
   {
      // TODO: Check type somehow
      String servType = "local";
      for(DevService s: services)
      {
         if(servType.equals(s.getName())
         {
            // Call service methods
            break;
         }
      }
   }
}
查看更多
登录 后发表回答