Spring inject XML не маршалируемая сущность - PullRequest
2 голосов
/ 15 марта 2019

Я работаю над приложением Spring, и я хотел бы знать, могу ли я указать в своей конфигурации путь к файлу XML, автоматически распаковывая его в объект Java через JAXB (я могу рассмотреть другие библиотеки), а затем внедрить его в бин.

Поиск в Google дает разные результаты, но они больше похожи на введение маршаллера / демаршаллера в ваш бин, а затем на выполнение работы самостоятельно (например, https://www.intertech.com/Blog/jaxb-tutorial-how-to-marshal-and-unmarshal-xml/), и я больше заинтересован в том, чтобы передать этот шаблон Spring. .

Спасибо

1 Ответ

1 голос
/ 19 марта 2019

Вы можете реализовать свой собственный загрузчик ресурсов на основе этой статьи: Spicy Spring: создайте свой собственный 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 - создать unmarshaller

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

Простая реализация unmarshaller, где вам нужно создать 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 боб. Вы можете попытаться найти лучшую реализацию или улучшить ее, если это необходимо.

Шаг 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 классу, но это является недостатком этого решения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...