OneCoder


  • 首页

  • 归档

  • 标签

  • 关于

  • 搜索

教你快速写出多线程Junit单元测试用例 - GroboUtils

发表于 2012-06-13

写过Junit单元测试的同学应该会有感觉,Junit本身是不支持普通的多线程测试的,这是因为Junit的底层实现上,是用System.exit退出用例执行的。JVM都终止了,在测试线程启动的其他线程自然也无法执行。JunitCore代码如下

/**

* Run the tests contained in the classes named in the <code>args</code>.

* If all tests run successfully, exit with a status of 0. Otherwise exit with a status of 1.

* Write feedback while tests are running and write

* stack traces for all failed tests after the tests all complete.

* @param args names of classes in which to find tests to run

*/

public static void main(String... args) {

runMainAndExit(new RealSystem(), args);

}

/**

* Do not use. Testing purposes only.

* @param system

*/

public static void runMainAndExit(JUnitSystem system, String... args) {

Result result= new JUnitCore().runMain(system, args);

system.exit(result.wasSuccessful() ? 0 : 1);

}

RealSystem:

public void exit(int code) {

System.exit(code);

}

所以要想编写多线程Junit测试用例,就必须让主线程等待所有子线程执行完成后再退出。想到的办法自然是Thread中的join方法。话又说回来,这样一个简单而又典型的需求,难道会没有第三方的包支持么?通过google,笔者很快就找到了GroboUtils这个Junit多线程测试的开源的第三方的工具包。

  • GroboUtils官网如下:http://groboutils.sourceforge.net/
  • 下载页面:http://groboutils.sourceforge.net/downloads.html

Maven依赖方式:

<dependency>

<groupId>net.sourceforge.groboutils</groupId>

<artifactId>groboutils-core</artifactId>

<version>5</version>

</dependency>

注:需要第三方库支持:Repository url https://oss.sonatype.org/content/repositories/opensymphony-releases

依赖好Jar包后就可以编写多线程测试用例了。上手很简单:

/**

* 多线程测试用例

*

* @author lihzh(One Coder)

* @date 2012-6-12 下午9:18:11

* @blog http://www.coderli.com

*/

@Test

public void MultiRequestsTest() {

// 构造一个Runner

TestRunnable runner = new TestRunnable() {

@Override

public void runTest() throws Throwable {

// 测试内容

}

};

int runnerCount = 100;

//Rnner数组,想当于并发多少个。

TestRunnable[] trs = new TestRunnable[runnerCount];

for (int i = 0; i < runnerCount; i++) {

trs[i] = runner;

}

// 用于执行多线程测试用例的Runner,将前面定义的单个Runner组成的数组传入

MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(trs);

try {

// 开发并发执行数组里定义的内容

mttr.runTestRunnables();

} catch (Throwable e) {

e.printStackTrace();

}

}

执行一下,看看效果。怎么样,你的Junit也可以执行多线程测试用例了吧:)。

阅读全文 »

Maven编译报错乱码问题和编译问题解决

发表于 2012-06-11

最近需要用Maven打包工程,却不想遇到乱码问题。在Eclipse中通过Maven Plugin执行install 命令报错如下:

[ERROR] Failure executing javac, but could not parse the error:”一串乱码”

错误信息都是乱码,问题解决起来就头疼了。所以决定先解决乱码问题。经过一番搜索排查终于找到了办法:

在控制面板的,区域和语言中,将非Unicode语言改为英语美国即可。

(注:笔者系统为win7,xp听说没这个选项?如果没有,改位置的里的信息试试。同理,如果你已经是英语了但是还乱码,那就改成中文的,总之取决于你的Maven环境和你的系统语言的匹配。)

改后重启,再编译,错误信息出来了:

[ERROR] Failure executing javac,  but could not parse the error:The system cannot find the path specifie.

第一反映就是检查path里配置,用:

echo %path%

打印path里的结果,没什么问题。

猛然间,OneCoder想起,我们的Maven工程里,自定义了一个变量,用于工程编译的

org.apache.maven.plugins maven-compiler-plugin 2.3.2 true true ${JAVA_1_6_HOME}/bin/javac 1.6 1.6 1.6

这是强制大家用1.6版本JDK进行工程编译。这个变量是在各自Maven setting.xml文件中赋值的。

compiler C:/Program Files/Java/jdk1.6.0_30 

问题就在这,笔者最近升级了1.6版本的jdk到32,而这里还配置的30的路径,自己找不到了。将这里的值改为正确的路径。再次编译,成功!

阅读全文 »

Spring源码学习-含有通配符路径解析(上)

发表于 2012-06-10

继续前文《[原创] Spring源码学习-FileSystemXmlApplicationContext路径格式及解析方式》的问题。

先测试分析包含通配符(?)的。

	/**
	 * 测试包含通配符:*,?的路径
	 * <p>;D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\ap?-context.xml</p>;
	 * 通过读取配置文件失败的情况,因为此时Spring不支持\\路径的通配符解析
	 * 
	 * @author lihzh
	 * @date 2012-5-5 上午10:53:53
	 */
	@Test
	public void testAntStylePathFail() {
		String pathOne = "D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\ap?-context.xml";
		ApplicationContext appContext = new FileSystemXmlApplicationContext(pathOne);
		assertNotNull(appContext);
		VeryCommonBean bean = null;
		try {
			bean = appContext.getBean(VeryCommonBean.class);
			fail("Should not find the [VeryCommonBean].");
		} catch (NoSuchBeanDefinitionException e) {
		}
		assertNull(bean);
	}

该测试用例是可以正常通过测试,也就是是找不到该Bean的。这又是为什么? Spring不是支持通配符吗?FileSystemXmlApplicationContext的注释里也提到了通配符的情况:

