Плагин Maven для проверки конфигурации Spring? - PullRequest
17 голосов
/ 21 ноября 2010

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

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

Я искал и ничего не нашел.

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

Ответы [ 3 ]

9 голосов
/ 02 декабря 2010

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

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

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

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

РЕДАКТИРОВАТЬ (чтобы включить пример кода):
То, как мы это делаем, это как минимум 3 разных файла пружин ...

  • 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 является основным пружинным файлом для всего приложения. Он содержит bean-компонент PropertyPlaceHolder, позволяющий настраивать определенные значения свойств в различных средах, в которых мы развертываем (тест или продукт). Он импортирует все основные компоненты, необходимые для запуска приложения. Любые bean-компоненты, которые не должны использоваться в тесте, такие как bean-компоненты DB или другие классы, которые взаимодействуют с внешними службами / ресурсами, должны быть определены в этом файле.
  • Файл beanDefinitions.xml содержит все ваши обычные компоненты, которые не зависят от внешних факторов. Эти бины могут и будут ссылаться на бины, определенные в файле appContext.xml.
  • Файл testContext.xml является тестовой версией appContext. Ему нужны версии всех bean-компонентов, определенных в файле appContext.xml, но мы использовали библиотеку-макет для создания экземпляров этих 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 является рекомендуемым способом загрузки контекстов пружины. Вышеприведенное может быть заменено на:

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

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

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

0 голосов
/ 07 марта 2012

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

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

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

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's 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's root Spring configuration file name.
     *
     * @parameter expression="${applicationContext}" default-value="webAppConfig.xml"
     */
    private String applicationContext;


    /**
     * The WAR's 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's 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>

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

mvn myplugins:maven-spring-validation-plugin:validate
0 голосов
/ 29 ноября 2010

Вот URL сайта обновления Spring IDE (плагин Eclipse).Он делает то, что вы описали выше.Их сайт кажется недоступным.

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