Создание динамического компонента Spring

Я использую 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 творил чудеса, находя правильный класс XXXLocalServiceUtil для каждого XXXLocalService, который я внедряю. Итак, что мне нужно, так это некая динамическая фабрика компонентов, которая будет делать всю работу за меня, конечно, на основе ленивой загрузки.

Кто-нибудь знает, как это может быть легко достигнуто?


person p.mesotten    schedule 29.05.2012    source источник
comment
Вы пытались поместить @Autowired вместо @Inject и определить свой xml для автоматического подключения по типу?   -  person richarbernal    schedule 29.05.2012
comment
Я думаю, вы можете найти свой ответ здесь.   -  person Reza    schedule 08.06.2012


Ответы (2)


Вы можете использовать GenericApplicationContext для динамической загрузки bean-компонентов в applicationContext вместе с остальными bean-компонентами Spring, объявленными в xml. Вот пример, реализованный с использованием библиотеки Reflections...

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();
}

Обновление: чтобы использовать это в веб-приложении, вы можете просто расширить XmlWebApplicationContext Spring и переопределить метод 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>
person hyness    schedule 05.06.2012
comment
Чудесно! Теперь, как я могу загрузить этот GenericApplicationContext в свое веб-приложение во время развертывания? Должен ли я писать собственный ContextLoader или есть более простой вариант? Заранее спасибо! - person p.mesotten; 07.06.2012
comment
Теперь я могу комментировать :) Рад, что смог помочь - person hyness; 08.06.2012

Одна вещь, которую вы можете попробовать, это изменить @Inject на @Autowired и определить в вашем applicationContext.xml автосвязывание по типу, например:

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

@Inject и @Autowired эквивалентны, но аннотация Spring @Autowired имеет то преимущество, что атрибут required должен быть введен в обязательном порядке.

Еще одно решение:

Вместо использования фабрик вы можете использовать bean-компоненты с областью действия prototype.

Ваш 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() вы будете получать новый объект этого типа с преимуществами внедрения всего в этот bean-компонент.

person richarbernal    schedule 29.05.2012
comment
Хм, я не могу использовать autowire-by-type, потому что Spring понятия не имеет, где найти экземпляр, например. тип UserLocalService (он не знает, что должен быть создан из фабричного класса UserLocalServiceUtil). Второе предложение на самом деле не вариант, потому что я хочу удалить как можно больше сантехники, а не добавлять больше. Спасибо, в любом случае. - person p.mesotten; 30.05.2012
comment
Затем введите свой XXXLocaServiceUtil вместо XXXLocalService или используйте прототип области видимости. - person richarbernal; 30.05.2012
comment
Я не хочу внедрять XXXLocalServiceUtil, потому что это класс только со статическими методами, который в фоновом режиме вызывает те же методы в XXXLocalService нестатическим способом. Это ограничение API, которое я использую, поэтому я не могу изменить этот факт. - person p.mesotten; 30.05.2012
comment
определите один новый bean-компонент для каждого XXXLocalServiceUtil в вашем applicationContext.xml с именем XXXLocalService , атрибутом factory-bean, ссылающимся на XXXLocalServiceUtil, и attibute factory-method, перемещенным из XXXLocalServiceUtil в этот bean-компонент. Надеюсь, эта работа для вас - person richarbernal; 30.05.2012
comment
Это именно то, что я делаю сейчас. Но поскольку существует более 100 таких XXXLocalServices, и все фабричные методы имеют одно и то же имя, мне стало интересно, есть ли простой способ определить эти bean-компоненты на динамической основе, без необходимости перечислять каждый из них. - person p.mesotten; 30.05.2012