2013-02-21 28 views
10

Giả sử tôi có cấu trúc sau đây với giao diện dịch vụ ServiceInterface và một số thành phần đang triển khai nó: ProductAServiceProductBService Tôi cũng có một hạt giống có một tài sản đủ điều kiện nói rằng chúng tôi đang nói hiện đang xử lý ProductA hoặc ProductB. Làm thế nào có thể sau đó tự động tiêm với autowiring hoặc chú thích khác việc thực hiện chính xác (ProductAService hoặc ProductBService) vào một số dịch vụ cần nó (ServiceThatNeedsServiceInterface dưới đây).Tùy chỉnh Autowire đậu ứng cử viên trong mùa xuân 3

public interface ServiceInterface { 
    void someMethod(); 
} 

@Component(name="ProductAService") 
public class ProductAService implements ServiceInterface { 
    @Override public void someMethod() { 
    System.out.println("Hello, A Service"); 
    } 
} 

@Component(name="ProductBService") 
public class ProductBService implements ServiceInterface { 
    @Override public void someMethod() { 
    System.out.println("Hello, B Service"); 
    } 
} 

@Component 
public class ServiceThatNeedsServiceInterface { 

    // What to do here??? 
    @Autowired 
    ServiceInterface service; 

    public void useService() { 
    service.someMethod(); 
    } 
} 

@Component 
@Scope(value = WebApplicationContext.SCOPE_REQUEST) 
public class RequestContext { 
    String getSomeQualifierProperty(); 
} 

Trả lời

10

Nguồn Spring đã đề cập đến vấn đề của bạn khi họ tạo ra ServiceLocatorFactoryBean quay lại phiên bản 1.1.4. Để sử dụng nó, bạn cần phải thêm một giao diện tương tự như hình dưới đây:

public interface ServiceLocator { 
    //ServiceInterface service name is the one 
     //set by @Component 
    public ServiceInterface lookup(String serviceName); 
} 

Bạn cần phải thêm đoạn mã sau vào applicationContext.xml

<bean id="serviceLocatorFactoryBean" 
    class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean"> 
    <property name="serviceLocatorInterface" 
       value="org.haim.springframwork.stackoverflow.ServiceLocator" /> 
</bean> 

của bạn Bây giờ ServiceThatNeedsServiceInterface của bạn sẽ trông giống như bên dưới:

@Component 
public class ServiceThatNeedsServiceInterface { 
    // What to do here??? 
    // @Autowired 
    // ServiceInterface service; 

    /* 
    * ServiceLocator lookup returns the desired implementation 
    * (ProductAService or ProductBService) 
    */ 
@Autowired 
    private ServiceLocator serviceLocatorFactoryBean; 

    //Let’s assume we got this from the web request 
    public RequestContext context; 

    public void useService() { 
     ServiceInterface service = 
     serviceLocatorFactoryBean.lookup(context.getQualifier()); 
     service.someMethod();   
     } 
} 

ServiceLocatorFactoryBean sẽ trả lại dịch vụ mong muốn dựa trên vòng loại RequestContext. Ngoài các chú giải mùa xuân, mã của bạn không phụ thuộc vào Mùa xuân. tôi thực hiện bài kiểm tra đơn vị sau đây cho trên

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = { "classpath:META-INF/spring/applicationContext.xml" }) 
public class ServiceThatNeedsServiceInterfaceTest { 

    @Autowired 
    ServiceThatNeedsServiceInterface serviceThatNeedsServiceInterface; 

    @Test 
    public void testUseService() { 
    //As we are not running from a web container 
    //so we set the context directly to the service 
     RequestContext context = new RequestContext(); 
     context.setQualifier("ProductAService"); 
     serviceThatNeedsServiceInterface.context = context; 
     serviceThatNeedsServiceInterface.useService(); 

     context.setQualifier("ProductBService"); 
     serviceThatNeedsServiceInterface.context = context; 
     serviceThatNeedsServiceInterface.useService(); 
    } 

} 

Giao diện điều khiển sẽ hiển thị
Xin chào, Một dịch vụ
Xin chào, B Dịch vụ

Một lời cảnh báo. Tài liệu API nêu rõ rằng
“Các trình định vị dịch vụ như vậy… thường sẽ được sử dụng cho các bean nguyên mẫu, nghĩa là các phương thức của nhà máy được cho là trả về một cá thể mới cho mỗi cuộc gọi… Đối với các hạt đơn, bộ đặt trực tiếp hoặc tiêm khởi tạo của bean mục tiêu thích hợp hơn. ”

Tôi không thể hiểu tại sao điều này có thể gây ra vấn đề. Trong mã của tôi, nó trả về cùng một dịch vụ trên hai cuộc gọi tuần tự tới serviceThatNeedsServiceInterface.useService();

Bạn có thể tìm thấy mã nguồn ví dụ của tôi trong GitHub

+0

Bingo! Đây là câu trả lời chính xác. Tôi không ngại chút xíu về cấu hình XML. – Strelok

1

Tôi đoán, bạn đã bỏ lỡ chú thích, cho bạn biết rằng bạn có dịch vụ tùy chỉnh. Vì vậy, giải pháp của bạn là thêm chú thích này trước tên lớp:

@Service("ProductAService") 
public class ProductAService implements ServiceInterface { 
    @Override public void someMethod() { 
    System.out.println("Hello, A Service"); 
    } 
} 

