OneCoder


  • 首页

  • 归档

  • 标签

  • 关于

  • 搜索

Spring Batch 初探、使用样例

发表于 2014-09-28 | 阅读次数

工作里用到了Spring Batch项目,以前我也是做并行计算的,听说过Spring Batch不过并没有去具体了解过。今天抽空看看官方文档进行下初步的了解。

官方文档地址:http://docs.spring.io/spring-batch/trunk/reference/html/index.html

    

层次架构如上图。分三层:应用层,核心层,基础设施层。应用层包括所有的batch任务和用户开发的代码。核心层包括在运行期运行一个任务所需要的类,例如:JobLauncher,Job和Step的实现。应用和核心层都在基础设施层之上,基础设施层包括通用的读写器(readers and writers)以及如RetryTemplate等服务。

Spring Batch中的一些概念:


Job

Job是Step的容器,用来定义和配置整个任务的信息:

  • Job的名字
  • 定义Step的顺序
  • 定义任务可否重启

Job Config Sample:

<job id="footballJob">
    <step id="playerload" next="gameLoad"/>
    <step id="gameLoad" next="playerSummarization"/>
    <step id="playerSummarization"/></job>

JobInstance

是实际运行的Job实例,实例间数据也和业务独立。

JobParameters

Job运行的参数。。似乎没什么好解释的。

JobExecution

JobExecution是一个技术上的概念,只一次单独的执行任务。执行可以以成功和失败结尾,但是JobInstance只有在JobExecution成功结束的情况下,才被认为是完成的。例如,一个JobInstance第一次执行失败,重新执行,即为另一个JobExecution但是是同一个JobInstance。

Step

一个Job是有一个或者多个Step组成的。Step可复杂可简单。与Job类似,Step也有对应的StepExecution。


StepExecution

Step的每一次执行都是即为一个StepExecution。每次Step执行都会创建一个StepExecution。每个execution都包含了相关的Step和JobExecution的引用以及事务相关的数据和起始、结束时间。每个StepExecution也包含了一个ExecutionContext,包含了开发需要持久化的数据。

ExecutionContext

key/value对的对象,用于保存需要记录的上下文信息。scope是包括StepExecution和JobExecution。类似于Quartz中的JobDataMap。可以保存执行过程的信息,用于故障时重新执行和继续执行,甚至状态回滚等等,不过需要用户自行记录。任何时刻,一个StepExecution只会有一个ExecutionContext,由Spring Batch框架负责持久化和保证读取的准确性。

另外,需要注意的是,每个JobExectuion都有一个ExecutionContext,每个StepExecution也有一个独立的ExecutionContext。例如:

ExecutionContext ecStep = stepExecution.getExecutionContext();
ExecutionContext ecJob = jobExecution.getExecutionContext();
//ecStep does not equal ecJob

上述代码中,两个ExecutionContext是不相等的,Step中的context是在每个提交点上被保存,而job的context会在两个Step执行之间被保存。

JobRepository

JobRepository是关于前面提到的Job模版(Stereotypes)的持久化机制。提供了对了JobLauncher,Job和Step实现类的CRUD操作。当初次加载Job的时候,从repository中获取 JobExecution,在执行的过程中,StepExecution和JobExectuion的实现类通过repository持久化。


JobLauncher

JobLauncher是用于用指定的JobParameters加载Job的接口。

public interface JobLauncher {

    public JobExecution run(Job job, JobParameters jobParameters)
                throws JobExecutionAlreadyRunningException, JobRestartException;
}

Item Reader、 Item Writer、Item Processor

分别用于读、写和转换业务数据。转换即是进行数据模型的转换。

命名空间(Batch Namesapce)

<beans:beans xmlns="http://www.springframework.org/schema/batch"
     xmlns:beans="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/batch
           http://www.springframework.org/schema/batch/spring-batch-2.2.xsd">

完整使用样例:

配置文件 batch-context.xml

<beans:beans xmlns= "http://www.springframework.org/schema/batch"
     xmlns:beans="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/batch
           http://www.springframework.org/schema/batch/spring-batch-2.2.xsd">
     <beans:bean id ="first-tasklet"
           class= "com.coderli.spring.batch.firstjob.FirstTasklet" ></beans:bean >

     <beans:bean id ="jobRepository"
           class= "org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean" >
           <beans:property name ="transactionManager" ref= "transactionManager" />
     </beans:bean >

     <beans:bean id ="transactionManager"
           class= "org.springframework.batch.support.transaction.ResourcelessTransactionManager" ></beans:bean >

     <job id ="onecoder-job">
           <step id ="first-step" next="secend-step">
               <tasklet ref ="first-tasklet"></ tasklet>
           </step >
           <step id ="secend-step" >
               <tasklet >
                    <chunk reader ="myReader" writer= "myWriter" processor ="myProcessor"
                         commit-interval= "1"></chunk >
               </tasklet >
           </step >
     </job >

     <beans:bean id ="myReader" class= "com.coderli.spring.batch.firstjob.MyReader" ></beans:bean >
     <beans:bean id ="myWriter" class= "com.coderli.spring.batch.firstjob.MyWriter" ></beans:bean >
     <beans:bean id ="myProcessor"
           class= "com.coderli.spring.batch.firstjob.MyProcessor" ></beans:bean >
     <beans:bean id ="myFirstJobLauncher"
           class= "org.springframework.batch.core.launch.support.SimpleJobLauncher" >
           <beans:property name ="taskExecutor" ref= "syncTaskExecutor" />
           <beans:property name ="jobRepository" ref= "jobRepository" />
     </beans:bean >
     <beans:bean id ="syncTaskExecutor"
           class= "org.springframework.core.task.SyncTaskExecutor" />
     <beans:bean id ="jobLauncherTestUtils"
           class= "org.springframework.batch.test.JobLauncherTestUtils" >
           <beans:property name ="job" ref= "onecoder-job"></beans:property >
     </beans:bean >

</beans:beans>

First-Tasklet类

package com.coderli.spring.batch.firstjob;

import lombok.extern.slf4j.Slf4j;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

@Slf4j
public class FirstTasklet implements Tasklet {

     @Override
     public RepeatStatus execute(StepContribution contribution,
              ChunkContext chunkContext) throws Exception {
           log.info( "This is tasklet one in step one of job MyJob");
           return RepeatStatus.FINISHED;
     }
}

Reader类:

package com.coderli.spring.batch.firstjob;

import lombok.extern.slf4j.Slf4j;

import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;

/**
* 自定义Reader类
*
* @author OneCoder
* @date 2014年9月28日 下午2:33:43
*/
@Slf4j
public class MyReader implements ItemReader<MyModel> {

     private int count;

     @Override
     public MyModel read() throws Exception, UnexpectedInputException,
              ParseException, NonTransientResourceException {
           log.info( "This is my reader in step two of job: [MyJob.]");
          MyModel model = null;
           if ( count < 2) {
               model = new MyModel();
               model.setDescription( "My Description");
               model.setId( "My ID");
               model.setName( "My Name");
               count++;
          }
           return model;
     }

}

writer类

package com.coderli.spring.batch.firstjob;

import java.util.List;

import lombok.extern.slf4j.Slf4j;

import org.springframework.batch.item.ItemWriter;

/**
* 自定义Writer类
*
* @author OneCoder
* @date 2014年9月28日 下午2:46:20
*/
@Slf4j
public class MyWriter implements ItemWriter<String> {

     @Override
     public void write(List<? extends String> items) throws Exception {
           log.info( "This is my writer in step two for job: [MyJob].");
           log.info( "Write the JSON string to the console.");
           for (String item : items) {
               log.info( "Write item: {}", item);
          }
     }

}

processor类

package com.coderli.spring.batch.firstjob;

import lombok.extern.slf4j.Slf4j;

import org.springframework.batch.item.ItemProcessor;

import com.google.gson.Gson;

/**
*
* @author OneCoder
* @date 2014年9月28日 下午2:39:48
*/
@Slf4j
public class MyProcessor implements ItemProcessor<MyModel, String> {

     @Override
     public String process(MyModel item) throws Exception {
           log.info( "This is my process in step two of job: [MyJob].");
           log.info( "Transfer MyModel to JSON string.");
          Gson gson = new Gson();
           return gson.toJson( item);
     }
}

测试类

package com.coderli.spring.batch.firstjob;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
* 任务启动器,通过JUnit测试方式启动
*
* @author OneCoder
* @date 2014年9月28日 下午3:03:03
*/
@RunWith(SpringJUnit4ClassRunner. class)
@ContextConfiguration(locations = { "../batch-context.xml" })
public class MyFirstJobTest {

     @Autowired
     private JobLauncherTestUtils jobLauncherTestUtils;


     @Test
     public void testJob() throws Exception {
           jobLauncherTestUtils.launchJob();
          JobExecution jobExecution = jobLauncherTestUtils .launchJob();
          Assert. assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode());
     }
}

简单介绍上述代码里用到的东西,@SLF4J注解是lombok中的,之前介绍过。测试用的Spring-test和spring-batch-test提供的相关功能。

阅读全文 »

关于ShallowEtagHeaderFilter 大文件下载Out of memory问题解决

发表于 2014-09-14 | 阅读次数

最近解决大文件下载的问题,遇到一个"Out of memory"的exception。建厂controller层的代码,发现是用BufferdOutputStream写入Response中的,缓冲区也只有8m,按理说不应该出现内存溢出的。

仔细观察异常堆栈,发现堆栈中出现了“ShallowEtagHeaderFilter ”这个拦截器。该拦截器是用来处理ETag信息的。关于Etag和该拦截器的介绍可参考:
摘自百度百科:
http://baike.baidu.com/view/3039264.htm
ShallowEtagHeaderFilter介绍:
http://blog.csdn.net/geloin/article/details/7445251

阅读代码发现问题就出在这个过滤器中,这个过滤器中会将Buffered流转换成ByteArray流写入Response。而ByteArrayOutputStream都存储内存中,还需要频繁的扩容。这在大文件下载的时候自然会内存溢出。

解决方案

考虑到大部分url还是需要该拦截器进行过滤的,只是需要排除掉跟文件下载相关的url。所以这里OneCoder决定复写该Filter,设置一个黑名单,复写其中的doFilterInternal方法,对于黑名单中的url都直接传递给下一个filter,否则super一下,继续走原来的逻辑。

/**
 * The filter is used for resolving the big file download problem when using
 * {@link ShallowEtagHeaderFilter}. The urls on the black list will be passed
 * directly to the next filter in the chain, the others will be filtered as
 * before.
 * <p>
 * Sample:<br>
 * {@code <filter>}<br>
 * &amp;nbsp&amp;nbsp&amp;nbsp {@code<filter-name>BigFileEtagFilter</filter-name>}<br>
 * &amp;nbsp&amp;nbsp&amp;nbsp
 * {@code<filter-class>com.coderli.filter.BigFileDownloadEtagHeaderFilter</filter-class>}<br>
 * &amp;nbsp&amp;nbsp&amp;nbsp {@code<!-- url sperators includes: blank space ; , and /r/n.
 * Black list is optional.>}<br>
 * &amp;nbsp&amp;nbsp&amp;nbsp {@code<init-param>}<br>
 * &amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp {@code<param-name>blackListURL</param-name>}<br>
 * &amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp
 * {@code <param-value> /aa /bb/** /cc/* </param-value>}<br>
 * &amp;nbsp&amp;nbsp&amp;nbsp {@code</init-param>}<br>
 * {@code</filter>}<br>
 * {@code<filter-mapping>}<br>
 * &amp;nbsp&amp;nbsp&amp;nbsp {@code<filter-name>BigFileEtagFilter</filter-name>}<br>
 * &amp;nbsp&amp;nbsp&amp;nbsp {@code<url-pattern>/*</url-pattern>}<br>
 * {@code</filter-mapping>}
 * 
 * @author li_hongzhe@nhn.com
 * @date 2014-9-12 9:46:38
 */
public class BigFileDownloadEtagHeaderFilter extends ShallowEtagHeaderFilter {

	private final String[] NULL_STRING_ARRAY = new String[0];
	private final String URL_SPLIT_PATTERN = &quot;[, ;\r\n]&quot;;

	private final PathMatcher pathMatcher = new AntPathMatcher();

	private final Logger logger = LoggerFactory
			.getLogger(BigFileDownloadEtagHeaderFilter.class);
	// url while list
	// private String[] whiteListURLs = null;
	// url black list
	private String[] blackListURLs = null;

