2010-05-25 26 views
38

Với một nhà máy sản xuất bean Spring được cấu hình XML, tôi có thể dễ dàng khởi tạo nhiều phiên bản của cùng một lớp với các tham số khác nhau. Làm thế nào tôi có thể làm tương tự với chú thích? Tôi muốn một cái gì đó như thế này:Khởi tạo nhiều hạt cùng lớp với chú thích mùa xuân

@Component(firstName="joe", lastName="smith") 
@Component(firstName="mary", lastName="Williams") 
public class Person { /* blah blah */ } 
+2

Tôi không nghĩ bạn có thể. '@ Component' là một tiện ích nhẹ, nhưng không thay thế cho cấu hình XML. – skaffman

+7

Tôi nghĩ thật đáng tiếc khi XML được coi là cách thích hợp để cấu hình một ứng dụng. –

+0

Chỉ vì '@ Component' không thể làm điều đó không có nghĩa là XML là giải pháp. Tôi không biết về năm 2011, nhưng bạn có thể đạt được hiệu quả tương tự trong java '@ Configuration' ngay bây giờ. – apottere

Trả lời

15

Vâng, bạn có thể làm điều đó với sự trợ giúp của tùy chỉnh BeanFactoryPostProcessor thực hiện của bạn.

Đây là một ví dụ đơn giản.

Giả sử chúng tôi có hai thành phần. Một là phụ thuộc cho người khác.

thành phần đầu tiên:

import org.springframework.beans.factory.InitializingBean; 
import org.springframework.util.Assert; 

public class MyFirstComponent implements InitializingBean{ 

    private MySecondComponent asd; 

    private MySecondComponent qwe; 

    public void afterPropertiesSet() throws Exception { 
     Assert.notNull(asd); 
     Assert.notNull(qwe); 
    } 

    public void setAsd(MySecondComponent asd) { 
     this.asd = asd; 
    } 

    public void setQwe(MySecondComponent qwe) { 
     this.qwe = qwe; 
    } 
} 

Như bạn có thể thấy, không có gì là đặc biệt về thành phần này. Nó có sự phụ thuộc vào hai trường hợp khác nhau của MySecondComponent.

Thứ hai thành phần:

import org.springframework.beans.factory.FactoryBean; 
import org.springframework.beans.factory.annotation.Qualifier; 


@Qualifier(value = "qwe, asd") 
public class MySecondComponent implements FactoryBean { 

    public Object getObject() throws Exception { 
     return new MySecondComponent(); 
    } 

    public Class getObjectType() { 
     return MySecondComponent.class; 
    } 

    public boolean isSingleton() { 
     return true; 
    } 
} 

Đó là khó khăn hơn một chút. Đây là hai điều cần giải thích. Đầu tiên - @Qualifier - chú thích chứa tên của hạt MySecondComponent. Đó là một tiêu chuẩn, nhưng bạn được tự do thực hiện của riêng bạn. Bạn sẽ thấy một chút sau đó tại sao.

Điều thứ hai cần đề cập là triển khai FactoryBean. Nếu đậu thực hiện giao diện này, nó sẽ tạo ra một số trường hợp khác. Trong trường hợp của chúng ta, nó tạo ra các cá thể với kiểu MySecondComponent.

Phần khó khăn nhất là thực hiện BeanFactoryPostProcessor:

import java.util.Map; 

import org.springframework.beans.BeansException; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 


public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { 
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { 
     Map<String, Object> map = configurableListableBeanFactory.getBeansWithAnnotation(Qualifier.class); 
     for(Map.Entry<String,Object> entry : map.entrySet()){ 
      createInstances(configurableListableBeanFactory, entry.getKey(), entry.getValue()); 
     } 

    } 

    private void createInstances(
      ConfigurableListableBeanFactory configurableListableBeanFactory, 
      String beanName, 
      Object bean){ 
     Qualifier qualifier = bean.getClass().getAnnotation(Qualifier.class); 
     for(String name : extractNames(qualifier)){ 
      Object newBean = configurableListableBeanFactory.getBean(beanName); 
      configurableListableBeanFactory.registerSingleton(name.trim(), newBean); 
     } 
    } 

    private String[] extractNames(Qualifier qualifier){ 
     return qualifier.value().split(","); 
    } 
} 

nó làm gì? Nó đi qua tất cả các bean được chú thích bằng @Qualifier, trích xuất các tên từ chú thích và sau đó tạo thủ công các hạt của loại này với các tên được chỉ định.

Đây là một cấu hình mùa xuân:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 

    <bean class="MyBeanFactoryPostProcessor"/> 

    <bean class="MySecondComponent"/> 


    <bean name="test" class="MyFirstComponent"> 
     <property name="asd" ref="asd"/> 
     <property name="qwe" ref="qwe"/> 
    </bean> 

</beans> 

Điều cuối cùng để nhận thấy ở đây là mặc dù bạn có thể làm điều đó bạn không nên trừ khi nó là điều bắt buộc, bởi vì đây là một cách không thực sự tự nhiên cấu hình. Nếu bạn có nhiều hơn một thể hiện của lớp, tốt hơn là nên kết nối với cấu hình XML.

+0

Không nên 'isSingleton' trả về' false'? – OrangeDog

+1

Không có bean nào được tạo ra sẽ được xử lý sau, có lẽ vì 'BeanFactoryPostProcessor' chạy trước khi bất kỳ' BeanPostProcessor 'nào đã được tạo. Tài liệu này cũng cho biết không thể khởi tạo bất kỳ bean nào (ví dụ: 'getBeansWithAnnotation') trong' postProcessBeanFactory'. – OrangeDog