* <p>;The config location defaults can be overridden via {@link #getConfigLocations},
 * Config locations can either denote concrete files like "/myfiles/context.xml"
 * or Ant-style patterns like "/myfiles/*-context.xml" (see the
 * {@link org.springframework.util.AntPathMatcher} javadoc for pattern details).

从代码中寻找答案。回到上回的else分支中,因为包含通配符,所以进入第一个子分支。

	/**
	 * Find all resources that match the given location pattern via the
	 * Ant-style PathMatcher. Supports resources in jar files and zip files
	 * and in the file system.
	 * @param locationPattern the location pattern to match
	 * @return the result as Resource array
	 * @throws IOException in case of I/O errors
	 * @see #doFindPathMatchingJarResources
	 * @see #doFindPathMatchingFileResources
	 * @see org.springframework.util.PathMatcher
	 */
	protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		String rootDirPath = determineRootDir(locationPattern);
		String subPattern = locationPattern.substring(rootDirPath.length());
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource>; result = new LinkedHashSet<Resource>;(16);
		for (Resource rootDirResource : rootDirResources) {
			rootDirResource = resolveRootDirResource(rootDirResource);
			if (isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
			}
			else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
			}
			else {
				result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
			}
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
		}
		return result.toArray(new Resource[result.size()]);
	}

此方法传入的完整的没有处理的路径,从第一行开始,就开始分步处理解析传入的路径,首先是决定根路径: determineRootDir(locationPattern)

    /**
	 * Determine the root directory for the given location.
	 * <p>;Used for determining the starting point for file matching,
	 * resolving the root directory location to a <code>;java.io.File</code>;
	 * and passing it into <code>;retrieveMatchingFiles</code>;, with the
	 * remainder of the location as pattern.
	 * <p>;Will return "/WEB-INF/" for the pattern "/WEB-INF/*.xml",
	 * for example.
	 * @param location the location to check
	 * @return the part of the location that denotes the root directory
	 * @see #retrieveMatchingFiles
	 */
	protected String determineRootDir(String location) {
		int prefixEnd = location.indexOf(":") + 1;
		int rootDirEnd = location.length();
		while (rootDirEnd >; prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
			rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
		}
		if (rootDirEnd == 0) {
			rootDirEnd = prefixEnd;
		}
		return location.substring(0, rootDirEnd);
	}

这个”根”,就是不包含通配符的最长的部分,以我们的路径为例,这个”根”本来应该是: D:\workspace-home\spring-custom\src\main\resources\spring\,但是实际上,从determineRootDir的实现可以看出,

首先,先找到冒号’:’索引位,赋值给 prefixEnd。

然后,在从冒号开始到最后的字符串中,循环判断是否包含通配符,如果包含,则截断最后一个由”/”分割的部分,例如:在我们路径中,就是最后的ap?-context.xml这一段。再循环判断剩下的部分,直到剩下的路径中都不包含通配符。

如果查找完成后,rootDirEnd=0了,则将之前赋值的prefixEnd的值赋给rootDirEnd,也就是”:”所在的索引位。

最后,将字符串从开始截断rootDirEnd。

我们的问题,就出在关键的第二步,Spring这里只在字符串中查找”/”,并没有支持”\\“这样的路径分割方式,所以,自然找不到”\\“,rootDirEnd = -1 + 1 = 0。所以循环后,阶段出来的路径就是D:自然Spring会找不到配置文件,容器无法初始化

基于以上分析,我们将路径修改为:D:/workspace-home/spring-custom/src/main/resources/spring/ap?-context.xml,再测试如下:

   /**
	 * 测试包含通配符:*,?的路径
	 * <p>;D:/workspace-home/spring-custom/src/main/resources/spring/ap?-context.xml</p>;
	 * 通过读取配置文件
	 * 
	 * @author lihzh
	 * @date 2012-5-5 上午10:53:53
	 */
	@Test
	public void testAntStylePath() {
		String pathOne = "D:/workspace-home/spring-custom/src/main/resources/spring/ap?-context.xml";
		ApplicationContext appContext = new FileSystemXmlApplicationContext(pathOne);
		assertNotNull(appContext);
		VeryCommonBean bean = appContext.getBean(VeryCommonBean.class);
		assertNotNull(bean);
		assertEquals("verycommonbean-name", bean.getName());
	}

测试通过。

刚才仅仅分析了,我们之前路径的问题所在,还有一点我想也是大家关心的,就是通配符是怎么匹配的呢?那我们就继续分析源码,回到 findPathMatchingResources方法。

将路径分成包含通配符和不包含的两部分后,Spring会将根路径生成一个Resource,用的还是getResources方法。然后检查根路径的类型,是否是Jar路径?是否是VFS路径?对于我们这种普通路径,自然走到最后的分支。

	/**
	 * Find all resources in the file system that match the given location pattern
	 * via the Ant-style PathMatcher.
	 * @param rootDirResource the root directory as Resource
	 * @param subPattern the sub pattern to match (below the root directory)
	 * @return the Set of matching Resource instances
	 * @throws IOException in case of I/O errors
	 * @see #retrieveMatchingFiles
	 * @see org.springframework.util.PathMatcher
	 */
	protected Set<Resource>; doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
			throws IOException {

		File rootDir;
		try {
			rootDir = rootDirResource.getFile().getAbsoluteFile();
		}
		catch (IOException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Cannot search for matching files underneath " + rootDirResource +
						" because it does not correspond to a directory in the file system", ex);
			}
			return Collections.emptySet();
		}
		return doFindMatchingFileSystemResources(rootDir, subPattern);
	}
   /**
	 * Find all resources in the file system that match the given location pattern
	 * via the Ant-style PathMatcher.
	 * @param rootDir the root directory in the file system
	 * @param subPattern the sub pattern to match (below the root directory)
	 * @return the Set of matching Resource instances
	 * @throws IOException in case of I/O errors
	 * @see #retrieveMatchingFiles
	 * @see org.springframework.util.PathMatcher
	 */
	protected Set<Resource>; doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
		}
		Set<File>; matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
		Set<Resource>; result = new LinkedHashSet<Resource>;(matchingFiles.size());
		for (File file : matchingFiles) {
			result.add(new FileSystemResource(file));
		}
		return result;
	}
   /**
	 * Retrieve files that match the given path pattern,
	 * checking the given directory and its subdirectories.
	 * @param rootDir the directory to start from
	 * @param pattern the pattern to match against,
	 * relative to the root directory
	 * @return the Set of matching File instances
	 * @throws IOException if directory contents could not be retrieved
	 */
	protected Set<File>; retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
		if (!rootDir.exists()) {
			// Silently skip non-existing directories.
			if (logger.isDebugEnabled()) {
				logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
			}
			return Collections.emptySet();
		}
		if (!rootDir.isDirectory()) {
			// Complain louder if it exists but is no directory.
			if (logger.isWarnEnabled()) {
				logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
			}
			return Collections.emptySet();
		}
		if (!rootDir.canRead()) {
			if (logger.isWarnEnabled()) {
				logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
						"] because the application is not allowed to read the directory");
			}
			return Collections.emptySet();
		}
		String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
		if (!pattern.startsWith("/")) {
			fullPattern += "/";
		}
		fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
		Set<File>; result = new LinkedHashSet<File>;(8);
		doRetrieveMatchingFiles(fullPattern, rootDir, result);
		return result;
	}
   /**
	 * Recursively retrieve files that match the given pattern,
	 * adding them to the given result list.
	 * @param fullPattern the pattern to match against,
	 * with prepended root directory path
	 * @param dir the current directory
	 * @param result the Set of matching File instances to add to
	 * @throws IOException if directory contents could not be retrieved
	 */
	protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File>; result) throws IOException {
		if (logger.isDebugEnabled()) {
			logger.debug("Searching directory [" + dir.getAbsolutePath() +
					"] for files matching pattern [" + fullPattern + "]");
		}
		File[] dirContents = dir.listFiles();
		if (dirContents == null) {
			if (logger.isWarnEnabled()) {
				logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
			}
			return;
		}
		for (File content : dirContents) {
			String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
			if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
				if (!content.canRead()) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
								"] because the application is not allowed to read the directory");
					}
				}
				else {
					doRetrieveMatchingFiles(fullPattern, content, result);
				}
			}
			if (getPathMatcher().match(fullPattern, currPath)) {
				result.add(content);
			}
		}
	}

