3

I'm using an API that exposes services in the form of XXXLocalServiceUtil classes which are static wrappers of singleton objects. Instead of using the static XXXLocalServiceUtil methods I want to inject the XXXLocalService objects themselves to use them directly in my code, e.g.:

@Named
public class MyMailingService {        
    @Inject UserLocalService userService;

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

And configure my applicationContext.xml like so:

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

This works perfectly. Now, this API I'm talking about has about 100 of these XXXLocalServiceUtil classes, each with their own static getService method which returns the actual service. Instead of listing all those services in my applicationContext.xml I would like to let Spring do the magic of finding the right XXXLocalServiceUtil class for each XXXLocalService I inject. So what I need is some kind of dynamic bean factory that will do the work for me, on a lazy-loading basis of course.

Anybody knows how this can be achieved easily?

p.mesotten
  • 1,402
  • 1
  • 14
  • 26
  • have you tried to put _@Autowired_ instead of _@Inject_, and define your xml to autowire by type? – richarbernal May 29 '12 at 19:35
  • 1
    I think you can find your answer [here](http://stackoverflow.com/questions/4540713/add-bean-programatically-to-spring-web-app-context). – Reza Jun 08 '12 at 10:32

2 Answers2

7

You can use a GenericApplicationContext to dynamically load the beans to an applicationContext along with the rest of your spring beans declared in xml. Here is an example implemented using the 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();
}

Update: To use this in a web application, you can simply extend Spring's XmlWebApplicationContext and override the initBeanDefinitionReader method as follows...

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

}

and add the following context-param to your web.xml...

<context-param>
  <param-name>contextClass</param-name>
  <param-value>x.y.z.MyXmlWebApplicationContext</param-value>
</context-param>
hyness
  • 4,785
  • 1
  • 22
  • 24
  • Wonderful! Now how can I load this GenericApplicationContext in my webapp at deploy time? Do I have to write a custom ContextLoader or is there an easier option? Thanks in advance! – p.mesotten Jun 07 '12 at 10:32
  • I can now comment :) Glad I was able to help – hyness Jun 08 '12 at 16:02
3

One thing you could try is to change @Inject to @Autowired and define in your applicationContext.xml to autowire by type, like this:

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

@Inject and @Autowired are equivalent, but Spring's @Autowired annotation has the advantage to have the required attribute to be mandatory injected.

Another solution:

Instead of using factories, you can use beans with the scope prototype.

Your XXXLocalService must implement the ApplicationContextAware interface, and get the prototyped bean through it, i. 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);
    }
}

And your applicationContext.xml looks like:

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

With every invocation of getBean() you'll get a new object of that type, with the benefits of injecting everything in that bean.

richarbernal
  • 1,063
  • 2
  • 14
  • 32
  • Hmm I can't really use autowire-by-type because Spring has no idea where to find an instance of e.g. the `UserLocalService` type (it doesn't know it has to be constructed from the `UserLocalServiceUtil` factory class). The second suggestion is not really an option, because I want to remove as much plumbing as possible, not add more. Thanks anyway. – p.mesotten May 30 '12 at 06:21
  • Then inject your XXXLocaServiceUtil instead of XXXLocalService, or use the scope prototype – richarbernal May 30 '12 at 06:25
  • I don't want to inject XXXLocalServiceUtil, because it's a class with only static methods that, in the background, calls the same methods on XXXLocalService in a non-static way. This is a limitation of the API I'm using, so I can't change this fact. – p.mesotten May 30 '12 at 08:31
  • 1
    define one new bean for each XXXLocalServiceUtil in your applicactionContext.xml with the name XXXLocalService ,the attribute factory-bean referencing XXXLocalServiceUtil and the attibute factory-method moved from XXXLocalServiceUtil to this bean. Hope this work for you – richarbernal May 30 '12 at 08:50
  • This is exactly what I'm doing now. But because there is 100+ of such XXXLocalServices, and all factory methods have the same name, I wondered if there were an easy way to define these beans on a "dynamic" basis, without having to list each and every one of them. – p.mesotten May 30 '12 at 09:27