+1

Tôi đã tìm hiểu cách tránh một số vấn đề sau: http://stackoverflow.com/a/41489377/476716 – OrangeDog

28

Không thể thực hiện được. Bạn nhận được một ngoại lệ trùng lặp.

Nó cũng tối ưu với dữ liệu cấu hình như thế này trong các lớp triển khai của bạn.

Nếu bạn muốn sử dụng chú thích, bạn có thể cấu hình lớp học của bạn với Java config:

@Configuration 
public class PersonConfig { 

    @Bean 
    public Person personOne() { 
     return new Person("Joe", "Smith"); 
    } 

    @Bean 
    public Person personTwo() { 
     return new Person("Mary", "Williams"); 
    } 
} 
+1

Nó không thực sự không thể, nhưng nó không cần thiết khó khăn. – wax

+2

Nhưng bạn đang sử dụng một nhà máy riêng biệt cho từng hạt mùa xuân khác nhau mà bạn tạo. Tôi tin rằng anh ấy muốn cấu hình chú thích nằm trong lớp Person. Và tôi chỉ có thể thấy một chú thích trong ví dụ tương đối nâng cao của bạn và chú thích đó không hỗ trợ một số bean khác nhau. Nhưng tôi ấn tượng về sự phức tạp của giải pháp của bạn mặc dù :-) – Espen

+0

làm cách nào bạn sử dụng giá trị Joe từ tệp thuộc tính? –

8

Tôi chỉ phải giải quyết một trường hợp tương tự. Điều này có thể hoạt động nếu bạn có thể xác định lại lớp học.

// This is not a @Component 
public class Person { 

} 

@Component 
public PersonOne extends Person { 
    public PersonOne() { 
     super("Joe", "Smith"); 
    } 
} 

@Component 
public PersonTwo extends Person { 
    public PersonTwo() { 
    super("Mary","Williams"); 
    } 
} 

Sau đó, chỉ cần sử dụng PersonOne hoặc PersonTwo bất cứ khi nào bạn cần tự động một trường hợp cụ thể, ở mọi nơi khác, chỉ cần sử dụng Person.

+4

Đây là phương pháp Java đơn giản - nhiều hơn giống như Spring và ít mã hơn sẽ sử dụng chú thích Spring @Qualifier. – Pavel

1

Lấy cảm hứng từ wax's answer, việc thực hiện có thể được an toàn hơn và không bỏ qua khác post-processing nếu định nghĩa được thêm vào, độc thân không xây dựng:

public interface MultiBeanFactory<T> { // N.B. should not implement FactoryBean 
    T getObject(String name) throws Exception; 
    Class<?> getObjectType(); 
    Collection<String> getNames(); 
} 

public class MultiBeanFactoryPostProcessor implements BeanFactoryPostProcessor { 
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 
    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; 
    Map<String, MultiBeanFactory> factories = beanFactory.getBeansOfType(MultiBeanFactory.class); 

    for (Map.Entry<String, MultiBeanFactory> entry : factories.entrySet()) { 
     MultiBeanFactory factoryBean = entry.getValue(); 
     for (String name : factoryBean.getNames()) { 
     BeanDefinition definition = BeanDefinitionBuilder 
      .genericBeanDefinition(factoryBean.getObjectType()) 
      .setScope(BeanDefinition.SCOPE_SINGLETON) 
      .setFactoryMethod("getObject") 
      .addConstructorArgValue(name) 
      .getBeanDefinition(); 
     definition.setFactoryBeanName(entry.getKey()); 
     registry.registerBeanDefinition(entry.getKey() + "_" + name, definition); 
     } 
    } 
    } 
} 

@Configuration 
public class Config { 
    @Bean 
    public static MultiBeanFactoryPostProcessor() { 
    return new MultiBeanFactoryPostProcessor(); 
    } 

    @Bean 
    public MultiBeanFactory<Person> personFactory() { 
    return new MultiBeanFactory<Person>() { 
     public Person getObject(String name) throws Exception { 
     // ... 
     } 
     public Class<?> getObjectType() { 
     return Person.class; 
     } 
     public Collection<String> getNames() { 
     return Arrays.asList("Joe Smith", "Mary Williams"); 
     } 
    }; 
    } 
} 

Tên đậu vẫn có thể đến từ bất cứ nơi nào, chẳng hạn như @Qualifier dụ sáp của . Có nhiều thuộc tính khác trên định nghĩa bean, bao gồm cả khả năng kế thừa từ chính nhà máy.

+0

Điều này thật tuyệt. Tôi ngần ngại vi phạm ràng buộc vòng đời của mùa xuân trên các kiểu 'BeanFactoryPostProcessor' bằng cách tạo ra các bean trong giai đoạn xử lý bài nhà máy đậu. Mặc dù các bean được tạo ra ở đây chỉ có ý định thay đổi các định nghĩa bean (mà, riêng nó, _should_ được an toàn trong giai đoạn này), điều quan trọng là phải biết rằng bất kỳ bean nào khác được yêu cầu bởi lớp '@ Configuration' cũng có thể được khởi tạo. Điều này có thể dẫn đến các vấn đề vì các ràng buộc vòng đời bị vi phạm. Miễn là tài liệu này được hiểu rõ, được hiểu và các thay đổi được xem xét cẩn thận thì đây là một giải pháp tốt. –

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