主要的匹配工作,是从doRetrieveMatchingFiles方法开始的。前面的都是简单的封装过渡,在retrieveMatchingFiles中判断了下根路径是否存在、是否是文件夹、是否可读。否则都直接返回空集合。都满足了以后才进入,doRetrieveMatchingFiles方法。在该方法中

  • 首先,列出该文件夹下的所有文件。
  • 然后,遍历所有文件,如果仍是文件夹,递归调用doRetrieveMatchingFiles方法。如果不是,则调用getPathMatcher().match(fullPattern, currPath)进行文件名的最后匹配,将满足条件放入结果集。该match方法,实际是调用了AntPathMatcher的doMatch方法,
   /**
	 * Actually match the given <code>;path</code>; against the given <code>;pattern</code>;.
	 * @param pattern the pattern to match against
	 * @param path the path String to test
	 * @param fullMatch whether a full pattern match is required (else a pattern match
	 * as far as the given base path goes is sufficient)
	 * @return <code>;true</code>; if the supplied <code>;path</code>; matched, <code>;false</code>; if it didn't
	 */
	protected boolean doMatch(String pattern, String path, boolean fullMatch,
			Map<String, String>; uriTemplateVariables) {

		if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
			return false;
		}

		String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
		String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);

		int pattIdxStart = 0;
		int pattIdxEnd = pattDirs.length - 1;
		int pathIdxStart = 0;
		int pathIdxEnd = pathDirs.length - 1;

		// Match all elements up to the first **
		while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			String patDir = pattDirs[pattIdxStart];
			if ("**".equals(patDir)) {
				break;
			}
			if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
				return false;
			}
			pattIdxStart++;
			pathIdxStart++;
		}

		if (pathIdxStart >; pathIdxEnd) {
			// Path is exhausted, only match if rest of pattern is * or **'s
			if (pattIdxStart >; pattIdxEnd) {
				return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
						!path.endsWith(this.pathSeparator));
			}
			if (!fullMatch) {
				return true;
			}
			if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
				return true;
			}
			for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
				if (!pattDirs[i].equals("**")) {
					return false;
				}
			}
			return true;
		}
		else if (pattIdxStart >; pattIdxEnd) {
			// String not exhausted, but pattern is. Failure.
			return false;
		}
		else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
			// Path start definitely matches due to "**" part in pattern.
			return true;
		}

		// up to last '**'
		while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			String patDir = pattDirs[pattIdxEnd];
			if (patDir.equals("**")) {
				break;
			}
			if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
				return false;
			}
			pattIdxEnd--;
			pathIdxEnd--;
		}
		if (pathIdxStart >; pathIdxEnd) {
			// String is exhausted
			for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
				if (!pattDirs[i].equals("**")) {
					return false;
				}
			}
			return true;
		}

		while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			int patIdxTmp = -1;
			for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
				if (pattDirs[i].equals("**")) {
					patIdxTmp = i;
					break;
				}
			}
			if (patIdxTmp == pattIdxStart + 1) {
				// '**/**' situation, so skip one
				pattIdxStart++;
				continue;
			}
			// Find the pattern between padIdxStart & padIdxTmp in str between
			// strIdxStart & strIdxEnd
			int patLength = (patIdxTmp - pattIdxStart - 1);
			int strLength = (pathIdxEnd - pathIdxStart + 1);
			int foundIdx = -1;

			strLoop:
			for (int i = 0; i <= strLength - patLength; i++) {
				for (int j = 0; j < patLength; j++) {
					String subPat = pattDirs[pattIdxStart + j + 1];
					String subStr = pathDirs[pathIdxStart + i + j];
					if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
						continue strLoop;
					}
				}
				foundIdx = pathIdxStart + i;
				break;
			}

			if (foundIdx == -1) {
				return false;
			}

			pattIdxStart = patIdxTmp;
			pathIdxStart = foundIdx + patLength;
		}

		for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
			if (!pattDirs[i].equals("**")) {
				return false;
			}
		}

		return true;
	}

比较方法如下

  • 首先,分别将输入路径和待比较路径,按照文件分隔符分割成字符串数组。(例如:{”D:”, “workspace-home”, “spring-custom”…})
  • 然后,设置好起始和结束位后,对这两个数组进行while循环(代码中第一个while循环),逐断比较匹配(matchStrings)情况。如果有一段不满足则返回fasle。

由于我们当前的测试路径中不包含的部分,所以主要的判断基本都在第一个while就可以搞定。这部分工作自然是由matchStrings**完成的。

如果让你完成一个通配符路径匹配的功能,你会如何去做?是否自然的联想到了正则?似乎是个好选择,看看spring是怎么做的。

private boolean matchStrings(String pattern, String str, Map<String, String>; uriTemplateVariables) {
		AntPathStringMatcher matcher = new AntPathStringMatcher(pattern, str, uriTemplateVariables);
		return matcher.matchStrings();
	}