	@Override
	public final void initFilterBean() {
		initConfig();
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String reqUrl = request.getPathInfo();
		if (isBlackURL(reqUrl)) {
			logger.debug(&quot;Current url  {} is on the black list.&quot;, reqUrl);
			filterChain.doFilter(request, response);
		} else {
			super.doFilterInternal(request, response, filterChain);
		}
	}

	private void initConfig() {
		// No need white list now.
		// String whiteListURLStr = getFilterConfig().getInitParameter(
		// &quot;whiteListURL&quot;);
		// whiteListURLs = strToArray(whiteListURLStr);
		String blackListURLStr = getFilterConfig().getInitParameter(
				&quot;blackListURL&quot;);
		blackListURLs = strToArray(blackListURLStr);
	}

	// No need white list now.
	// private boolean isWhiteURL(String currentURL) {
	// for (String whiteURL : whiteListURLs) {
	// if (pathMatcher.match(whiteURL, currentURL)) {
	// logger.debug(
	// &quot;url filter : white url list matches : [{}] match [{}] continue&quot;,
	// whiteURL, currentURL);
	// return true;
	// }
	// logger.debug(
	// &quot;url filter : white url list not matches : [{}] match [{}]&quot;,
	// whiteURL, currentURL);
	// }
	// return false;
	// }

	private boolean isBlackURL(String currentURL) {
		for (String blackURL : blackListURLs) {
			if (pathMatcher.match(blackURL, currentURL)) {
				logger.debug(
						&quot;url filter : black url list matches : [{}] match [{}] break&quot;,
						blackURL, currentURL);
				return true;
			}
			logger.debug(
					&quot;url filter : black url list not matches : [{}] match [{}]&quot;,
					blackURL, currentURL);
		}
		return false;
	}

	private String[] strToArray(String urlStr) {
		if (urlStr == null) {
			return NULL_STRING_ARRAY;
		}
		String[] urlArray = urlStr.split(URL_SPLIT_PATTERN);
		List<String> urlList = new ArrayList<String>();

		for (String url : urlArray) {
			url = url.trim();
			if (url.length() == 0) {
				continue;
			}
			urlList.add(url);
		}
		return urlList.toArray(NULL_STRING_ARRAY);
	}
}

OneCoder也是参考网上有人提供的现成的样例,做了简单修改而已:http://jinnianshilongnian.iteye.com/blog/1663481

关于ShallowEtagHeaderFilter这个“Bug”,OneCoder发现网上也有人向spring反应了,不过好像Spring方面认为这是使用问题,不作为bug来进行处理,那我们就自己解决一下。

阅读全文 »

点滴积累 Lombok 实用且有想法的jar

发表于 2014-09-03 | 阅读次数

官网:http://projectlombok.org/

通过官网的视频,可以看到Lombok可以帮助我们节约很多机械而繁琐的代码。例如在写Pojo类或者Log的时候。写段代码体会一下:

Gradle依赖配置:

'org.projectlombok:lombok:1.14.4'

验证代码:

package com.coderli.lombok;

import lombok.Data;
import lombok.extern.java.Log;

/**
* Lombok 工具jar 测试类
*
* @author OneCoder
* @date 2014年9月3日 下午10:48:12
* @website http://www.coderli.com
*/
@Log
@Data
public class LombokTest {
   
     private String name;
     private int count;
   
     public static void main(String[] args) {
          log.info(&quot;Print log with Lombok&quot;);
     }
}

可以看到,代码中没有声明log对象,我们却是可以直接使用。这就是Lombok的作用。在类生配置了@Log注解,类中就可以直接使用log对象。

当然,如果想在Eclipse不提示编译错误,首先自然是用把Lombok安装到eclipse中。双击下载好的jar即可安装。

同样@Data注解,就表明该类是一个model类。会自动给属性增加get/set 方法。节约代码。在Eclipse中按F4,查看该类的Type Hierarchy,可以看到每个属性get set方法已经存在。

OneCoder查了一下他的实现原理,应该利用AST,抽象语法树实现的。具体的我也不是很了解这方面的东西。有空研究一下。有两篇文章有比较详细的介绍:

https://www.ibm.com/developerworks/cn/java/j-lombok/
http://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html

Lombok的更多功能,大家可以自己体会一下。确实是个很有想法的项目。佩服。

太晚了,休息了。晚安。

阅读全文 »

Java面试题 实现单例模式

发表于 2014-08-12 | 阅读次数

好久没有更新博客了,OneCoder没挂,只是儿子出生,忙了一个多月。生活节奏有点小乱。这期间也抽空换了份工作,坚持技术路线不动摇。So,抓紧调整一下状态,继续读书,写码。

面试题系列,是OneCoder早就打算学习的方向。为以后打基础,主要是学习《剑指Offer》和《编程之美》这两本书。iOS的单排第二季也会在近期开始,效仿马克思的学习方式,撸累了iOS的时候,做两道题,调剂一下。

不过,这次是从面试题先开始的,因为恰好OneCoder之前在准备面试的时候,看到了这个问题。早就想总结一下,分享给大家。结果一直拖到现在。

进入正题,Singleton模式是什么,这里不解释,直接上代码。这里分享了5种写法,并附上了评论。有好有坏,大家自行理解。


package com.coderli.interview;

/**
* 常见面试题:实现单例模式 <br>
* 《剑指offer》,第二章,面试题2 <br>
* 这里给出五种写法和对应的评论
*
* @author lihzh
* @date 2014年8月12日 上午11:10:13
*/
public class Singleton {
      /**
      * 写法一 <br>
      * 最直接的初级写法,忽略了对多线程并发的考虑。 <br>
      * 不推荐
      *
      * @author OneCoder
      * @blog http://www.coderli.com
      * @date 2014年8月12日 上午11:46:58
      */
      static class SingletonOne {

           // 私有化构造函数是必须的
           private SingletonOne() {
          }
           static SingletonOne instance = null;

           static SingletonOne getInstance() {
               if ( instance == null) {
                    instance = new SingletonOne();
              }
               return instance;
          }
     }
      /**
      * 写法二 <br>
      * 考虑到多线程并发的情况,加锁控制。 <br>
      * 功能正确,但是效率不高,每次获取实例都需要先获取锁。 <br>
      * 不推荐
      *
      * @author OneCoder
      * @blog http://www.coderli.com
      * @date 2014年8月12日 下午12:01:59
      */
      static class SingletonTwo {

