Spring注入XML未编组的实体

时间:2019-03-15 10:46:04

标签: java spring dependency-injection jaxb

我正在开发一个Spring应用程序,我想知道是否可以在配置中指定XML文件的路径,并通过JAXB将其自动解组为Java对象(我可能会考虑其他方法)库),然后将其注入到bean中。

Google搜索产生不同的结果,但它们似乎更多地是在您的bean中注入编组器/解组器,然后自己完成工作(例如https://www.intertech.com/Blog/jaxb-tutorial-how-to-marshal-and-unmarshal-xml/),我对将样板委托给Spring更感兴趣。

谢谢

1 个答案:

答案 0 :(得分:1)

您可以根据以下文章实现自定义资源加载器:Spicy Spring: Create your own ResourceLoader。它需要一些假设:

  • 您要加载的类具有JAXB使用的所有必需注释,这些注释允许反序列化。
  • 您可以使用给定的类列表来构建JaxbContext
  • 您需要检查自己是否已加载类。

步骤0-创建POJO

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "User")
@XmlAccessorType(XmlAccessType.FIELD)
public class User {

    @XmlElement(name = "firstName")
    private String firstName;

    @XmlElement(name = "lastName")
    private String lastName;

    // getters, setters, toString
}

您需要预定义将从POJO文件加载的XML模型。上面的示例仅显示了一个类,但对于所有其他POJO类来说应该相似。

步骤1-创建解组器

import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

@Component
public class JaxbResourceUnmarshaller {

    private JAXBContext context;

    public JaxbResourceUnmarshaller() {
        try {
            context = JAXBContext.newInstance(User.class);
        } catch (JAXBException e) {
            throw new IllegalArgumentException(e);
        }
    }

    public Object read(Resource resource) {
        try {
            Unmarshaller unmarshaller = context.createUnmarshaller();

            return unmarshaller.unmarshal(resource.getInputStream());
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }
}

您需要创建JAXBContext的简单解组器实现。您需要提供所有root类。

第2步-创建课程资源

import org.springframework.core.io.AbstractResource;

import java.io.IOException;
import java.io.InputStream;

public class ClassResource extends AbstractResource {

    private final Object instance;

    public ClassResource(Object instance) {
        this.instance = instance;
    }

    public Object getInstance() {
        return instance;
    }

    @Override
    public String getDescription() {
        return "Resource for " + instance;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return null;
    }
}

我找不到任何可以返回POJO实例的特定类。上面的类很容易将类从反序列化器转移到Spring bean。您可以尝试找到更好的实施方式,或者在需要时对此进行改进。

第3步-创建JAXB资源加载器

import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

public class JaxbResourceLoader implements ResourceLoader {

    private static final String DB_URL_PREFIX = "jaxb:";
    private final ApplicationContext applicationContext;
    private final ResourceLoader delegate;

    public JaxbResourceLoader(ApplicationContext applicationContext, ResourceLoader delegate) {
        this.applicationContext = applicationContext;
        this.delegate = delegate;
    }

    @Override
    public Resource getResource(String location) {
        if (location.startsWith(DB_URL_PREFIX)) {
            JaxbResourceUnmarshaller unmarshaller = this.applicationContext.getBean(JaxbResourceUnmarshaller.class);
            String resourceName = location.replaceFirst(DB_URL_PREFIX, "");
            Resource resource = applicationContext.getResource("classpath:" + resourceName);

            Object instance = unmarshaller.read(resource);

            return new ClassResource(instance);
        }
        return this.delegate.getResource(location);
    }

    @Override
    public ClassLoader getClassLoader() {
        return this.delegate.getClassLoader();
    }
}

如果资源定义从jaxb:开始,让我们尝试处理它。在其他情况下,推迟到默认实现。仅支持classpath资源。

步骤4-注册JAXB资源加载器

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Ordered;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;

@Component
public class ResourceLoaderBeanPostProcessor implements BeanPostProcessor, BeanFactoryPostProcessor, Ordered,
        ResourceLoaderAware, ApplicationContextAware {

    private ResourceLoader resourceLoader;
    private ApplicationContext applicationContext;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) bean).setResourceLoader(this.resourceLoader);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        this.resourceLoader = new JaxbResourceLoader(this.applicationContext, this.resourceLoader);
        beanFactory.registerResolvableDependency(ResourceLoader.class, this.resourceLoader);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

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

这只是文章中注册类的一个副本,仅作了一些更改。最新的Spring版本可能会大大改善。

第5步-简单用法

假设您在pojos/user.xml文件夹中有resource个文件,如下所示:

<User>
    <firstName>Rick</firstName>
    <lastName>Bartez</lastName>
</User>

您可以将其注入Spring上下文中,如下所示:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;

@Configuration
public class JaxbAwareConfiguration {

    @Bean
    public AppOwner appOwner(ResourceLoader resourceLoader) {
        ClassResource resource = (ClassResource) resourceLoader.getResource("jaxb:pojos/user.xml");
        User user = (User) resource.getInstance();

        return new AppOwner(user);
    }
}

有点令人不快的是将资源投射到ClassResource和实例到User类,但这是该解决方案的缺点。