2012-05-29 150 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的限制,所以我无法改变这个事实。 –