           // 私有化构造函数是必须的
           private SingletonTwo() {
          }
           static SingletonTwo instance = null;

           static synchronized SingletonTwo getInstance() {
               if ( instance == null) {
                    instance = new SingletonTwo();
              }
               return instance;
          }
     }
      /**
      * 写法三 <br>
      * 考虑到多线程并发的情况,加锁控制。 <br>
      * 同时考虑到效率问题,进行二次判断,只在需要创建新实例的时候加锁。获取的时候无锁 <br>
      * 勉强过关
      *
      *
      * @author OneCoder
      * @blog http://www.coderli.com
      * @date 2014年8月12日 下午12:01:59
      */
      static class SingletonThree {

           // 私有化构造函数是必须的
           private SingletonThree() {
          }
           static SingletonThree instance = null;
           static byte[] lock = new byte[0];

           static SingletonThree getInstance() {
               if ( instance == null) {
                    synchronized ( lock) {
                         if ( instance == null) {
                              instance = new SingletonThree();
                        }
                   }
              }
               return instance;
          }
     }
      /**
      * 写法四 <br>
      * 考虑到多线程并发的情况,利用Java执行原理,静态方法执行一次 <br>
      * 无锁,效率高 <br>
      * 缺点:无论使用与否,都预先初始化完成。浪费资源。 <br>
      * 推荐写法的一种
      *
      *
      * @author OneCoder
      * @blog http://www.coderli.com
      * @date 2014年8月12日 下午12:01:59
      */
      static class SingletonFour {

           // 私有化构造函数是必须的
           private SingletonFour() {
          }
           static SingletonFour instance = new SingletonFour();

           static SingletonFour getInstance() {
               return instance;
          }
     }
    
      /**
      * 写法四 <br>
      * 考虑到多线程并发的情况,通过内部类实现按序初始化,且无锁 <br>
      * 最推荐的写法
      *
      *
      * @author OneCoder
      * @blog http://www.coderli.com
      * @date 2014年8月12日 下午12:01:59
      */
      static class SingletonFive {
         
           static class Inner {
               static SingletonFive new_instance = new SingletonFive();
          }
           // 私有化构造函数是必须的
           private SingletonFive() {
          }

           static SingletonFive getInstance() {
               return Inner. new_instance;
          }
     }

}
阅读全文 »

关于Shurnim-storage项目更新和喜获奖

发表于 2014-06-25 | 阅读次数

前端时间参加了又拍云存储的开发者大赛,没想到今天公布名单居然得了一等奖,小意外:)

shurnim项目介绍:

http://www.coderli.com/onecoder-shurnim-storage

结果公布页如下:
https://www.upyun.com/op/dev/showcase.html

说巧不巧的,这个项目本来一直没有更新,昨天正好我有一个需求,因为购买的又拍图片管家最近要到期了,所以我想把图片都迁移到七牛上。因为七牛现在每个月10g以内都是免费的。所以打算拿来当图床。当然,网站的CDN加速,还是用的又拍的赞助:)

考虑到以前发布过的文章中的图片已经有外链地址。所以,而这里二级域名的变化是不可避免的。不过这里有一个好办法,就是如果仅仅前缀二级域名变化的,可以通过在数据执行:

UPDATE  `wp_posts` set post_content=replace(post_content,'http://8wuliao.v.yupoo.com/','http://onecoder.qiniudn.com');

进行全部替换。那么我们只要保持后面的相对路径不变即可。好在七牛这方面比较简单,因为他是key-value式存储,所以你只要把key设置成想要的路径即可,而不要考虑文件的时间存储位置。

从又拍导出的图片文件名是用"_"分隔的三段名,而这三段正好是图片访问的相对路径。例如,图片名为:8wuliao_C6UViNqh_YOGEq.jpg,则原始对应的访问路径为:"http://8wuliao.v.yupoo.com/8wuliao/C6UViNqh/YOGEq.jpg"。所以,我们只要把文件名解析成对应的key即可。

这里,正好也丰富下shurnim-storage。开发一个本地文件测插件local,后台接口再增加一个批量递归上传的接口。

代码已经上传到OSChina的git上了,项目地址:
http://git.oschina.net/onecoder/shurnim-storage

不过,由于着急实现功能,这里还有几个OneCoder不是很满意的地方,考虑做一些优化:

1、给本地插件增加一个类别,这样以后所有的本地化操作插件,在调用download接口式,都不会生成临时文件。也不会删除临时文件。增加选项,是否删除原文件。
2、不上传子目录的分支尚为TODO状态
3、支持文件夹、文件名过滤。

慢慢实现,不着急:)

阅读全文 »

OneCoder的shurnim-storage项目

发表于 2014-05-22 | 阅读次数

shurnim-storage

背景介绍

Shurnim,是我和我老婆曾经养过的一只仓鼠的名字。

shurnim-storage,是一个插件式云存储/网盘同步管理工具。是在参加又拍云开发大赛的过程中设计并开发。

项目介绍
shurnim-storage 的设计初衷是给大家提供一个可方便扩展的云存储/网盘同步工具。分后端接口和前端UI界面两部分。


由于目前各种云存储和网盘系统层出不穷,单一工具往往支持支持某几个特定存储之间的同步,如又拍云到七牛云存储的同步工具,此时如若想同步到其他存则可能需要新的工具,给用户带来不便。shurnim-storage 正是为了解决此问题而设计的。
在shurnim-storage中,用户使用的固定的统一的后端接口。而所有云存储/网盘API的支持则是以插件的形式部署到系统中的。如此,如果用户想要一个从又拍云到Dropbox的同步工具,则只需要在原有基础上,增加Dropbox的插件,即可实现互通,方便快捷。


同时,后端统一接口的设计也考虑到界面开发的需求,可直接通过后端提供的接口开发具有上述扩展功能的云存储UI工具。


目前,后端整体框架的核心部分已经基本开发完成。只需逐步补充后端接口和插件开发接口的定义即可。但由于个人时间和能力所限,UI部分没有开发,有兴趣的同学可以一试。

获取代码

   * GitHub项目主页: https://github.com/lihongzheshuai/shurnim-storage
   * OSChina项目主页: http://git.oschina.net/onecoder/shurnim-storage

GitHub上的会持续更新。欢迎任何形式的fork。

另外你也可以通过OSChina的Maven库获取依赖,或者自己编译jar包。

maven


