2010-11-21 75 views
17

有谁知道可以用来验证Spring配置文件的Maven插件吗?通过验证,我的意思是:Maven插件来验证Spring配置?

  • 验证所有Bean引用类的构建路径
  • 上验证所有Bean引用指向一个有效的bean定义
  • 确认没有孤立豆存在
  • 其他配置错误我我确定我错过了。

我四处搜寻,并没有拿出任何东西。

Maven插件对于我的目的来说是理想的,但其他工具(Eclipse插件等)将不胜感激。

回答

9

我们对我们的项目所做的只是编写一个加载Spring配置的JUnit测试。这确实几个像你描述的事情:

  • 验证XML
  • 确保豆可以在类路径装载的类(在此不懒加载至少豆)

它不检查是否没有孤儿豆。无论如何,从代码中的任何位置考虑都没有可靠的方法,您可以直接通过ID查找bean。仅仅因为一个bean没有被其他bean引用并不意味着它没有被使用。实际上,所有的Spring配置都至少有一个不被其他bean引用的bean,因为它始终必须是根层级。

如果您有依靠真正服务,如数据库或豆类的东西,你不希望连接到这些服务在JUnit测试,你只需要抽象的配置,以允许测试值。这可以通过PropertyPlaceholderConfigurer之类的东西轻松实现,它允许您为每个环境在单独的配置文件中指定不同的属性,然后由一个bean定义文件引用它们。

EDIT(包括示例代码):
我们这样做的方式,这是至少有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文件是主要弹簧文件为您的整个应用程序。它包含一个PropertyPlaceHolder bean,以允许某些属性值在我们部署到的不同环境(测试与产品)之间配置。它导入应用程序需要运行的所有主要bean。应该在该文件中定义任何不应该用于测试的bean,如DB bean或其他与外部服务/资源通信的类。
  • beanDefinitions.xml文件包含所有正常的bean,它不依赖于外部事物。这些bean可以并将引用appContext.xml文件中定义的bean。
  • testContext.xml file是appContext的测试版本。它需要appContext.xml文件中定义的所有bean的版本,但我们使用了一个模拟库来实例化这些bean。这样,真实的类就不会被使用,并且不存在访问外部资源的风险。该文件也不需要属性占位符bean。

现在,我们有我们不怕从测试负载的测试范围内,这里是代码做...

SpringContextTest.java 包com。示例;

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上下文一切都正确的接线方式:以上或许能够被替换。从那里,你可以加载bean并像以前一样声明。

希望这会有所帮助!

+0

在死亡之旅项目中,希望能找到一个零以为AUTOMAGIC解决方案,但你说得很好。猜猜我实际上必须考虑一点点。叽。谢谢。 – Ickster 2010-12-03 04:02:34

+0

Gweebz,介意分享这个源代码?我很想在我的项目中进行此JUnit测试。 – 2011-09-07 17:06:07

+0

@Cory - 我会尽力为你找到这段代码,这是我前段时间工作的一个项目,所以我将不得不通过SVN进行查看。 – 2011-09-09 16:28:17

0

我在使用google搜索时遇到了这个问题 - 我有完全相同的问题。

我写了一个(非常未经测试的)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