2010-11-21 2 views
17

Spring 구성 파일의 유효성을 검사하는 데 사용할 수있는 Maven 플러그인을 아는 사람이 있습니까? 검증함으로써, 내 말은 :Maven 플러그인으로 Spring 설정을 확인 하시겠습니까?

  • 모든 콩 모든 빈 참조 더 고아 콩
  • 다른 구성 실수를 존재하지 않는 확인 유효한 빈 정의
  • 참조 확인 빌드 경로
  • 에 클래스를 참조 확인 I 내가 실종 된 것 같아.

나는 주위를 수색했지만 아무 것도 생각 나지 않았다.

Maven 플러그인은 내 용도에 이상적이지만 다른 툴 (이클립스 플러그인 등)은 만족 스러울 것입니다.

답변

9

프로젝트에서 우리가 수행하는 작업은 Spring 구성을로드하는 JUnit 테스트를 작성하는 것입니다. 이것은 당신이 같이 설명 된 것들 중 몇을 수행합니다

  • 검증
  • 콩 클래스 경로에 클래스를로드 할 수 있습니다 보장하는 XML
(-게으른로드되지 않습니다 적어도 콩에서)

고아가 없음을 확인하지 않습니다. 어쨌든 어디서나 코드를 고려하여 신뢰할 수있는 방법이 없기 때문에 ID를 사용하여 직접 콩을 조회 할 수 있습니다. bean이 다른 bean에 의해 참조되지 않는다고해서 그것이 사용되지 않는다는 것을 의미하지는 않습니다. 실제로 모든 Spring 설정은 항상 다른 bean에 의해 참조되지 않는 적어도 하나의 bean을 가질 것입니다. 왜냐하면 항상 그 bean에 대한 루트가 있어야하기 때문입니다.

에 의존하는 bean이 있고 JUnit 테스트에서 이러한 서비스에 연결하지 않으려면 테스트 값을 허용하기 위해 구성을 추상화하기 만하면됩니다. 이것은 PropertyPlaceholderConfigurer과 같은 것으로 쉽게 수행 할 수 있습니다.이 속성을 사용하면 각 환경에 대해 별도의 구성 파일에 지정된 다른 등록 정보를 갖 고 하나의 bean 정의 파일에서 참조 할 수 있습니다.

EDIT (샘플 코드를 포함하는) :
우리가하는 방법이있다/applicationContext.xml

  • 적어도 3 개 개의 다른 봄 파일 ...

    • SRC/메인/자원이 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 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> 
    

    , 내가 설명 할 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 ... applicationContext.xml 파일입니다

    • 전체 응용 프로그램의 주요 스프링 파일. PropertyPlaceHolder 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 클래스는 스프링 컨텍스트를로드 할 때 권장되는 방법입니다.

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

    내가 한 줄은 당신이 당신의 봄 컨텍스트를 확인하는 데 필요한 모든 기능이 제대로 연결되어 달성 할 것이라고 믿는다 : 수있을 위는로 대체합니다. 거기에서 콩을로드하고 이전과 같이 주장 할 수 있습니다.

    희망이 도움이됩니다.

  • +0

    하고, 제로 생각 자동으로 자료 해결책을 찾기 위해 기대했다 그러나 당신은 좋은 지적을합니다. 실제로 조금 생각해야 할 것 같네요. 불평. 감사. – Ickster

    +0

    Gweebz,이 소스 코드를 공유 하시겠습니까? 나는 내 프로젝트에서이 JUnit 테스트를하고 싶다. –

    +0

    @Cory - 나는이 코드를 찾으려고 노력할 것입니다. 제가 SVN을 살펴보아야 할 것입니다. –

    0

    여기 URL은 Spring IDE update site (이클립스 플러그인)입니다. 위에서 설명한 내용을 수행합니다. 그들의 site는 사용할 수없는 것 같습니다.

    0

    나는 인터넷 검색을 할 때이 질문을 접하게되었다. 나는 정확히 같은 질문을했다.

    나는 (매우 많은 테스트를 거친) Maven 플러그인을 작성했습니다. 현재는 WAR 만 지원하지만 쉽게 확장 할 수 있습니다. 또한이 플러그인을 만족시키기 위해 많은 속성 집합을 유지해야하는 번거 로움을 싫어하기 때문에 실제로 콩을로드하는 것은 어렵지 않습니다.

    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; 
        } 
    } 
    

    그리고 플러그인의 치어 :

    여기 그것은 과거의 어떤 사용의 경우입니다.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 
    
    관련 문제