@Service("ProductBService") 
public class ProductBService implements ServiceInterface { 
    @Override public void someMethod() { 
    System.out.println("Hello, B Service"); 
    } 
} 

Và sau đó bạn có thể dây tự động nó, nhưng để sử dụng các dịch vụ cụ thể, bạn cần phải thêm chú thích Qualifier() như thế này:

@Autowired 
    @Qualifier("ProductBService") // or ProductAService 
    ServiceInterface service; 

Hoặc có thể bạn phải thêm chỉ là một chú thích Qualifier ("tên của đậu của bạn") :)

+0

Tôi biết tôi có thể làm điều này, nhưng tôi sẽ phải có tất cả các dịch vụ khác nhau như lĩnh vực riêng biệt trong lớp có sử dụng nó. Tôi muốn các tài sản trong RequestContext là yếu tố quyết định mà "sản phẩm" thực hiện để tiêm khi autowiring. – Strelok

0

tôi không nghĩ rằng bạn có thể làm điều này với chú thích, lý do là bạn cần một bean mà là động trên thời gian chạy (có thể là dịch vụ hoặc dịch vụ B), vì vậy @Autowire w bị bệnh có dây trước khi đậu được sử dụng ở bất kỳ nơi nào. Một giải pháp là lấy đậu từ ngữ cảnh khi bạn cần.

@Component 
public class ServiceThatNeedsServiceInterface { 


    ServiceInterface service; 

    public void useService() { 
    if(something is something){ 
     service = applicationContext.getBean("Abean", ServiceInterface.class); 
    }else{ 
     service = applicationContext.getBean("Bbean", ServiceInterface.class); 
    } 
    service.someMethod(); 
    } 
} 

Bạn có thể đặt là logic khác ở đâu đó trong lớp như một chức năng riêng biệt:

public void useService() { 
     service = findService(); 
     service.someMethod(); 
     } 

public ServiceInterface findService() { 
     if(something is something){ 
      return applicationContext.getBean("Abean", ServiceInterface.class); 
     }else{ 
      return applicationContext.getBean("Bbean", ServiceInterface.class); 
     } 

     } 

Đây là năng động và điều này có thể là những gì bạn muốn.

3

Cách duy nhất tôi có thể nghĩ để làm một cái gì đó giống như những gì bạn đang tìm kiếm là tạo một cái gì đó giống như một FactoryBean trả về việc triển khai thích hợp dựa trên thuộc tính RequestContext. Đây là điều tôi đã tát cùng nhau có hành vi mà bạn muốn:

import org.springframework.beans.factory.FactoryBean; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.context.annotation.Scope; 
import org.springframework.stereotype.Component; 
import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RequestMethod; 
import org.springframework.web.bind.annotation.ResponseBody; 
import org.springframework.web.context.WebApplicationContext; 

import javax.servlet.http.HttpServletRequest; 

public class InjectionQualifiedByProperty { 

    @Controller 
    @Scope(WebApplicationContext.SCOPE_REQUEST) 
    public static class DynamicallyInjectedController { 
     @Autowired 
     @Qualifier("picker") 
     Dependency dependency; 

     @RequestMapping(value = "/sayHi", method = RequestMethod.GET) 
     @ResponseBody 
     public String sayHi() { 
      return dependency.sayHi(); 
     } 
    } 

    public interface Dependency { 
     String sayHi(); 
    } 

    @Configuration 
    public static class Beans { 
     @Bean 
     @Scope(WebApplicationContext.SCOPE_REQUEST) 
     @Qualifier("picker") 
     FactoryBean<Dependency> dependencyPicker(final RequestContext requestContext, 
               final BobDependency bob, final FredDependency fred) { 
      return new FactoryBean<Dependency>() { 
       @Override 
       public Dependency getObject() throws Exception { 
        if ("bob".equals(requestContext.getQualifierProperty())) { 
         return bob; 
        } else { 
         return fred; 
        } 
       } 

       @Override 
       public Class<?> getObjectType() { 
        return Dependency.class; 
       } 

       @Override 
       public boolean isSingleton() { 
        return false; 
       } 
      }; 
     } 
    } 

    @Component 
    public static class BobDependency implements Dependency { 
     @Override 
     public String sayHi() { 
      return "Hi, I'm Bob"; 
     } 
    } 

    @Component 
    public static class FredDependency implements Dependency { 
     @Override 
     public String sayHi() { 
      return "I'm not Bob"; 
     } 
    } 

    @Component 
    @Scope(WebApplicationContext.SCOPE_REQUEST) 
    public static class RequestContext { 
     @Autowired HttpServletRequest request; 

     String getQualifierProperty() { 
      return request.getParameter("which"); 
     } 
    } 
} 

Tôi đã làm ví dụ làm việc sử dụng mã này on Github. Bạn có thể sao chép và chạy nó với:

git clone git://github.com/zzantozz/testbed tmp 
cd tmp/spring-mvc 
mvn jetty:run 

Sau đó tham quan http://localhost:8080/dynamicallyInjected để xem kết quả của một sự phụ thuộc, và http://localhost:8080/dynamicallyInjected?which=bob để xem người kia.

0

Bạn có thể sử dụng các chú thích @Qualifier kết hợp với bí danh. Xem ví dụ về cách nó được sử dụng để tải một bean dựa trên thuộc tính here. Bạn có thể sửa đổi cách tiếp cận này và thay đổi thuộc tính/bí danh trong requestcontext ...

Các vấn đề liên quan