簡體   English   中英

Maven插件驗證Spring配置?

[英]Maven plugin to validate Spring configuration?

有誰知道可以用來驗證Spring配置文件的Maven插件? 通過驗證,我的意思是:

  • 驗證所有bean引用構建路徑上的類
  • 驗證所有bean引用是否引用了有效的bean定義
  • 驗證沒有孤立的bean存在
  • 其他配置錯誤我敢肯定我錯過了。

我四處搜索,沒有想出任何東西。

Maven插件非常適合我的目的,但是任何其他工具(Eclipse插件等)都會受到贊賞。

我們在項目中所做的只是編寫一個加載Spring配置的JUnit測試。 這會做一些你描述的事情:

  • 驗證XML
  • 確保bean可以在類路徑上加載類(至少bean不是延遲加載的)

它不會檢查是否沒有孤兒豆。 無論如何,考慮到代碼中的任何地方,都沒有可靠的方法來執行此操作,您可以直接根據ID查找bean。 僅僅因為bean沒有被任何其他bean引用並不意味着它沒有被使用。 事實上,所有Spring配置都至少有一個bean沒有被其他bean引用,因為總是必須有層次結構的根。

如果您的bean依賴於數據庫等實際服務,並且您不希望在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文件是整個應用程序的主要spring文件。 它包含一個PropertyPlaceHolder bean,允許在我們部署到的不同環境(test vs. prod)之間配置某些屬性值。 它導入應用程序需要運行的所有主bean。 任何不應在測試中使用的bean,如DB bean,或與外部服務/資源通信的其他類,都應在此文件中定義。
  • beanDefinitions.xml文件包含所有正常的bean,它們不依賴於外部事物。 這些bean可以並將引用appContext.xml文件中定義的bean。
  • testContext.xml文件是appContext的測試版本。 它需要appContext.xml文件中定義的所有bean的版本,但我們使用模擬庫來實例化這些bean。 這樣就不會使用真正的類,也不存在訪問外部資源的風險。 此文件也不需要屬性占位符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");
    }

我相信一行將完成驗證彈簧環境正確接線所需的一切。 從那里,您可以像以前一樣加載bean並斷言。

希望這可以幫助!

這是Spring IDE更新站點 (Eclipse插件)的URL。 它完成了你上面描述的。 他們的網站似乎無法使用。

我在谷歌搜索時遇到了這個問題 - 我有完全相同的問題。

我已經編寫了一個( 非常未經測試的 )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>

安裝后,在WAR模塊的根級別運行:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM