现在有一个需求,要求所有web项目在上线前都得在一个properties文件里配有一个属性。假设需求是在每个web项目的WEB-INF\classes目录下的settingConfig.properties文件里需要有一个value值为login.jsp,key可以随意定义。我们的web项目有二、三十个,为避免马虎的同学没按照要求来,也为减少运维同学的工作量,我决定引入强制检查。我们在用maven,就自然的想到了enforcer这个插件。
上Maven Enforcer Plugin网站一查,果然没有直接可用的规则,那就自己写吧。
其实maven的插件机制还是挺不错的,它内部也用了一个IoC控制反转的框架,叫plexus,你自己编写的插件只要实现它提供的一个接口,外加几个注解即可搞定,这是Maven Enforcer Plugin的源码,一看就基本知道什么个意思:
@Mojo( name = "enforce", defaultPhase = LifecyclePhase.VALIDATE, threadSafe = true ) public class EnforceMojo extends AbstractMojo implements Contextualizable{ @Parameter( property = "enforcer.fail", defaultValue = "true" ) protected boolean fail = true; //省略…… }
我呢,是增加Enforcer Plugin的一个规则,这就更简单了,引用它提供的jar包,实现它提供的org.apache.maven.enforcer.rule.api.EnforcerRule接口即可。官网的例子其实已经很详细了。链接在这儿
我实现的代码在下面,首先是两个公用类:
public abstract class AbstractStandardEnforcerRule implements EnforcerRule { /** * Specify a friendly message if the rule fails. */ private String message = null; /** * 是否跳过检查 */ private boolean skip = false; //getter and setter…… } public abstract class AbstractNonCacheableEnforcerRule extends AbstractStandardEnforcerRule{ /* * @see org.apache.maven.enforcer.rule.api.EnforcerRule#getCacheId() */ public String getCacheId(){ return "0"; } /* * @see org.apache.maven.enforcer.rule.api.EnforcerRule#isCacheable() */ public boolean isCacheable(){ return false; } /* * @see org.apache.maven.enforcer.rule.api.EnforcerRule#isResultValid(org.apache.maven.enforcer.rule.api.EnforcerRule) */ public boolean isResultValid( EnforcerRule cachedRule ){ return false; } }
这个是我定义的配置Bean,maven插件把xml到java Bean的转换都做了,太方便了。
public class RequirePropertyFileBean { private String location; private String property; private String regex; //getter and setter…… }
这三个属性一看便知意思。location是要检查的properties文件的路径,property是properties文件里的key的名字,regex是value值的验证规则,是正则表达式。
虽然需求是只要求验证当前文件里是否有对应的value值,但我做的更通用一些:
location | property | regex | 解释 |
有 | 有 | 无 | 检查properties文件里是否有该key |
有 | 无 | 有 | 检查properties文件里value是否符合指定的正则 |
有 |
有 | 有 | 检查properties文件里key对应的value是否符合指定的正则 |
这是代码,也就是if、else的事了:
public class FileRequirePropertyEnforcerRule extends AbstractNonCacheableEnforcerRule { private List<RequirePropertyFileBean> files; @Override public void execute(EnforcerRuleHelper helper) throws EnforcerRuleException { Log log = helper.getLog(); if (isSkip()) { log.info("skip FileRequireProperty Enforcer Rule"); return; } if (files == null || files.size() == 0) { log.warn("FileRequireProperty EnforcerRule check files is empty"); return; } String failures = checkEnforcerRules(files); if (failures.length() > 0) { throw new EnforcerRuleException( failures.toString() ); } } private String checkEnforcerRules(List<RequirePropertyFileBean> checkedFiles){ StringBuilder failures = new StringBuilder(); for (RequirePropertyFileBean fileBean : checkedFiles) { boolean fileReadFail = false; boolean validateFail = false; Properties properties = null; if (isNotEmpty(fileBean.getLocation())) { File file = new File(fileBean.getLocation()); if(file.exists()) { try { properties = readPropertyFile(file); } catch (Exception e) { fileReadFail = true; } }else { fileReadFail = true; } } else { fileReadFail = true; } if (isNotEmpty(fileBean.getProperty()) || isNotEmpty(fileBean.getRegex())) { if (!checkFileBean(fileBean, properties)) { validateFail = true; } } else { validateFail = true; } if (fileReadFail) { failures.append(fileBean.getLocation() + " read Fail!\n"); } if(validateFail){ failures.append(fileBean.getLocation()+" not pass the enforcer rule: [property: "+fileBean.getProperty()+",regex:"+fileBean.getRegex()+"]\n"); } } return failures.toString(); } private Properties readPropertyFile(File file) throws Exception { InputStream fis = null; Properties properties=null; try { fis = new FileInputStream(file); if (fis != null) { properties = new Properties(); properties.load(fis); fis.close(); } } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { } } } return properties; } private boolean checkFileBean(RequirePropertyFileBean fileBean,Properties properties) { boolean validateFail = false; String checkedProperty = fileBean.getProperty(); String checkedRegex = fileBean.getRegex(); if (isNotEmpty(checkedProperty) && isEmpty(checkedRegex)) { if (properties.containsKey(checkedProperty)) { validateFail = true; } } else if (isEmpty(checkedProperty) && isNotEmpty(checkedRegex)) { Pattern pattern = Pattern.compile(checkedRegex); for (Object propertyValue : properties.values()) { Matcher matcher = pattern.matcher(propertyValue.toString()); if (matcher.matches()) { validateFail = true; break; } } } else if (isNotEmpty(checkedProperty) && isNotEmpty(checkedRegex)) { Pattern pattern = Pattern.compile(checkedRegex); String propertyValue = properties.getProperty(checkedProperty); if (isNotEmpty(propertyValue)) { Matcher matcher = pattern.matcher(propertyValue); if (matcher.matches()) { validateFail = true; } } } return validateFail; } private boolean isNotEmpty(String value) { return null != value && !"".equals(value); } private boolean isEmpty(String value) { return null == value || "".equals(value); } public List<RequirePropertyFileBean> getFiles() { return files; } public void setFiles(List<RequirePropertyFileBean> files) { this.files = files; } }
可以设置skip参数,在执行前会检查,如果是true,会跳过检查。
下面是我定义的pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>me.riverslob</groupId> <artifactId>enforce-rules</artifactId> <packaging>jar</packaging> <version>1.0.0-SNAPSHOT</version> <name>maven enforce rules</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <api.version>1.3.1</api.version> <maven.version>3.0.5</maven.version> </properties> <dependencies> <dependency> <groupId>org.apache.maven.enforcer</groupId> <artifactId>enforcer-api</artifactId> <version>${api.version}</version> <exclusions> <exclusion> <artifactId>plexus-container-default</artifactId> <groupId>org.codehaus.plexus</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-project</artifactId> <version>3.0-alpha-2</version> <exclusions> <exclusion> <artifactId>plexus-container-default</artifactId> <groupId>org.codehaus.plexus</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-core</artifactId> <version>${maven.version}</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-artifact</artifactId> <version>${maven.version}</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>${maven.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.6</version> <configuration> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.5.1</version> <configuration> <source>1.6</source> <target>1.6</target> <encoding>${project.build.sourceEncoding}</encoding> <showWarnings>true</showWarnings> </configuration> </plugin> </plugins> </build> </project>
使用方法就像下面这样:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>me.riverslob</groupId> <artifactId>enforce-rules</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> </dependencies> <executions> <execution> <id>enforce</id> <configuration> <rules> <requireJavaVersion> <version>[1.3,1.6]</version> </requireJavaVersion> <requireMavenVersion> <version>2.0.6</version> </requireMavenVersion> <fileRequireProperty implementation="me.riverslob.maven.enforcer.rule.FileRequirePropertyEnforcerRule"> <!--<skip>true</skip>--> <files> <file> <location>${basedir}/src/main/resources/settingConfig.properties</location> <regex>.*login\.jsp$</regex> </file> <file> <location>${basedir}/src/main/resources/settingConfig.properties</location> <regex>.*non-login2\.jsp$</regex> </file> </files> </fileRequireProperty> </rules> </configuration> <goals> <goal>enforce</goal> </goals> </execution> </executions> </plugin>
很简单吧?
现在说说debug,代码都测试差不多了,google一把,发现maven在2.0.9版本后增加了mvnDebug命令,执行命令,然后用idea开远程debug就行,就这么简单!
mvnDebug validate
然后远程debug连8000端口即可。