Подтвердить что ты не робот

Плагин Maven для проверки конфигурации Spring?

Кто-нибудь знает о плагине Maven, который можно использовать для проверки файлов конфигурации Spring? Подтверждением я имею в виду:

  • Проверить все beans ссылку на класс в пути сборки
  • Убедитесь, что все ссылки bean относятся к допустимому определению bean
  • Проверить отсутствие сиротских beans
  • Другие ошибки конфигурации Я уверен, что мне не хватает.

Я искал вокруг и ничего не придумал.

Плагин Maven был бы идеален для моих целей, но любые другие инструменты (плагин Eclipse и т.д.) были бы оценены.

4b9b3361

Ответ 1

Что мы делаем в нашем проекте, просто напишите JUnit-тест, который загружает конфигурацию Spring. Вот некоторые из описанных вами вещей:

  • Подтвердить XML
  • Обеспечивает загрузку beans классами класса (по крайней мере beans, которые не являются ленивыми)

Он не проверяет, нет ли сироты beans. Нет надежного способа сделать это в любом случае из любого места в вашем коде, вы можете искать beans напрямую, учитывая их идентификатор. Просто потому, что bean не ссылается ни на один другой beans, не означает, что он не используется. Фактически все конфигурации Spring будут иметь как минимум один bean, на который не ссылаются другие beans, потому что в иерархии всегда должен быть корень.

Если у вас есть beans, который полагается на реальные службы, такие как базы данных или что-то еще, и вы не хотите подключаться к этим сервисам в тесте JUnit, вам просто нужно абстрагироваться от конфигурации, чтобы можно было использовать тестовые значения. Это можно легко выполнить с помощью PropertyPlaceholderConfigurer, который позволяет вам иметь разные свойства, указанные в отдельных файлах конфигурации для каждой среды, а затем ссылаться на одно определение beans файл.

EDIT (чтобы включить пример кода):
Как мы это делаем, есть как минимум 3 разных файла Spring...

  • SRC/основные/ресурсы/applicationContext.xml
  • SRC/основные/ресурсы/beanDefinitions.xml
  • SRC/тест/ресурсы/testContext.xml

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="classpath:beanDefinitions.xml"/>

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="file:path/environment.properties" />
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${driver}" />
        ...
    </bean>

    ... <!-- more beans which shouldn't be loaded in a test go here -->

</beans>

beanDefinitions.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="myBean" class="com.example.MyClass">
        ...
    </bean>

    <bean id="myRepo" class="com.example.MyRepository">
        <property name="dataSource" ref="dataSource"/>
        ...
    </bean>

    ... <!-- more beans which should be loaded in a test -->

</beans>

testContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="classpath:beanDefinitions.xml"/>

    <bean id="dataSource" class="org.mockito.Mockito" factory-method="mock">
        <constructor-arg value="org.springframework.jdbc.datasource.DriverManagerDataSource"/>
    </bean>

</beans>

Здесь многое происходит, позвольте мне объяснить...

  • Файл applicationContext.xml является основным Spring файлом для всего вашего приложения. Он содержит PropertyPlaceHolder bean, позволяющий настраивать определенные значения свойств между различными средами, которые мы развертываем (test vs. prod). Он импортирует все основные beans, которые приложение должно выполнить. Любые beans, которые не должны использоваться в тесте, например DB beans, или другие классы, которые взаимодействуют с внешними службами/ресурсами, должны быть определены в этом файле.
  • В файле beanDefinitions.xml есть все ваши обычные beans в нем, которые не полагаются на внешние вещи. Эти beans могут и будут ссылаться на beans, определенные в файле appContext.xml.
  • Файл testContext.xml является тестовой версией appContext. Ему нужны версии всех beans, определенных в файле appContext.xml, но мы использовали насмешливую библиотеку для создания этих beans. Таким образом, реальные классы не используются и нет риска доступа к внешним ресурсам. Этот файл также не нуждается в заполнитель свойств bean.

Теперь, когда у нас есть тестовый контекст, который мы не боимся загружать из теста, вот код для этого...

SpringContextTest.java  пакет com.example;

import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class SpringContextTest {
    @Test
    public void springContextCanLoad() {
        XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("testContext.xml"));

        for (String beanName : factory.getBeanDefinitionNames()) {
            Object bean = factory.getBean(beanName);
            // assert anything you want
        }
    }
}

Это может быть не оптимальный способ сделать это; класс ApplicationContext является рекомендуемым способом загрузки контекстов Spring. Вышеупомянутое может быть заменено на:

    @Test
    public void springContextCanLoad() {
        ApplicationContext context = new FileSystemXmlApplicationContext("classpath:testContext.xml");
    }