加入OSC仓库

				<repositories>
            		<repository>
            			<id>nexus</id>
            			<name>local private nexus</name>
            			<url>http://maven.oschina.net/content/groups/public/</url>
            			<releases>
            				<enabled>true</enabled>
            			</releases>
            			<snapshots>
            				<enabled>false</enabled>
            			</snapshots>
            		</repository>
            	</repositories>


加入依赖

<dependency>
			  <groupId>com.coderli</groupId>
			  <artifactId>shurnim-storage</artifactId>
 			  <version>0.1-alpha</version>
			</dependency>

项目采用Gradle管理依赖,可通过gradle编译Jar

在项目目录执行

gradle jar

最后

时间仓促,功能简陋,望您包涵。OneCoder(Blog:http://www.coderli.com)特别希望看到该项目对您哪怕一点点的帮助。任意的意见和建议,欢迎随意与我沟通,联系方式:

   * Email: wushikezuo@gmail.com
   * QQ:57959968
   * Blog:OneCoder
 

阅读全文 »

为项目编写Readme.MD文件

发表于 2014-05-21 | 阅读次数

了解一个项目,恐怕首先都是通过其Readme文件了解信息。如果你以为Readme文件都是随便写写的那你就错了。github,oschina git gitcafe的代码托管平台上的项目的Readme.MD文件都是有其特有的语法的。称之为Markdown语法。基本规则如下:

Markdown 语法速查表
1 标题与文字格式
标题
# 这是 H1 <一级标题>
## 这是 H2 <二级标题>
###### 这是 H6 <六级标题>
文字格式
**这是文字粗体格式**
*这是文字斜体格式*
~~在文字上添加删除线~~
2 列表
无序列表
* 项目1
* 项目2
* 项目3
有序列表
1. 项目1
2. 项目2
3. 项目3
   * 项目1
   * 项目2
3 其它
图片
![图片名称](http://gitcafe.com/image.png)
链接
[链接名称](http://gitcafe.com)
引用
> 第一行引用文字
> 第二行引用文字
水平线
***
代码
`<hello world>`
代码块高亮
```ruby
  def add(a, b)
    return a + b
  end
```
表格
  表头  | 表头
  ------------- | -------------
 单元格内容  | 单元格内容
 单元格内容l  | 单元格内容

如果直接记语法,那似乎困难了些。这里OneCoder推荐两个Markdown的编辑器。

在线编辑器:stackedit
网址:https://stackedit.io/

Mac下离线编辑器Mou
下载地址:http://mouapp.com/


OneCoder这里使用的是后者为自己的shurnim-storage项目写Readme。至于这个项目是什么,见Readme文档,OneCoder也会在另外的博文做一些补充说明。成品Readme如下:

# shurnim-storage

![Shurnim icon](http://onecoder.qiniudn.com/8wuliao/DLPii2Jx/rEBO.jpg)

## 目录
* [背景介绍](#背景介绍)
* [项目介绍](#项目介绍)
* [使用说明](#使用说明)
  * [获取代码](#获取代码)
  * [开发插件](#开发插件)
  * [使用ShurnimStorage接口](#使用ShurnimStorage接口)
       * [接口介绍](#接口介绍)
       * [使用样例](#使用样例)
* [其他](#其他)

<a name="背景介绍"></a>
## 背景介绍

*Shurnim*,是我和我老婆曾经养过的一只仓鼠的名字。<br/>
*shurnim-storage*,是一个插件式云存储/网盘同步管理工具。是在参加又拍云开发大赛的过程中设计并开发。

<a name="项目介绍"></a>
## 项目介绍

*shurnim-storage* 的设计初衷是给大家提供一个可方便扩展的云存储/网盘同步工具。分后端接口和前端UI界面两部分。<br>

由于目前各种云存储和网盘系统层出不穷,单一工具往往支持支持某几个特定存储之间的同步,如**又拍云**到**七牛云存储**的同步工具,此时如若想同步到其他存则可能需要新的工具,给用户带来不便。*shurnim-storage*  正是为了解决此问题而设计的。

在*shurnim-storage*中,用户使用的固定的统一的后端接口。而所有云存储/网盘API的支持则是以插件的形式部署到系统中的。如此,如果用户想要一个从**又拍云**到**Dropbox**的同步工具,则只需要在原有基础上,增加**Dropbox**的插件,即可实现互通,方便快捷。<br/>

同时,后端统一接口的设计也考虑到界面开发的需求,可直接通过后端提供的接口开发具有上述扩展功能的云存储UI工具。<br>

目前,后端整体框架的核心部分已经基本开发完成。只需逐步补充后端接口和插件开发接口的定义即可。但由于个人时间和能力所限,UI部分没有开发,有兴趣的同学可以一试。

<a name="使用说明"></a>
## 使用说明

<a name="获取代码"></a>
### 获取代码

* gitcafe项目主页: <https://gitcafe.com/onecoder/shurnim-storage-for-UPYUN>
* OSChina项目主页: <http://git.oschina.net/onecoder/shurnim-storage><br>
OSChina上的会持续更新。

另外你也可以通过OSChina的Maven库获取依赖,或者自己编译jar包。

* maven

     1. 加入OSC仓库
   
                    <repositories>
                      <repository>
                           <id>nexus</id>
                           <name>local private nexus</name>
                           <url>http://maven.oschina.net/content/groups/public/</url>
                           <releases>
                                <enabled>true</enabled>
                           </releases>
                           <snapshots>
                                <enabled>false</enabled>
                           </snapshots>
                      </repository>
                 </repositories>

     2. 加入依赖
   
               <dependency>
                 <groupId>com.coderli</groupId>
                 <artifactId>shurnim-storage</artifactId>
                  <version>0.1-alpha</version>
               </dependency>
* Gradle 编译Jar

在项目目录执行
   
     gradle jar
   
<a name="开发插件"></a>
### 开发插件

在*shurnim-storage*中,插件就像一块一块的积木,不但支撑着框架的功能,也是框架可扩展性的基石。开发一个插件,仅需两步:

1. 实现PluginAPI接口

```
package com.coderli.shurnim.storage.plugin;

import java.io.File;
import java.util.List;

import com.coderli.shurnim.storage.plugin.model.Resource;

/**
* 各种云存储插件需要实现的通用接口
*
* @author OneCoder
* @date 2014年4月22日 下午9:43:41
* @website http://www.coderli.com
*/
public interface PluginAPI {

     /**
      * 初始化接口
      *
      * @author OneCoder
      * @date 2014年5月19日 下午10:47:40
      */
     void init();

     /**
      * 获取子资源列表
      *
      * @param parentPath
      * @return
      * @author OneCoder
      * @date 2014年4月24日 下午11:29:14
      */
     List<Resource> getChildResources(String parentPath);

     /**
      * 下载特定的资源
      *
      * @param parentPath
      *            目录路径
      * @param name
      *            资源名称
      * @param storePath
      *            下载资源保存路径
      * @return
      * @author OneCoder
      * @date 2014年4月24日 下午11:30:19
      */
     Resource downloadResource(String parentPath, String name, String storePath);

     /**
      * 创建文件夹
      *
      * @param path
      *            文件夹路径
      * @param auto
      *            是否自动创建父目录
      * @return
      * @author OneCoder
      * @date 2014年5月15日 下午10:10:04
      */
     boolean mkdir(String path, boolean auto);

     /**
      * 上传资源
      *
      * @param parentPath
      *            父目录路径
      * @param name
      *            资源名称
      * @param uploadFile
      *            待上传的本地文件
      * @return
      * @author OneCoder
      * @date 2014年5月15日 下午10:40:13
      */
     boolean uploadResource(String parentPath, String name, File uploadFile);
}
```

目前插件的接口列表仅为同步资源设计,如果想要支持更多操作(如删除,查找等),可扩展该接口定义。<br/><br/>
接口中,所有的参数和返回值均为*shurnim-storage*框架中定义的通用模型。因此,您在开发插件过程中需要将特定SDK中的模型转换成接口中提供的模型。<br/><br/>
插件实现类只要与*shurnim-storage*工程在同一个classpath即可使用。您既可以直接在源码工程中开发插件,就如工程里提供的*upyun*和*qiniu*插件一样,也可以作为独立工程开发,打成jar,放置在同一个classpath下。<br/><br/>
*upyun*插件样例(功能不完整):

```  
package com.coderli.shurnim.storage.upyun.plugin;

import java.io.File;
import java.util.List;

import com.coderli.shurnim.storage.plugin.AbstractPluginAPI;
import com.coderli.shurnim.storage.plugin.model.Resource;
import com.coderli.shurnim.storage.plugin.model.Resource.Type;
import com.coderli.shurnim.storage.upyun.api.UpYun;

public class UpYunPlugin extends AbstractPluginAPI {

     private UpYun upyun;
     private String username;
     private String password;
     private String bucketName;

     public UpYun getUpyun() {
          return upyun;
     }

     public void setUpyun(UpYun upyun) {
          this.upyun = upyun;
     }

     public String getUsername() {
          return username;
     }

     public void setUsername(String username) {
          this.username = username;
     }

     public String getPassword() {
          return password;
     }

     public void setPassword(String password) {
          this.password = password;
     }

     public String getBucketName() {
          return bucketName;
     }

     public void setBucketName(String bucketName) {
          this.bucketName = bucketName;
     }

     /*
      * (non-Javadoc)
      *
      * @see
      * com.coderli.shurnim.storage.plugin.PluginAPI#getChildResources(java.lang
      * .String)
      */
     @Override
     public List<Resource> getChildResources(String parentPath) {
          return null;
     }

     /*
      * (non-Javadoc)
      *
      * @see
      * com.coderli.shurnim.storage.plugin.PluginAPI#downloadResource(java.lang
      * .String, java.lang.String, java.lang.String)
      */
     @Override
     public Resource downloadResource(String parentPath, String name,
               String storePath) {
          File storeFile = new File(storePath);
//          if (!storeFile.exists()) {
//               try {
//                    storeFile.createNewFile();
//               } catch (IOException e) {
//                    e.printStackTrace();
//               }
//          }
          String filePath = getFullPath(parentPath, name);
          upyun.readDir("/api");
          if (upyun.readFile(filePath, storeFile)) {
               Resource result = new Resource();
               result.setName(name);
               result.setPath(parentPath);
               result.setType(Type.FILE);
               result.setLocalFile(storeFile);
               return result;
          }
          return null;
     }

     String getFullPath(String parentPath, String name) {
          if (!parentPath.endsWith(File.separator)) {
               parentPath = parentPath + File.separator;
          }
          return parentPath + name;
     }

     /*
      * (non-Javadoc)
      *
      * @see com.coderli.shurnim.storage.plugin.PluginAPI#mkdir(java.lang.String,
      * boolean)
      */
     @Override
     public boolean mkdir(String path, boolean auto) {
          // TODO Auto-generated method stub
          return false;
     }

     /*
      * (non-Javadoc)
      *
      * @see
      * com.coderli.shurnim.storage.plugin.PluginAPI#uploadResource(java.lang
      * .String, java.lang.String, java.io.File)
      */
     @Override
     public boolean uploadResource(String parentPath, String name,
               File uploadFile) {
          // TODO Auto-generated method stub
          return false;
     }

     /*
      * (non-Javadoc)
      *
      * @see com.coderli.shurnim.storage.plugin.AbstractPluginAPI#init()
      */
     @Override
     public void init() {
          upyun = new UpYun(bucketName, username, password);
     }

}
```


2. 编写插件配置文件

```
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
     <id>qiniu</id>
     <name>七牛云存储</name>
     <api>
          <className>com.coderli.shurnim.storage.qiniu.QiniuPlugin</className>
          <params>
               <param name="access_key" displayName="ACCESS_KEY">EjREKHI_GFXbQzyrKdVhhXrIRyj3fRC1s9UmZPZO
               </param>
               <param name="secret_key" displayName="SECRET_KEY">88NofFWUvkfJ6T6rGRxlDSZOQxWkIxY2IsFIXJLX
               </param>
               <param name="bucketName" displayName="空间名">onecoder
               </param>
          </params>
     </api>
</plugin>
```
   * **id** 为该插件在*shurnim-storage*框架下的唯一标识,不可重复,必填。
    * **name** 为显示值,为UI开发提供可供显示的有语义的值。
    * **className** 为插件接口实现类的完整路径。必填
    * **params/param** 为插件需要用户配置的参数列表。其中
         * *name* 代表参数名,需要与接口实现类中的参数名严格一致,且必须有相应的set方法的格式要求严格,即set+首字母大写的参数名。例如:setAccess_key(String arg); 目前只支持*String*类型的参数。
         * *displayName* 为参数显示名,同样是为了UI开发的考虑,方便用户开发出可根据参数列表动态显示的UI界面。
         * 参数的值可以直接配置在配置文件中,也可以在运行期动态赋值。直接配置值,对于直接使用后端接口来说较为方便。对于UI开发来说,运行期动态赋值更为合理。<br/></br>

     在使用源码工程时,插件配置文件统一放置在工程的*plugins*目录下。你也可以统一放置在任何位置。此时,在构造后端接口实例时,需要告知接口该位置。
   
<a name="使用ShurnimStorage接口"></a>
### 使用*ShurnimStorage*接口

<a name="接口介绍"></a>
#### 接口介绍

**ShurnimStorage**接口是*shurinm-storage*框架全局的也是唯一的接口,目前定义如

```
package com.coderli.shurnim.storage;

import java.util.List;
import java.util.Map;

import com.coderli.shurnim.storage.plugin.model.Plugin;
import com.coderli.shurnim.storage.plugin.model.Resource;

/**
* 后台模块的全局接口<br>
* 通过该接口使用后台的全部功能。<br>
* 使用方式:<br>
* <li>
* 1.先通过{@link #getSupportedPlugins()}方法获取所有支持的平台/插件列表。 <li>
* 2.将列表中返回的ID传入对应的接口参数中,进行对应的平台的相关操作。<br>
* 需要注意的是,不同平台的插件需要给不同的参数赋值,该值可以直接配置在配置文件中。<br>
* 也可以在运行期动态赋值。(会覆盖配置文件中的值。)<br>
*
* 参数列表的设计,方便UI开发人员动态的根据参数列表生成可填写的控件。并给参数赋值。增强了可扩展性。
*
* @author OneCoder
* @date 2014年4月22日 下午9:21:58
* @website http://www.coderli.com
*/
public interface ShurnimStorage {

     /**
      * 获取当前支持的插件列表<br>
      * 没有支持的插件的时候可能返回null
      *
      * @return
      * @author OneCoder
      * @date 2014年5月7日 下午8:53:25
      */
     List<Plugin> getSupportedPlugins();

     /**
      * 给指定的插件的对应参数赋值<br>
      * 此处赋值会覆盖配置文件中的默认值
      *
      * @param pluginId
      *            插件ID
      * @param paramsKV
      *            参数键值对
      * @author OneCoder
      * @date 2014年5月9日 上午12:41:53
      */
     void setParamValues(String pluginId, Map<String, String> paramsKV);

     /**
      * 获取插件对应目录下的资源列表
      *
      * @param pluginId
      *            插件ID
      * @param path
      *            指定路径
      * @return
      * @author OneCoder
      * @date 2014年5月11日 上午8:52:00
      */
     List<Resource> getResources(String pluginId, String path);

     /**
      * 同步资源
      *
      * @param fromPluginId
      *            待同步的插件Id
      * @param toPluginIds
      *            目标插件Id
      * @param resource
      *            待同步的资源
      * @return 同步结果
      * @author OneCoder
      * @date 2014年5月11日 上午11:41:24
      */
     boolean sycnResource(String fromPluginId, String toPluginId,
                    Resource resource) throws Exception;
}
```    

当前接口实际仅包含了获取资源列表*getResources*和同步资源*sycnResource*功能,*getSupportedPlugins*和*setParamValues*实际为辅助接口,在UI开发时较为有用。<br/><br/>
同样,您也可以扩展开发该接口增加更多的您喜欢的特性。例如,同时删除给定存储上的文件。当然,这需要插件接口的配合支持。<br/><br/>

这里,*sycnResource*设计成插件间一对一的形式,是考虑到获取同步是否成功的结果的需求。如果您想开发一次同步到多个存储的功能,建议您重新开发您自己的接口实现类,因为默认实现会多次下次资源(每次同步后删除),造成网络资源的浪费。

接口的默认实现类是: **DefaultShurnimStorageImpl**

<a name="使用样例"></a>
#### 使用样例
```      
package com.coderli.shurnim.test.shurnimstorage;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import com.coderli.shurnim.storage.DefaultShurnimStorageImpl;
import com.coderli.shurnim.storage.ShurnimStorage;
import com.coderli.shurnim.storage.plugin.model.Resource;
import com.coderli.shurnim.storage.plugin.model.Resource.Type;

/**
* 全局接口测试类<br>
* 时间有限,目前仅作整体接口测试。细粒度的单元测试,随开发补充。
*
* @author OneCoder
* @date 2014年5月19日 下午10:50:27
* @website http://www.coderli.com
*/
public class ShurnimStorageTest {

     private static ShurnimStorage shurnim;

     @BeforeClass
     public static void init() {
          shurnim = new DefaultShurnimStorageImpl(
                    "/Users/apple/git/shurnim-storage-for-UPYUN/plugins");
     }

     @Test
     public void testSycnResource() {
          Resource syncResource = new Resource();
          syncResource.setPath("/api");
          syncResource.setName("api.html");
          syncResource.setType(Type.FILE);
          try {
               Assert.assertTrue(shurnim.sycnResource("upyun", "qiniu",
                         syncResource));
          } catch (Exception e) {
               e.printStackTrace();
          }
     }
}
```
<a name="其他"></a>
## 其他

时间仓促,功能简陋,望您包涵。OneCoder(Blog:[http://www.coderli.com](http://www.coderli.com))特别希望看到该项目对您哪怕一点点的帮助。任意的意见和建议,欢迎随意与我沟通,联系方式:

* Email: <wushikezuo@gmail.com>
* QQ:57959968
* Blog:[OneCoder](http://www.coderli.com)

项目的Bug和改进点,可在OSChina上以issue的方式直接提交给我。

效果预览:

阅读全文 »

Spring集成Hibernate注解配置 无hibernate.cfg.xml文件,自动生成表配置

发表于 2014-05-09 | 阅读次数

本以为一个无足挂齿的小问题,没想到还折腾了一下。遂记录一下。主要搜索出的结果排名靠前的大多是在hibernate.cfg.xml中的配置方式。与我的环境不符。正确配置方式如下。已测试。

<bean id= "sessionFactory"
           class= "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" >
           <property name ="dataSource">
               <ref bean ="dataSource" />
           </property >
           <property name ="packagesToScan" value= "xxx.xxx" />
           <property name ="hibernateProperties">
               <props >
                    <prop key= "hibernate.hbm2ddl.auto">create</prop >
               </props >
               <!-- <value>hibernate.hbm2ddl.auto=create</value> -->
           </property >
     </bean >

注:注释掉的配置value配置方式和prop配置方式,均有效。
 

阅读全文 »

log4j 同一线程隔离classloader下MDC信息不同问题解决 ThreadLocal问题分析

发表于 2014-05-06 | 阅读次数

最近遇到日志文件记录错误的问题。一个任务的日志信息会被莫名的拆分到两个不同目录中。且有一个目录还是曾经执行过的任务的目录。经过分析,首先怀疑的是MDC没有清理的问题,这也是最直观的问题。因为任务是在线程池(fixedThreadPool)中运行的。由于线程会被重用,而MDC是绑定在Threadlocal上的,所以如果没有清理,是会造成上述问题。但是在代码检查中发现在线程的开始,是重新设置过MDC信息的。所以,怀疑的对象转移到了多classloader上。由于不能肯定,所以进行测试如下:

package com.coderli.log4j.mdc;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;


import org.apache.log4j.MDC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* 单线程多 classloader模式下,log4jMDC信息传递测试。
*
* @author OneCoder
* @blog http://www.coderli.com
* @date 2014年5月6日 上午10:55:34
*/
public class SingleThreadMultiLoader {


     private static final Logger logger = LoggerFactory
              . getLogger(SingleThreadMultiLoader.class);
     private static String key = "loaderName";
     static ThreadLocal<String> tl = new ThreadLocal<String>();


     public static void main(String[] args) throws MalformedURLException,
              ClassNotFoundException, InstantiationException,
              IllegalAccessException, NoSuchMethodException, SecurityException,
              IllegalArgumentException, InvocationTargetException {
          MDC. put(key, "in main loader" );
           logger.info( "线程名: {}; 日志MDC信息:{}。" , Thread.currentThread().getName(),
                   MDC. get(key));
           tl.set( "huanyige");
          ClassLoader cLoader = Thread.currentThread().getContextClassLoader();
          URL[] urls = new URL[] {
                    new URL("file:\\D:\\acap\\workspace\\shurnim-lab\\lib\\mdc.jar" ),
                    new URL(
                              "file:\\D:\\acap\\workspace\\shurnim-lab\\lib\\log4j-1.2.17.jar" ),
                    new URL(
                              "file:\\D:\\acap\\workspace\\shurnim-lab\\lib\\slf4j-api-1.7.5.jar" ),
                    new URL(
                              "file:\\D:\\acap\\workspace\\shurnim-lab\\lib\\slf4j-log4j12-1.7.5.jar" ) };
          ClassLoader loader = new URLClassLoader(urls, null);
          Thread. currentThread().setContextClassLoader(loader);
          String className = SingleThreadMultiLoader.class.getName();
           Class clz = loader.loadClass(className);
          Method main = clz.getMethod("logMethod");
          main.invoke( null);
           logger.info( "线程名: {}; 日志MDC信息:{}。" , Thread.currentThread().getName(),
                   MDC. get(key));
     }


     public static void logMethod() {
           logger.info( "线程名: {}; 日志MDC信息:{}。" , Thread.currentThread().getName(),
                   MDC. get(key));
          MDC. put(key, "hahahahhaha" );
          System. out.println( tl.get());
     }
}

执行结果如下:

2014-05-06 16:02:53,802 >> INFO  >> main >> com.coderli.log4j.mdc.SingleThreadMultiLoader.main(SingleThreadMultiLoader.java:32) >> 线程名: main; 日志MDC信息:in main loader。
2014-05-06 16:02:53,869 >> INFO  >> main >> com.coderli.log4j.mdc.SingleThreadMultiLoader.logMethod(SingleThreadMultiLoader.java:62) >> 线程名: main; 日志MDC信息:null。
null
2014-05-06 16:02:53,870 >> INFO  >> main >> com.coderli.log4j.mdc.SingleThreadMultiLoader.main(SingleThreadMultiLoader.java:50) >> 线程名: main; 日志MDC信息:in main loader。


可以看到,在全隔离的两个Classloader下,MDC信息也是隔离的,不互通的。OneCoder遇到的bug也由此而来,在沙箱内部,虽然是同一个线程,但是MDC内的信息是上一个任务的,自然会出错了。

为了更直观的说明问题,OneCoder还验证了ThreadLocal的情况,结果一样也是隔离的。这就跟ThreadLocal的实现机制有关了。

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

从ThreadLocal的get()方法便可理解。在取值的时候,首先通过当前线程对象作为key。获取到当前线程的ThreadLocalMap,再用ThreadLocal对象作为key从Map中获取值。而在隔离的Classloader中,这个ThreadLocal对象是不同的。自然取到的值也就不同了。


当然,如果你的Classloader不是完全隔离的。在ThreadLocal层是有共同的父loader话,ThreadLocal中的值还是可以互通的。

阅读全文 »

OneCoder的五味杂谈

发表于 2014-04-14 | 阅读次数

一直都在写技术博客……

春暖花开,春心荡漾,心中萌发几多感慨。

跟媳妇一起看《穿越屋顶的high kick》,戏里戏外欢乐,安心。

一直希望可以进入一种沉迷的感觉,一种心安理得的做自己的喜欢事情的感觉,哪怕是一个游戏。

喜欢一个自己的封闭的小空间。一张床,一张桌子。

想要安静无旁骛的工作,学习,却不停的东张西望。厌恶自己的性格。

不求成大事,只求心安理得的生活。

唯一的渴望,就是在程序员的生涯中,有一个属于自己的完整的成果。时间走的很快……

阅读全文 »
1 … 16 17 18 … 33
LiHongZhe

LiHongZhe

onecoder's blog.

326 日志
8 分类
RSS
Creative Commons
Links
  • 酷壳
  • 酸菜鱼
  • 私塾在线学习网
  • 煮酒品茶
  • 点滴技术博客
  • 思考者日记网·束洋洋
  • 开源视窗
  • 小弟子的网络之路
  • 寄存心–移动开发
  • TicmyBlog
  • 中国程序员人才网
  • 图表秀-在线图表制作
  • IT热血青年
© 2012 - 2022 LiHongZhe
由 Jekyll 强力驱动
主题 - NexT.Muse