【问题标题】:Dynamic Spring bean creation动态 Spring bean 创建
【发布时间】:2012-06-04 00:22:01
【问题描述】:

我正在使用一个 API,它以 XXXLocalServiceUtil 类的形式公开服务,这些类是单例对象的静态包装器。我不想使用静态 XXXLocalServiceUtil 方法,而是想自己注入 XXXLocalService 对象以直接在我的代码中使用它们,例如:

@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 工厂,它可以为我完成工作,当然是在延迟加载的基础上。

有人知道如何轻松实现吗?

【问题讨论】:

  • 您是否尝试过使用 @Autowired 而不是 @Inject,并将您的 xml 定义为按类型自动装配?
  • 我想你可以找到答案here

标签: spring cdi inject


【解决方案1】:

您可以尝试的一件事是将 @Inject 更改为 @Autowired 并在 applicationContext.xml 中定义为按类型自动装配,如下所示:

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

@Inject@Autowired 是等价的,但是 Spring 的 @Autowired 注解的优点是强制注入了 required 属性。

另一种解决方案:

您可以使用 prototype 范围的 bean,而不是使用工厂。

您的XXXLocalService 必须实现ApplicationContextAware 接口,并通过它获取原型bean,即。 e. :

@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() 时,您都会获得该类型的新对象,并具有将所有内容注入该bean 的好处。

【讨论】:

  • 嗯,我不能真正使用 autowire-by-type,因为 Spring 不知道在哪里可以找到例如UserLocalService 类型(它不知道必须从 UserLocalServiceUtil 工厂类构造)。第二个建议不是一个真正的选择,因为我想尽可能多地移除管道,而不是添加更多。无论如何,谢谢。
  • 然后注入你的 XXXLocaServiceUtil 而不是 XXXLocalService,或者使用作用域原型
  • 我不想注入 XXXLocalServiceUtil,因为它是一个只有静态方法的类,在后台以非静态方式调用 XXXLocalService 上的相同方法。这是我使用的 API 的限制,所以我无法改变这一事实。
  • 为您的 applicactionContext.xml 中的每个 XXXLocalServiceUtil 定义一个新的 bean,名称为 XXXLocalService,引用 XXXLocalServiceUtil 的属性 factory-bean 和属性 factory-method 从 XXXLocalServiceUtil 移至此 bean。希望这对你有用
  • 这正是我现在正在做的。但是因为有 100 多个这样的 XXXLocalService,并且所有工厂方法都具有相同的名称,所以我想知道是否有一种简单的方法可以在“动态”的基础上定义这些 bean,而不必逐个列出。
【解决方案2】:

您可以使用 GenericApplicationContext 将 bean 与在 xml 中声明的其余 spring bean 一起动态加载到 applicationContext。这是一个使用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>

【讨论】:

  • 太棒了!现在如何在部署时将这个 GenericApplicationContext 加载到我的 webapp 中?我必须编写自定义 ContextLoader 还是有更简单的选择?提前致谢!
  • 我现在可以发表评论了 :) 很高兴我能提供帮助
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-19
  • 2012-08-18
  • 2018-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-16
相关资源
最近更新 更多