Я считаю, что одна строка выполнит все, что вам нужно, чтобы проверить, что ваш контекст Spring правильно подключен. Оттуда вы можете загрузить beans и утверждать, как раньше.

Надеюсь, это поможет!

Ответ 3

Я наткнулся на этот вопрос, когда гуглинг - у меня был точно такой же вопрос.

Я написал (очень непроверенный) плагин Maven, чтобы сделать это. В настоящее время он поддерживает только WAR, но может быть легко расширен. Кроме того, я не беспокоюсь о загрузке beans, так как я не хочу, чтобы вам пришлось поддерживать большой набор свойств, чтобы удовлетворить этот плагин.

Вот он, если он когда-нибудь будет использоваться:

package myplugins;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.ClassUtils;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Validates Spring configuration resource and class references
 * using a classloader that looks at the specified WAR lib and classes
 * directory.
 * <p/>
 * It doesn't attempt to load the application context as to avoid the
 * need to supply property files
 * <br/>
 * TODO: maybe one day supplying properties will become an optional part of the validation.
 *
 * @goal validate
 * @aggregator
 * @phase install
 */
public class WarSpringValidationMojo extends AbstractMojo
{
    private final static String FILE_SEPARATOR = System.getProperty("file.separator");


    /**
     * Project.
     * @parameter expression="${project}"
     * @readonly
     */
    private MavenProject project;


    /**
     * The WAR root Spring configuration file name.
     *
     * @parameter expression="${applicationContext}" default-value="webAppConfig.xml"
     */
    private String applicationContext;


    /**
     * The WAR directory.
     *
     * @parameter expression="${warSourceDirectory}" default-value="${basedir}/target/${project.build.finalName}"
     */
    private File warSourceDirectory;


    @SuppressWarnings("unchecked")
    public void execute() throws MojoExecutionException
    {
        try
        {
            if ("war".equals(project.getArtifact().getType()))
            {
                File applicationContextFile = new File(warSourceDirectory, "WEB-INF" + FILE_SEPARATOR + applicationContext);
                File classesDir = new File(warSourceDirectory, "WEB-INF" + FILE_SEPARATOR + "classes");
                File libDir = new File(warSourceDirectory, "WEB-INF" + FILE_SEPARATOR + "lib");

                Set<URL> classUrls = new HashSet<URL>();

                if (classesDir.exists())
                {
                    classUrls.addAll(getUrlsForExtension(classesDir, "class", "properties"));
                }
                if (libDir.exists())
                {
                    classUrls.addAll(getUrlsForExtension(libDir, "jar", "zip"));
                }

                ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
                ClassLoader classLoader = new URLClassLoader(classUrls.toArray(new URL[classUrls.size()]), parentClassLoader);

                ClassUtils.overrideThreadContextClassLoader(classLoader);

                DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
                factory.setBeanClassLoader(classLoader);

                XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
                reader.setValidating(true);
                reader.loadBeanDefinitions(new FileSystemResource(applicationContextFile));

                for (String beanName : factory.getBeanDefinitionNames())
                {
                    validateBeanDefinition(classLoader, factory.getBeanDefinition(beanName), beanName);
                }

                getLog().info("Successfully validated Spring configuration (NOTE: validation only checks classes, " +
                        "property setter methods and resource references)");
            }
            else
            {
                getLog().info("Skipping validation since project artifact is not a WAR");
            }
        }
        catch (Exception e)
        {
            getLog().error("Loading Spring beans threw an exception", e);

            throw new MojoExecutionException("Failed to validate Spring configuration");
        }
    }


    private void validateBeanDefinition(ClassLoader beanClassloader, BeanDefinition beanDefinition, String beanName) throws Exception
    {
        Class<?> beanClass = validateBeanClass(beanClassloader, beanDefinition, beanName);
        validateBeanConstructor(beanDefinition, beanName, beanClass);
        validateBeanSetters(beanDefinition, beanName, beanClass);
    }


    private Class<?> validateBeanClass(ClassLoader beanClassloader, BeanDefinition beanDefinition, String beanName) throws Exception
    {
        Class<?> beanClass;

        try
        {
            beanClass = beanClassloader.loadClass(beanDefinition.getBeanClassName());
        }
        catch (ClassNotFoundException e)
        {
            throw new ClassNotFoundException("Cannot find " + beanDefinition.getBeanClassName() +
                    " for bean '" + beanName + "' in " + beanDefinition.getResourceDescription(), e);
        }

        return beanClass;
    }


