繁体   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