在构造AntPathStringMatcher实例的时候,spring果然也创建了正则:

		AntPathStringMatcher(String pattern, String str, Map<String, String>; uriTemplateVariables) {
		this.str = str;
		this.uriTemplateVariables = uriTemplateVariables;
		this.pattern = createPattern(pattern);
	}

private Pattern createPattern(String pattern) {
		StringBuilder patternBuilder = new StringBuilder();
		Matcher m = GLOB_PATTERN.matcher(pattern);
		int end = 0;
		while (m.find()) {
			patternBuilder.append(quote(pattern, end, m.start()));
			String match = m.group();
			if ("?".equals(match)) {
				patternBuilder.append('.');
			}
			else if ("*".equals(match)) {
				patternBuilder.append(".*");
			}
			else if (match.startsWith("{") && match.endsWith("}")) {
				int colonIdx = match.indexOf(':');
				if (colonIdx == -1) {
					patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
					variableNames.add(m.group(1));
				}
				else {
					String variablePattern = match.substring(colonIdx + 1, match.length() - 1);
					patternBuilder.append('(');
					patternBuilder.append(variablePattern);
					patternBuilder.append(')');
					String variableName = match.substring(1, colonIdx);
					variableNames.add(variableName);
				}
			}
			end = m.end();
		}
		patternBuilder.append(quote(pattern, end, pattern.length()));
		return Pattern.compile(patternBuilder.toString());
	}

简单说,就是spring先用正则:

private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");

找到路径中的”?”和”*“通配符,然后转换为Java正则的任意字符”.”和”.*“。生成另一个正则表达式去匹配查找到的文件的路径。如果匹配则返回true。

至此,对于路径中包含?和*的情况解析spring的解析方式,我们已经基本了解了。本来想把**的情况一起介绍了,不过考虑的篇幅过长,我们下次再一起研究吧。

写在最后:所有研究均为笔者工作之余消遣之做,错误指出还望指出,欢迎各种形势的探讨。

阅读全文 »

Spring源码学习-FileSystemXmlApplicationContext路径格式及解析方式

发表于 2012-06-09

了解完了FileSystemXmlApplicationContext构造函数,我们来看看路径解析的问题。

  • 支持路径格式的研究。(绝对?相对?通配符?classpath格式又如何?
  • 路径如何解析?

下面,我们就来一一验证和解答。先放出本次测试用的配置文件(app-context.xml和test.properties):

<bean id="placeHolderConfig"
 class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" /> 
 <property name="locations">
 <list>
 <value>classpath*:spring/test.properties</value>
 </list>
 </property>
 </bean>
 <bean id="veryCommonBean" class="kubi.coder.bean.VeryCommonBean">
 <property name="name" value="${test.name}"></property>
 </bean>

test.properties

test.name=verycommonbean-name

首先想到的自然是最普通的绝对路径:

/**
  * 测试通过普通的绝对路径:
  * <p>D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml</p>
  * 读取配置文件
  * 
  * @author lihzh
  * @date 2012-5-5 上午10:53:53
  */
 @Test
 public void testPlainAbsolutePath() {
 String path = "D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml";
 ApplicationContext appContext = new FileSystemXmlApplicationContext(path);
 assertNotNull(appContext);
 VeryCommonBean bean = appContext.getBean(VeryCommonBean.class);
 assertNotNull(bean);
 assertEquals("verycommonbean-name", bean.getName());
 }

测试通过,我们来看下Spring是怎么找到该文件的。之前已经说过refresh这个函数,是Spring生命周期的开始,我们就以它为入口,顺藤摸瓜,时序图如下

最终,我们找到解析路径的关键方法,PathMatchingResourcePatternResolver的getResources方法和DefaultResourceLoader中的getResource方法:

public Resource[] getResources(String locationPattern) throws IOException {
 Assert.notNull(locationPattern, "Location pattern must not be null");
 if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
 // a class path resource (multiple resources for same name possible)
 if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
 // a class path resource pattern
 return findPathMatchingResources(locationPattern);
 }
 else {
 // all class path resources with the given name
 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
 }
 }
 else {
 // Only look for a pattern after a prefix here
 // (to not get fooled by a pattern symbol in a strange prefix).
 int prefixEnd = locationPattern.indexOf(":") + 1;
 if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
 // a file pattern
 return findPathMatchingResources(locationPattern);
 }
 else {
 // a single resource with the given name
 return new Resource[] {getResourceLoader().getResource(locationPattern)};
 }
 }
 }
public Resource getResource(String location) {
 Assert.notNull(location, "Location must not be null");
 if (location.startsWith(CLASSPATH_URL_PREFIX)) {
 return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
 }
 else {
 try {
 // Try to parse the location as a URL...
 URL url = new URL(location);
 return new UrlResource(url);
 }
 catch (MalformedURLException ex) {
 // No URL -> resolve as resource path.
 return getResourceByPath(location);
 }
 }
 }

其中常量

CLASSPATH_ALL_URL_PREFIX = "classpath*:";
CLASSPATH_URL_PREFIX = "classpath:";

我们输入的路径是绝对路径:”D:\workspace-home\spring-custom\src\main\resources\spring\app-context.xml”。不是以classpath*开头的,所以会落入else之中。在else中:getPathMatcher().isPattern(),实际是调用AntPathMatcher中的isPattern()方法:

public boolean isPattern(String path) {
		return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
	}

是用来判断”:”以后的路径中是否包含通配符”*“或者 “?”。我们的路径显然也不包含,所以最终会直接走入getResource方法。

路径仍然既不是以classpath开头的,也不是URL格式的路径,所以最终会落入getResourceByPath(location)这个分支,而我们之前介绍过,这个方法恰好是在FileSystemXmlApplicationContext这个类中复写过的

protected Resource getResourceByPath(String path) {
 if (path != null && path.startsWith("/")) {
 path = path.substring(1);
 }
 return new FileSystemResource(path);
 }

我们给的路径不是以”/”开头,所以直接构造了一个FileSystemResource

public FileSystemResource(String path) {
 Assert.notNull(path, "Path must not be null");
 this.file = new File(path);
 this.path = StringUtils.cleanPath(path);
 }

即用路径直接构造了一个File。这里StringUtil.cleanPath方法主要是将传入的路径规范化,比如将windows的路径分隔”\“替换为标准的”/”,如果路径中含有”.”(当前文件夹),或者”..”(上层文件夹),则计算出其真实路径。而File本身是支持这样的路径的,也就是说,spring可以支持这样的路径。出于好奇,我们也针对这个方法测试如下:

/**
  * 测试通过含有.或者..的绝对路径
  * <p>D:\\workspace-home\\spring-custom\\.\\src\\main\\resources\\spring\\..\\spring\\app-context.xml</p>
  * 读取配置文件
  * 
  * @author lihzh
  * @date 2012-5-5 上午10:53:53
  */
 @Test
 public void testContainDotAbsolutePath() {
 String path = "D:\\workspace-home\\spring-custom\\.\\src\\main\\resources\\spring\\..\\spring\\app-context.xml";
 ApplicationContext appContext = new FileSystemXmlApplicationContext(path);
 assertNotNull(appContext);
 VeryCommonBean bean = appContext.getBean(VeryCommonBean.class);
 assertNotNull(bean);
 assertEquals("verycommonbean-name", bean.getName());
 }

容器可以正常初始化。路径计算正确。

补充说明:Spring最终读取配置文件,是通过InputStream加载的,Spring中的各种Resource的最上层接口InputStreamResource中定义了唯一的一个方法getInputStream。也就是说,只要保证各Resource的实现类的getInputStream方法能够正常获取流,Spring容器即可解析初始化。

对于FileSystemResource而言,其实现如下:

/**
  * This implementation opens a FileInputStream for the underlying file.
  * @see java.io.FileInputStream
  */
 public InputStream getInputStream() throws IOException {
 return new FileInputStream(this.file);
 }

所以,我们说,此时只有是File正常支持的格式,Spring才能正常初始化。

继续回到前面的话题。我们目前只验证else分支中的catch分支。根据代码分析,即使是FileSystemXmlApplicationContext也可以支持Classpath格式的路径和URL格式的路径的。验证如下

/**
  * 测试通过含有.或者..的绝对路径
  * <p>file:/D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml</p>
  * 读取配置文件
  * 
  * @author lihzh
  * @date 2012-5-5 上午10:53:53
  */
 @Test
 public void testURLAbsolutePath() {
 String path = "file:/D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml";
 ApplicationContext appContext = new FileSystemXmlApplicationContext(path);
 assertNotNull(appContext);
 VeryCommonBean bean = appContext.getBean(VeryCommonBean.class);
 assertNotNull(bean);
 assertEquals("verycommonbean-name", bean.getName());
 }
 
 /**
  * 测试通过Classpath类型的路径
  * <p>classpath:spring/app-context.xml</p>
  * 通过读取配置文件
  * 
  * @author lihzh
  * @date 2012-5-5 上午10:53:53
  */
 @Test
 public void testClassPathStylePath() {
 String path = "classpath:spring/app-context.xml";
 ApplicationContext appContext = new FileSystemXmlApplicationContext(path);
 assertNotNull(appContext);
 VeryCommonBean bean = appContext.getBean(VeryCommonBean.class);
 assertNotNull(bean);
 assertEquals("verycommonbean-name", bean.getName());
 }

验证通过,并且通过debug确认,确实走入了相应的分支,分别构造了UrlResource和ClassPathResource实例。所以,之后Spring会分别调用这个两个Resource中的getInputStream方法获取流,解析配置文件。附上这两个类中的getInputStream方法,有兴趣的可以继续研究。

/**
  * This implementation opens an InputStream for the given URL.
  * It sets the "UseCaches" flag to <code>false</code>,
  * mainly to avoid jar file locking on Windows.
  * @see java.net.URL#openConnection()
  * @see java.net.URLConnection#setUseCaches(boolean)
  * @see java.net.URLConnection#getInputStream()
  */
 public InputStream getInputStream() throws IOException {
 URLConnection con = this.url.openConnection();
 ResourceUtils.useCachesIfNecessary(con);
 try {
 return con.getInputStream();
 }
 catch (IOException ex) {
 // Close the HTTP connection (if applicable).
 if (con instanceof HttpURLConnection) {
 ((HttpURLConnection) con).disconnect();
 }
 throw ex;
 }
 }

 /**
  * This implementation opens an InputStream for the given class path resource.
  * @see java.lang.ClassLoader#getResourceAsStream(String)
  * @see java.lang.Class#getResourceAsStream(String)
  */
 public InputStream getInputStream() throws IOException {
 InputStream is;
 if (this.clazz != null) {
 is = this.clazz.getResourceAsStream(this.path);
 }
 else {
 is = this.classLoader.getResourceAsStream(this.path);
 }
 if (is == null) {
 throw new FileNotFoundException(
 getDescription() + " cannot be opened because it does not exist");
 }
 return is;
 }

上述两个实现所属的类,我想应该一目了然吧~~

至此,我们算是分析验证通过了一个小分支下的支持的路径的情况,其实,这只是这些都是最简单直接的。回想刚才的分析,如果路径包含通配符(?,*)spring是怎么处理的?如果是以classpath*开头的又是如何呢?我们下回分解……

阅读全文 »

Spring研究-容器初始化之FileSystemXmlApplicationContext构造函数

发表于 2012-06-07

宅男Coder,没有其他爱好,闲暇之余抱着瞻仰的心态去阅读一下Spring的源码,期许能收获一支半解。要学习Spring的源码,第一步自然是下载和编译Spring的源码,这个我在之前的博文中已经发表过了。具体可参考:《SpringFramework源码下载和编译教程》

面对茫茫多的Spring的工程和代码,很多人可能会无从下手。其实想想,Spring也是有入口的,那就是配置文件的加载。Spring容器的构建完全是基于配置文件的配置的。不论是Web工程,还是普通的Java应用,加载Spring配置文件都是首要的工作。所以,我就从配置文件的加载学起。

要加载配置文件,首先当然是要找到该文件。大多数人通常都是在Web应用中使用Spring。网上搜搜配置,配置文件的名字就叫约定的:applicationContext.xml,然后往编译路径下一扔,Spring自然就好用了,就没过多的关注过其他容器初始化的问题。其实,一个自然应该想到的问题就是:一个普通的J2SE应用该如何使用Spring呢?答案很简单:new 出一个ApplicationContext的实例就好了。例如:

	private static final String SPRINT_FILEPATH_CONTEXT = "D:\\workspace-home\\OpenSourceStudy\\src\\main\\resources\\spring\\app-context.xml";          
	ApplicationContext  appContext = new FileSystemXmlApplicationContext(
				SPRINT_FILEPATH_CONTEXT);