    private void validateBeanConstructor(BeanDefinition beanDefinition, String beanName,
            Class<?> beanClass) throws Exception
    {
        boolean foundConstructor = false;

        ConstructorArgumentValues constructorArgs = beanDefinition.getConstructorArgumentValues();
        Class<?>[] argTypes = null;

        if (constructorArgs != null)
        {
            Constructor<?>[] constructors = beanClass.getDeclaredConstructors();
            int suppliedArgCount = constructorArgs.getArgumentCount();
            boolean isGenericArgs = !constructorArgs.getGenericArgumentValues().isEmpty();

            for (int k = 0; k < constructors.length && !foundConstructor; k++)
            {
                Constructor<?> c = constructors[k];

                knownConstructorLoop:
                {
                    Class<?>[] knownConstructorsArgTypes = c.getParameterTypes();

                    if (knownConstructorsArgTypes.length == suppliedArgCount)
                    {
                        if (isGenericArgs)
                        {
                            foundConstructor = true; // TODO - support generic arg checking
                        }
                        else
                        {
                            for (int i = 0; i < knownConstructorsArgTypes.length; i++)
                            {
                                Class<?> argType = knownConstructorsArgTypes[i];
                                ConstructorArgumentValues.ValueHolder valHolder = constructorArgs.getArgumentValue(i,
                                        argType);

                                if (valHolder == null)
                                {
                                    break knownConstructorLoop;
                                }
                            }

                            foundConstructor = true;
                        }
                    }
                }
            }
        }
        else
        {
            try
            {
                Constructor c = beanClass.getConstructor(argTypes);
                foundConstructor = true;
            }
            catch (Exception ignored) { }
        }

        if (!foundConstructor)
        {
            throw new NoSuchMethodException("No matching constructor could be found for bean '" +
                        beanName + "' for " + beanClass.toString() + " in " + beanDefinition.getResourceDescription());
        }
    }


    private void validateBeanSetters(BeanDefinition beanDefinition, String beanName, Class<?> beanClass) throws Exception
    {
        MutablePropertyValues properties = beanDefinition.getPropertyValues();
        List<PropertyValue> propList = properties.getPropertyValueList();

        try
        {
            Method[] methods = beanClass.getMethods();

            for (PropertyValue p : propList)
            {
                boolean foundMethod = false;
                String propName = p.getName();
                String setterMethodName = "set" + propName.substring(0, 1).toUpperCase();

                if (propName.length() > 1)
                {
                    setterMethodName += propName.substring(1);
                }

                for (int i = 0; i < methods.length && !foundMethod; i++)
                {
                    Method m = methods[i];
                    foundMethod = m.getName().equals(setterMethodName);
                }

                if (!foundMethod)
                {
                    throw new NoSuchMethodException("No matching setter method " + setterMethodName
                            + " could be found for bean '" +    beanName + "' for " + beanClass.toString() +
                            " in " + beanDefinition.getResourceDescription());
                }
            }
        }
        catch (NoClassDefFoundError e)
        {
            getLog().warn("Could not validate setter methods for bean " + beanName +
                    " since getting the methods of " + beanClass + " threw a NoClassDefFoundError: "
                    + e.getLocalizedMessage());
        }
    }


    private Collection<? extends URL> getUrlsForExtension(File file, String... extensions) throws Exception
    {
        Set<URL> ret = new HashSet<URL>();

        if (file.isDirectory())
        {
            for (File childFile : file.listFiles())
            {
                ret.addAll(getUrlsForExtension(childFile, extensions));
            }
        }
        else
        {
            for (String ex : extensions)
            {
                if (file.getName().endsWith("." + ex))
                {
                    ret.add(file.toURI().toURL());
                    break;
                }
            }
        }

        return ret;
    }
}

И плагин pom.xml:

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        ... <my project parent> ...
    </parent>
    <groupId>myplugins</groupId>
    <artifactId>maven-spring-validation-plugin</artifactId>
    <version>1.0</version>
    <packaging>maven-plugin</packaging>
    <name>Maven Spring Validation Plugin</name>
    <url>http://maven.apache.org</url>

    <dependencies>
    <dependency>
        <groupId>org.apache.maven</groupId>
        <artifactId>maven-plugin-api</artifactId>
        <version>2.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.maven</groupId>
        <artifactId>maven-project</artifactId>
        <version>2.0.8</version>
    </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>3.0.7.RELEASE</version>
        </dependency>
    </dependencies>
</project>

После установки запустите на своем корневом уровне ваш WAR-модуль:

mvn myplugins:maven-spring-validation-plugin:validate