2012-05-29 381 views
3

我正在使用一個API,以XXXLocalServiceUtil類的形式公開服務,它們是單例對象的靜態包裝。相反,使用靜態XXXLocalServiceUtil方法我想注入XXXLocalService對象本身,直接在我的代碼使用它們,例如:動態Spring bean創建

@Named 
public class MyMailingService {   
    @Inject UserLocalService userService; 

    public String mailUser(String email) { 
     User user = userService.getUser(email); 
     emailUser(user); 
    } 
} 

而且配置我applicationContext.xml像這樣:

<beans ...> 
    <bean class="x.y.z.UserLocalServiceUtil" factory-method="getService"/> 
    <bean class="x.y.z.CompanyLocalServiceUtil" factory-method="getService"/> 
    ... 
</beans> 

這完美的作品。現在,我所說的這個API有大約100個這樣的XXXLocalServiceUtil類,每個類都有自己的靜態getService方法,它返回實際的服務。我不希望在我的applicationContext.xml中列出所有這些服務,而是希望讓Spring爲我注入的每個XXXLocalService找到正確的XXXLocalServiceUtil類。所以我需要的是某種動態bean工廠,當然會在懶惰加載的基礎上爲我完成工作。

有人知道如何輕鬆實現這一點嗎?

+0

你試圖把@的_ @ _ Inject_代替Autowired_,並定義xml自動裝配按類型? – richarbernal

+1

我想你可以找到你的答案[這裏](http://stackoverflow.com/questions/4540713/add-bean-programatically-to-spring-web-app-context)。 – Reza

回答

6

您可以使用GenericApplicationContext將bean動態加載到applicationContext以及在xml中聲明的其他spring bean。下面是使用Reflections library ...

private static final Pattern SERVICE_UTIL_PATTERN = Pattern.compile(".*LocalServiceUtil.*"); 

public static void main(String[] args) { 
    ConfigurationBuilder builder = new ConfigurationBuilder().addUrls(
      ClasspathHelper.forPackage("x.y.z")) 
      .setScanners(new SubTypesScanner(false)); 
    Reflections reflections = new Reflections(builder); 
    GenericApplicationContext applicationContext = new GenericApplicationContext(); 
    Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class); 

    for (Class<? extends Object> serviceUtilClass : classes) { 
     String className = serviceUtilClass.getName(); 

     if (SERVICE_UTIL_PATTERN.matcher(className).matches()) { 
      GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); 
      beanDefinition.setBeanClassName(className); 
      beanDefinition.setFactoryMethodName("getService"); 
      beanDefinition.setLazyInit(true); 

      String beanName = StringUtils.uncapitalize(serviceClass.getSimpleName().replace("Util", "")); 
      applicationContext.registerBeanDefinition(beanName, beanDefinition); 
     } 
    } 

    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(applicationContext); 
    reader.loadBeanDefinitions("classpath:/applicationContext.xml"); 
    applicationContext.refresh(); 
} 

更新的例子來實現:要在Web應用程序中使用這個,你可以簡單地擴展Spring的XmlWebApplicationContext並重寫initBeanDefinitionReader方法如下...

private static final Pattern SERVICE_UTIL_PATTERN = Pattern.compile(".*LocalServiceUtil.*"); 

@Override 
protected void initBeanDefinitionReader(
     XmlBeanDefinitionReader beanDefinitionReader) { 
    ConfigurationBuilder builder = new ConfigurationBuilder().addUrls(
      ClasspathHelper.forPackage("x.y.z")) 
      .setScanners(new SubTypesScanner(false)); 
    Reflections reflections = new Reflections(builder); 
    Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class); 
    BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); 

    for (Class<? extends Object> serviceClass : classes) { 
     String className = serviceClass.getName(); 

     if (SERVICE_UTIL_PATTERN.matcher(className).matches()) { 
      GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); 
      beanDefinition.setBeanClassName(className); 
      beanDefinition.setFactoryMethodName("getService"); 
      beanDefinition.setLazyInit(true); 
      String beanName = StringUtils.uncapitalize(serviceClass 
        .getSimpleName().replace("Util", "")); 
      registry.registerBeanDefinition(beanName, beanDefinition); 
     } 
    } 
} 

}

,並添加以下context-param的web.xml ...

<context-param> 
    <param-name>contextClass</param-name> 
    <param-value>x.y.z.MyXmlWebApplicationContext</param-value> 
</context-param> 
+0

太棒了!現在如何在部署時在我的webapp中加載這個GenericApplicationContext?我必須編寫自定義的ContextLoader還是有更簡單的選項?提前致謝! –

+0

感謝您的更新。賞金當之無愧:) –

+0

我現在可以評論:)很高興我能夠幫助 – hyness

3

有一兩件事你可以嘗試是改變@Inject@Autowired並在applicationContext.xml中定義的類型自動裝配,像這樣:

<beans ... default-autowire="byType"> 
    ... 
</beans> 

@Inject@Autowired相當,但春天的@Autowired註解有優勢要求要求屬性被強制注入。

另一種解決方案:

而不是使用的工廠,你可以用豆範圍原型

您的XXXLocalService必須實現ApplicationContextAware接口,並通過它獲取原型bean,即:

@Named 
public class MyMailingService implements ApplicationContextAware {   
    @Inject UserLocalService userService; 

    private ApplicationContext appContext; 

    @Override 
    public void setApplicationContext(ApplicationContext applicationContext) { 
     this.appContext = applicationContext; 
    } 

    public String mailUser(String email) { 
     User user = (User) appContext.getBean("user"); 
     emailUser(user); 
    } 
} 

和你的applicationContext.xml的樣子:

<beans ...> 
    <bean id="user" class="x.y.z.User" scope="prototype"/> 
    ... 
</beans> 

隨着getBean()每次調用你會得到該類型的新對象,與注射在豆一切的好處。

+0

嗯我不能真的使用autowire-by-type,因爲Spring不知道在哪裏可以找到一個實例。 UserLocalService類型(它不知道它必須從UserLocalServiceUtil工廠類構造)。第二個建議不是真正的選擇,因爲我想盡可能多地刪除管道,而不是添加更多。不管怎麼說,還是要謝謝你。 –

+0

然後注入你XXXLocaServiceUtil代替XXXLocalService,或者使用範圍的原型 – richarbernal

+0

我不想注入XXXLocalServiceUtil,因爲它是隻用靜態方法的類,在後臺調用非靜態的方式上XXXLocalService同樣的方法。這是我使用的API的限制,所以我無法改變這個事實。 –