只需上述一行代码,一个基于指定的配置文件的Spring容器就初始化完成了。

下面,我们来仔细看看FileSystemXmlApplicationContext这个类:

	public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
	/**
	 * Create a new FileSystemXmlApplicationContext for bean-style configuration.
	 * @see #setConfigLocation
	 * @see #setConfigLocations
	 * @see #afterPropertiesSet()
	 */
	public FileSystemXmlApplicationContext() {
	}

	/**
	 * Create a new FileSystemXmlApplicationContext for bean-style configuration.
	 * @param parent the parent context
	 * @see #setConfigLocation
	 * @see #setConfigLocations
	 * @see #afterPropertiesSet()
	 */
	public FileSystemXmlApplicationContext(ApplicationContext parent) {
		super(parent);
	}

	/**
	 * Create a new FileSystemXmlApplicationContext, loading the definitions
	 * from the given XML file and automatically refreshing the context.
	 * @param configLocation file path
	 * @throws BeansException if context creation failed
	 */
	public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

	/**
	 * Create a new FileSystemXmlApplicationContext, loading the definitions
	 * from the given XML files and automatically refreshing the context.
	 * @param configLocations array of file paths
	 * @throws BeansException if context creation failed
	 */
	public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
		this(configLocations, true, null);
	}

	/**
	 * Create a new FileSystemXmlApplicationContext with the given parent,
	 * loading the definitions from the given XML files and automatically
	 * refreshing the context.
	 * @param configLocations array of file paths
	 * @param parent the parent context
	 * @throws BeansException if context creation failed
	 */
	public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
		this(configLocations, true, parent);
	}

	/**
	 * Create a new FileSystemXmlApplicationContext, loading the definitions
	 * from the given XML files.
	 * @param configLocations array of file paths
	 * @param refresh whether to automatically refresh the context,
	 * loading all bean definitions and creating all singletons.
	 * Alternatively, call refresh manually after further configuring the context.
	 * @throws BeansException if context creation failed
	 * @see #refresh()
	 */
	public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
		this(configLocations, refresh, null);
	}

	/**
	 * Create a new FileSystemXmlApplicationContext with the given parent,
	 * loading the definitions from the given XML files.
	 * @param configLocations array of file paths
	 * @param refresh whether to automatically refresh the context,
	 * loading all bean definitions and creating all singletons.
	 * Alternatively, call refresh manually after further configuring the context.
	 * @param parent the parent context
	 * @throws BeansException if context creation failed
	 * @see #refresh()
	 */
	public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}


	/**
	 * Resolve resource paths as file system paths.
	 * <p>Note: Even if a given path starts with a slash, it will get
	 * interpreted as relative to the current VM working directory.
	 * This is consistent with the semantics in a Servlet container.
	 * @param path path to the resource
	 * @return Resource handle
	 * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
	 */
	@Override
	protected Resource getResourceByPath(String path) {
		if (path != null &amp;&amp; path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemResource(path);
	}

}

一共七个构造函数和一个复写的方法。我们现在重点关注构造函数,除前两个之外,其他的构造函数都最终指向构造函数

	public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}

只是对没有传入的参数,给了一个默认值。

该构造函数有三个参数:

  • String[] configLocations - 配置文件的路径数组。是数组也就是说,支持同时传入多个配置文件路径。
  • boolean refresh - 是否刷新,如果是true,则会开始初始化Spring容器, false则暂时不初始化Spring容器。
  • ApplicationContext parent - Spring容器上下文。即可以传入已经初始化过的Spring容器,新初始化的容器会包含parent上下文中的内容,例如:父容器中定义的bean等。

refresh()方法可谓Spring的核心的入口函数,Spring容器的初始化正是由此开始。一些Spring学习的书中,也向学习Spring的读者推荐,如果感到无所适从,可从该方法入手,研究Spring的整个生命周期。后续我们也会从该方法入手重点研究。现在只需理解,其为Spring容器初始化的一个“开关”即可。

前两个构造函数与该构造函数最大的区别就是,没有调用refresh函数。也就是说,Spring容器,此时并未初始化。此时如果用getBean方法去获取Bean的实例,会报容器并未初始化的异常。

下面给出一些关于构造函数的测试用例,能更直观、具体的说明问题

	private String filePath = "D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml";
	private String parentFilePath = "D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\parent-context.xml";

	/**
	 * 直接指定一个单一的配置文件初始化Spring容器。容器中包含该指定配置文件中定义的Bean{@code VeryCommonBean}。
	 * 
	 * @author lihzh
	 * @date 2012-4-29 下午9:39:27
	 */
	@Test
	public void testFileContextWithPath() {
		ApplicationContext appContext = new FileSystemXmlApplicationContext(
				filePath);
		assertNotNull("The app context should not be null.", appContext);
		assertNotNull(appContext.getBean(VeryCommonBean.class));
	}

	/**
	 * 将已经初始化好的Spring容器上下文传递给新的Spring容器。需手动调用容器的刷新
	 * {@code ConfigurableApplicationContext#refresh()}(初始化)。
	 * 
	 * @author lihzh
	 * @date 2012-5-4 下午9:28:40
	 */
	@Test
	public void testFileContextWithParentOnly() {
		ApplicationContext parentContext = new FileSystemXmlApplicationContext(
				parentFilePath);
		ConfigurableApplicationContext appContext = new FileSystemXmlApplicationContext(
				parentContext);
		assertNotNull("The parent context should not be null.", parentContext);
		assertNotNull(appContext);
		// 需要手动刷新(初始化)容器
		appContext.refresh();
		assertNotNull(parentContext.getBean(ParentBean.class));
		assertNotNull(appContext.getBean(ParentBean.class));
	}

	/**
	 * 根据指定的配置文件和已经初始化好的Parent容器上下文,实例化一个新的Spring容器,该容器中包含这两部分的信息。
	 * 
	 * @author lihzh
	 * @date 2012-5-4 下午10:07:20
	 */
	@Test
	public void testFileContextWithParentAndItself() {
		ApplicationContext parentContext = new FileSystemXmlApplicationContext(
				parentFilePath);
		ApplicationContext appContext = new FileSystemXmlApplicationContext(
				new String[] { filePath }, parentContext);
		assertNotNull("The parent context should not be null", parentContext);
		assertNotNull(appContext);
		assertTrue("Should not have bean:[VeryCommonBean] in parent context.",
				!parentContext.containsBean("VeryCommonBean"));
		assertNotNull(parentContext.getBean(ParentBean.class));
		assertNotNull(appContext.getBean(ParentBean.class));
		assertNotNull("Should have bean:[VeryCommonBean].",
				appContext.getBean("VeryCommonBean"));
	}

	/**
	 * 同时指定多个配置文件来初始化Spring容器
	 * 
	 * @author lihzh
	 * @date 2012-5-4 下午10:09:02
	 */
	@Test
	public void testFileContextWithMultiPath() {
		ApplicationContext appContext = new FileSystemXmlApplicationContext(
				parentFilePath, filePath);
		assertNotNull(appContext);
		assertNotNull(appContext.getBean(ParentBean.class));
		assertNotNull("Should have bean:[VeryCommonBean].",
				appContext.getBean(VeryCommonBean.class));
	}

了解这些有什么用?

  • 学会在一个普通的J2SE应用中使用Spring,初始化Spring容器。
  • 了解初始化Spring容器的几种方式和传入的参数,可配合不同的场景使用。例如:在程序运行期动态初始化一个新的Spring容器,包含已经初始化的容器,即可用与parent相关的构造函数。再例如:在可能的特定的场景下,Spring容器需要在特定的实际初始化,可先调用空构造函数,再传入location,再手动refresh。或者直接给refresh传入false,再手动refresh。再比如:配合Spring的foo包,可监控Spring配置文件的变化,利用refresh函数,实时更新Spring容器等等。
阅读全文 »

Hibernate4.0源码下载和编译教程

发表于 2012-06-06

废话不多说,直接进入主题。首先是下载源码,跟spring一样,hibernate也采用git管理。

  • git://github.com/hibernate/hibernate-orm.git

下载后,hiberante同样也是采用了gradle进行编译,所以同样同之前说的spring的编译, 参考阅读:

  • 《SpringFramework3 源码下载和编译教程》

在源码的根目录执行:

gradlew.bat assemble 

可跳过测试,否则执行gradlew.bat build的过程中,遇到test出错会报错退出。

本以为会顺利结束,没想到又报错了。是符号无法识别转换的错误。在网上搜索了一番,这个跟系统的区域设置有关。需要改为:英语(英国)。我试了英语(美国)居然还是报错。。。

PS:区域设置在控制面板中。

再此编译,终于成功。 最后,一样执行

gradlew.bat eclipse 

生成eclipse工程,导入,ok大功告成!

PS:不知道为什么hiberante编译出来的core和test工程会有循环依赖。我手动取消了core对test工程依赖,改为引用了build出来的test工程的jar包,解决问题。不知道大家是否遇到这样的问题。

阅读全文 »

开源Java高性能NIO框架推荐 - Netty

发表于 2012-06-05

作为一个Java程序员,多方面的知识储备是相当重要的。面对各种的情况,都有自己的解决方案也是一种能力的体现。Java优秀的开源项目很多,除了传统的SSH,作为一个Java程序员,你应该知道的更多:)

Netty简介

Netty 是一个异步的,事件驱动的网络编程框架和工具,使用Netty可以快速开发出可维护的,高性能、高扩展能力的协议服务及其客户端应用。

Netty原来属于JBoss开源社区,现在已经独立出来,新官网地址:

  • http://netty.io

最新版本为:3.5.0.Beta1和3.4.6.Final,Lisence为:ASL。

  • Github地址:https://github.com/netty/netty/

如果您需要开发分布式应用,Netty可作为通信框架的一种选择。笔者会在后续的研究中,给出Netty上手的例子,供大家参考。

同类产品:</div>

  • Apache Mina: http://mina.apache.org/
阅读全文 »

Eclipse使用技巧 - 自定义JavaDoc注解和代码模版,提升开发效率和规范性

发表于 2012-06-02

项目中对于注释和代码规范的要求往往是毕比较严格的,如果全靠手动完成效率低,还难以保证保证的规范。幸好Eclipse给我们提供了自定义代码模版的功能。

先说一下Java代码注释模版,它是指这里的配置:

是不是跟你的不一样,多了@author和@date。恩,这是我自定义过的注释模版。效果是在给方法用/*注释内容/,注释的时候,会生成如下形式的代码:

${tags}是生成@param,@return这些结果。其余的应该不用我多说了。

定义这样的模版很简单,在刚才的位置,点Edit,按照如下的输入即可:

这就够了?当然不是,如果在之前的方法忘记了@date时间注释,要怎么补上?直接用@ + 代码辅助?是不是找不到@date 标签?呵呵,当然,这个@date其实是我自定义的。定义的位置在这里:

赶紧New一个 @date标签吧。

注:Pattern中 @date字符为手动填写。后面两个变量为Eclipse内部提供的。

保存看看效果?

@date 出现了吧

是不是还想扩展Insert Variable里的内容?笔者也研究了一番,找到了这个

  • 《Eclipse使用技巧 – 自定义注释模板变量》

不过考虑到操作性价比,笔者并未尝试,有兴趣的朋友可以研究一下,欢迎交流。

阅读全文 »

Eclipse使用技巧 - 自定义注释模板变量

发表于 2012-06-02

Eclipse的代码注释模板很丰富,如:user、year、date、time等等,请参考附件。 尽管模板变量如此之多,但是对于复杂多边的实际项目而言,还是不够用,怎么办?自己增加新的呗! Eclipse配置界面都不支持定制自己的注释模板变量,没办法,改Eclipse源码。 具体怎么改,好像无头苍蝇,百度和谷歌了2天,还是无果,在边搜边摸索的过程中才知道需要修改如下两个架包对应的源码

  • org.eclipse.jdt.ui.jar
  • org.eclipse.text.jar

发现了三个重要的文件:

  • org.eclipse.jface.text.template.TextTemplateMessageg.properties
  • org.eclipse.jface.text.templates.GlobalTemplateVariables.java
  • org.eclipse.jdt.internal.corext.template.java.CodeTemplateContextType.java

TextTemplateMessageg.properties

定义了一些全局模板:

# global variables
GlobalVariables.variable.description.cursor=The cursor position after editing template variables
GlobalVariables.variable.description.dollar=The dollar symbol
GlobalVariables.variable.description.date=Current date
GlobalVariables.variable.description.year=Current year
GlobalVariables.variable.description.time=Current time
GlobalVariables.variable.description.user=User name
GlobalVariables.variable.description.selectedWord= The selected word
GlobalVariables.variable.description.selectedLines= The selected lines

我们可以看到user、year、date、time都在里面。 如果要增加自己的模板变量,就加吧?例如:

GlobalVariables.variable.description.copyright=Copyright all resolved

SimpleTemplateVariableResolver

全局模板变量在GlobalTemplateVariables里面,均继承自SimpleTemplateVariableResolver 增加一个对应GlobalVariables.variable.description.copyright的模板变量

  /**
     * The copyright variable evaluates to the current copyright.
     * Added by 博主 2011-10-30
      */
    public static class Copyright extends SimpleTemplateVariableResolver
    {
        /**
         * 默认从环境变量中取copyright信息,环境变量可以定义在eclipse.ini的vmargs下面
         */
        private static String value = System.getProperty("copyright");
        /**
         * Creates a new copyright variable
         */
        public Copyright()
        {
            super("copyright", TextTemplateMessages.getString("GlobalVariables.variable.description.copyright")); //$NON-NLS-1$ //$NON-NLS-2$
        }

        /**
         * {@inheritDoc}
         */
        protected String resolve(TemplateContext context)
        {
            if (value != null)
            {
                return TextTemplateMessages.getString("GlobalVariables.variable.description.copyright");
            }
            return value; //$NON-NLS-1$
        }
    }

CodeTemplateContextType

让Eclipse的Reference界面配置code template里支持${copyright},修改:CodeTemplateContextType,在其构造方法里面增加Copyright实例

public CodeTemplateContextType(String contextName) {
        super(contextName);

        fIsComment= false;

        // global
        addResolver(new GlobalTemplateVariables.Dollar());
        addResolver(new GlobalTemplateVariables.Date());
        addResolver(new GlobalTemplateVariables.Year());
        addResolver(new GlobalTemplateVariables.Time());
        addResolver(new GlobalTemplateVariables.User());
        //  Added by 博主 2011-10-30
       addResolver(new GlobalTemplateVariables.Copyright());

将修改的内容加到org.eclipse.jdt.ui.jar和org.eclipse.text.jar这两个包中 先退出Eclipse,再使用BeyondCompare之类的比较工具,将修改编译好的calss文件和properties文件覆盖到原有包中,不过如果你动jar的命令行操作,也可以使用jar命令打包。 好了,现在重新启动Eclipse,赶紧到windows-preferences->java-code style->code template界面去体验一下吧。

新建一个java类,在类名上方按Ctrl+Alt+j就可以出现自己的copyright注释了。 想想很简单。

附录:Eclipse针对java代码模板的内置变量

cursor 将编辑器的光标放在这个位置。 N/A
date 插入当前日期。
dollar 插入货币的文字符号。 $
elemType 尝试猜测具有给定 ID 的这个元素的类型。 MyType
enclosing_method 插入模板被插入其中的那个方法的名称。 method()
enclosing_method_arguments 为包围方法插入参数。 arg1, arg2
enclosing_package 插入当前类的包名。 com.example.ui
enclosing_project 插入包含所编辑的这个类的项目的名称。 myProject
enclosing_type 插入正在编辑的类型(类)的名称。 MyType
exception_variable_name 插入一个异常变量名称,进行最佳猜测。 e, ioe
file 文件的简称。 MyType.java
import 如果尚未导入,那么针对给定类型插入一个导入声明。 import com.example.ui.MyOtherType
importStatic 与 import 相同,只不过是静态导入。 import static com.example.ui.MyOtherType.*
line_selection 将选中的行插入到这里。这对用模板包装行是很有用的。 以选中行作为主体的 do、while 循环
primary_type_name 没有扩展名的文件简称。 MyType
time 插入当前的时间。
todo 注释中的 TODO 标记。 TODO
user 当前用户的名字。 ngood
var 解析为本地变量,如果不只一个,就提供一个列表。 myvar
word_selection 插入当前选中的单词。 N/A
year 将现在的年份插入到代码中。 2010

原文地址
http://shareal.blog.163.com/blog/static/27659056201193063914196

阅读全文 »

SpringFramework3 源码下载和编译教程

发表于 2012-06-02

Spring已经将源码从svn迁移到了git。所以,首先需要安装git环境。我使用的是Windows下的TortoiseGit,带图形界面,比较直观。

  • SpringFramework的GitHub地址:https://github.com/SpringSource/spring-framework
  • Git clone地址: git://github.com/SpringSource/spring-framework.git

下载后,进入下载到的文件夹,里面有个README.md的说明文件,如果E文可以,可以按照上面的说明去做。

接下来编译工程 Spring采用的是Gradle进行项目管理.

  • Gradle官网地址: http://gradle.org/

通过命令行进入刚在下载的目录(双击gradlew.bat貌似也可以),运行: ./gradlew build 开始下载gradle程序和依赖的jar文件等。这个过程比较耗时。等待半个小时之后,居然failed! 查看原因原来是测试代码failed,于是重新执行命令 ./gradlew build -x test 忽略测试代码,终于编译成功。

执行 ./gradlew install 将jar包安装到gradle库 接下来是将源码导入到IDE中。 由于我是windows用户,所以参考import-into-idea.md的说明 这里,由于我使用的是eclipse,所以执行: ./gradlew cleanIdea eclipse 生成Eclipse导入需要的文件 剩下的就是导入文件了。

  • PS:spring-oxm工程的有几个测似用例有编译错误,找不到他引用的类,之前下载的代码也有此问题,不知道是我下载的问题,还是spring那面的疏忽,不过只是测试用例,你可以自己修改一下,或者干脆删除就好了。
阅读全文 »
1 … 33 34 35 36
LiHongZhe

LiHongZhe

onecoder's blog.

354 日志
8 分类
RSS
Creative Commons
Links
  • 酷壳
  • 煮酒品茶
  • 小弟子的网络之路
  • 图表秀-在线图表制作
© 2012 - 2023 LiHongZhe
由 Jekyll 强力驱动
主题 - NexT.Muse
本站访客数 人次 本站总访问量 次