Spring源码阅读(一):Spring是如何扫描某个包下面所有的类

Spring扫描注解的功能 我们知道在Spring中可以使用注解声明Bean,可以让我们不用再去配置繁琐的xml文件,确实让我们的开发简便不少。只要在Spring xml里配置如下,就开启了这项功能。 <context:component-scan base-package="com.zhaoyanblog" /> Spring就会自动扫描该包下面所有打了Spring注解的类,帮你初始化病注册到Spring容器中。 @Service public class UserController { ​ @Autowired private UserDao userDao; ​ //TODO } 上述行为,就和在xml里进行下面的配置是等价的。 <bean class="com.zhaoyanblog.UserController"> <property name="userDao" ref="userDao" /> </bean> 那么问题来了,Spring是怎么扫描到com.zhaoyanblog包下面的所有带注解的类的呢? context:component-scan标签的处理者 要弄清楚为什么在xml里配置了context:component-scan就可以实现这样的功能,就要现找到这个标签的处理者。我们很容易联想到Spring解析xml的基本原理,就是遇到这个标签以后,交给一个类处理,这个类扫描包下带注解的类,初始化成对象。我们就是要找到这个关键的类。 Spring对Xml的解析功能后面阅读,这里先简要描述。Spring的jar包里有两个重要的配置文件:spring.schemas和spring.handlers,在META-INF目录下。 spring.schemas记录了xml的每个命名空间,对应的Schema校验XSD文件在哪个目录。 http\://www.springframework.org/schema/context/spring-context-4.2.xsd=org/springframework/context/config/spring-context.xsd http\://www.springframework.org/schema/context/spring-context-4.3.xsd=org/springframework/context/config/spring-context.xsd http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context.xsd spring.handlers里配置了每个命名空间的标签都由哪个类处理 http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler 我们可以看到context这个命名空间由org.springframework.context.config.ContextNamespaceHandler这个类处理。打开这个类 public class ContextNamespaceHandler extends NamespaceHandlerSupport { ​ @Override public void init() { registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); } ​ } 这个类为每个标签都给出了一个处理类,component-scan的处理类是ComponentScanBeanDefinitionParser 继续打开ComponentScanBeanDefinitionParser private static final String BASE_PACKAGE_ATTRIBUTE = "base-package"; public BeanDefinition parse(Element element, ParserContext parserContext) { String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE); basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage); String[] basePackages = StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); ​ // Actually scan for bean definitions and register them. ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages); registerComponents(parserContext.getReaderContext(), beanDefinitions, element); ​ return null; } 看到这里整明白了,扫描某个包下面的所有类的工作,就是ClassPathBeanDefinitionScanner干的。入口是doScan方法。 查找所有的包路径 ClassPathBeanDefinitionScanner有很多细节,比如可以设置class的filter, 设置classloader等等,我们先关注最主要的功能,就是怎么找到一个包下面所有的类的。 通过调用关系,一路找下去 doScan->findCandidateComponents(super)->scanCandidateComponents(super) private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 我们发现,原来Spring是在classpath下面,通过查找所有classpath*:com/zhaoyanblog/**/*.class的文件来实现的啊。 ...

2020年3月24日 · 3 分钟

Junit单元测试碰到静态变量如何处理

写单元测试用例,是程序员的好习惯,写java程序,一般使用Junit写单元测试。 我在写单元测试用例到时候,遇到一个问题:Junit在整个project中是一个java进程,如果你的程序里涉及静态变量,就会导致两个单元测试类之间相互影响。 你可以每次beforeClass初始化静态变量,但是有时候依赖了很多开源软件,你都不知道究竟一个测试流程里用到了哪些静态变量,甚至不知道如何还原它们。 最有效直接的办法就是每个测试用例都使用一个独立的classloader,也就是说每个test case都是类隔离的。 参考https://stackoverflow.com/questions/42102/using-different-classloaders-for-different-junit-tests 这篇帖子里的回复。实现了这个功能。 具体源码: https://github.com/johnyannj/junit-alone 用法如下: maven引入依赖 <dependency> <groupId>com.zhaoyanblog</groupId> <artifactId>junit-alone</artifactId> <version>1.0.1-SNAPSHOT</version> </dependency> 在你的测试用例中,用AloneRunner代替你原来真实的Runner,并把你真实的Runner,设置给@AloneWith @RunWith(AloneRunner.class) @AloneWith(JUnit4.class) public class JunitAloneTest { @Test public void test() { StaticClass.staticNum++; Assert.assertEquals(1, StaticClass.staticNum); } } 这样你的测试用例就会使用一个独立的classloader来执行了, 试试看吧。

2019年4月28日 · 1 分钟

JAVA简单调用C/C++语言(JNI学习三)

JNI的目的是可以使用C/C++完成部分逻辑,一方面 代码复用,避免重复劳动。另外一方面有些东西还是C语言处理起来比较方便,比如和底层驱动程序打交道等等。JAVA调用C/C++方法,就要把参数传递给C/C++代码,或者C/C++代码可以获取到JVM的内存数据, JAVA和C之间可以数据交互。 1、数据类型 JAVA和C/C++之间传递数据,就要有相互对应的数据类型。 首先是基本类型,每种语言都会有的,所以直接用typedef 定义完成: typedef unsigned char jboolean; typedef unsigned short jchar; typedef short jshort; typedef float jfloat; typedef double jdouble; typedef long jint; typedef __int64 jlong; typedef signed char jbyte; 在java里除此之外的对象都是继承于java.lang.Object。所以JNI里也定义了一个jobject的结构体。 这样java所有的对象都可以用jobject传递。 参考官方文档:http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html 为了方便使用,JNI还定义了一些特殊的常用对象,比如类对象jclass, 异常对象jthrowable,数组对象jarray等等。 不过这些对象都是空的,也就是你不通过调用它们的方法的方式去调用java里的方法。 如果想调用它们的方法,或者获取它们的一些属性。你需要把它们当作参数,传递给JNIENV的对应api。 比如你想获取一个数组的长度,你需要调用JNIENV的GetArrayLength方法: jsize GetArrayLength(jarray array) 2、JNIEnv指针。 在JNI自动生成的代码里,函数参数,必须有一个JNIEnv *指针,这个就是用来调用JVM的方法的。 关于JNIENV的定义可以查看jni.h。你会发现它是一个struct。 如果你是C++语言实现,那么它这个struct就相当于一个class,大家知道在C++里,struct和class是没有多大区别的,唯一的区别就默认访问权限的问题。这都不是很重要。重要的是struct里可以定义方法和属性。JNIENV里提供了一系列的api,供使用者调用。 如果是C语言实现的呢,会发现JNIENV也是一个struct,但是C语言是一种面向过程的语言,他没有对象的概念,它的struct里是不能定义方法的。但是有一个叫“函数指针”的东西,可以让C语言实现一种面向对象的感觉,JNI就是这样做到的,它在struct里定义了一系列的函数指针,用于访问JVM。 这样无论C,还是C++。都可以使用JNIENV的api访问jvm。 jni.h里是通过__cplusplus控制的 都有那些方法,可以查看oracle官方文档: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html 3、共享内存。 Java除了提供参数传递的方式,和C/C++交互数据之外,还有一种共享数据的方式就是共享内存。Java里可以直接申请JVM的堆外的内存,和C语言共用。 那就是java里的一个特殊类:ByteBuffer,它的allocateDirect方法,可以直接在JVM之外的内存中开辟空间。 这种开辟内存的方式要比直接申请JVM堆内内存耗时多,所以这种方式一般用于使用比较频繁,可以重复使用的场景。 比如java和c之间有大量数据要交互,那么就可以使用ByteBuffer申请一块一定大小内存作为缓存。使用ByteBuffer可以大量减少JVM和C语言之间的内存copy产生的开销 下一篇学习ByteBuffer的实际使用。

2016年5月29日 · 1 分钟

JAVA简单调用C/C++语言(JNI学习二)

一个maven的典型目录结构: src/main/java src/main/resources src/test/java src/test/resources 根据含义src代码源码库, main代表主代码 test表示测试代码。java表示java部分。 那么如果再加上C语言部分,那目录可以这样安排 src/main/java src/main/c — c语言部分 src/test/java src/test/c — c语言测试代码 src/test/resources include —引用的其它c语言头文件 lib —引用的其它动态链接库 使用maven编译c语言,还是要用插件: org.fusesource.hawtjni maven-hawtjni-plugin 1.13 这个插件会帮你生成一个c语言工程(主要是configure文件和makefile文件),并帮你编译成so文件,并放到jar包的指定目录下,非常好用。 示例配置如下: org.fusesource.hawtjni maven-hawtjni-plugin 1.10 build-linux64 ${project.artifactId} ${project.build.directory}/linux64 ${basedir}/src/main/c ${basedir}/target/classes/ --with-arch=x86_64 CFLAGS=-I${basedir}/include LDFLAGS=-L${basedir}/lib LIBS=-lXXXX linux64 true true generate build compile configureArgs 里包含了makefile需要的参数,其中XXXX表示你依赖的库名,也可以加上更多你需要的其它的参数。 假设你的工程名叫hello。执行mvn clean package 之后 maven会帮你在你的target/native-build目录下生成一个c语言工程,并执行相应操作系统的编译。 就比如上面的linux64位系统,最终会生成一个libhello.so文件到你的jar包的META-INF/native/linux64/ 目录下 你可以用各种手段使用System.loadLibrary加载它。 下一篇学习java和c语言之间的参数传递和方法调用。

2016年5月22日 · 1 分钟

JAVA简单调用C/C++语言(JNI学习一)

程序都是从hello world开始的,写一个简单的demo,java里调用c语言的方法输出一句hello world。 第一:首先实现一个java类:com.zhaoyanblog.Demo package com.zhaoyanblog; public class Demo { static{ System.loadLibrary("demo"); } public static void main(String[] args) { Demo demo = new Demo(); demo.printHelloWord(); } public native void printHelloWord(); } 声明为native的方法,jvm会主动从加载的library中对应寻找。 System.loadLibrary就是用于加载c语言编译的库文件。 在linux下面lib库文件的命名默认是: lib+{name}+.so java会到环境变量java.library.path对应的目录下寻找并加载。 第二: 编写c/c++语言部分 使用javah得到c/c++对应的头文件, 在com目录的父目录下执行 javah -jni javah -jni com.zhaoyanblog.Demo 会产生一个头文件 com_zhaoyanblog_Demo.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class com_zhaoyanblog_Demo */ #ifndef _Included_com_zhaoyanblog_Demo #define _Included_com_zhaoyanblog_Demo #ifdef __cplusplus extern "C" { #endif /* * Class: com_zhaoyanblog_Demo * Method: printHelloWord * Signature: ()V */ JNIEXPORT void JNICALL Java_com_zhaoyanblog_Demo_printHelloWord (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif 根据头文件完成源文件com_zhaoyanblog_Demo.cc ...

2016年5月14日 · 1 分钟

slf4j(简单日志门面)日志框架介绍(二)

日志门面的出现,目的就是在任何日志实现框架之间随意切换,而不需要改动一行代码。 所以如果你的代码中使用了slf4j-api打印日志。你可以通过更换适配包实现,更好不同的日志框架。 但是如果你的业务代码,或者依赖的sdk,使用了其它日志框架打印日志,例如commons-logging. 甚至直接使用了log4j等打印日志。怎么样切换到统一的日志框架呢? Slf4j还提供了另外一系列的包:叫做桥接(Bridging)包,原理就是替换log4j,jul,jcl等日志框架的包,把日志导向slf4j。桥接包的原理,就是重写对应日志框架的api和实现,内部使用slf4j打印日志。 下图是slf4j官方提供的: 桥接到slf4j 1、jcl/log4j->slf4j 把对应的commons-logging、log4j包删除。 增加jcl-over-slf4j-1.7.xx.jar和log4j-over-slf4j-1.7.xx.jar包 2、jul->slf4j 因为jdk的logging是无法删除的,所以jul-to-slf4j-1.7.xx.jar是无法通过替换api的方式,实现对jul的替换。它是提供了一个jdk的扩展org.slf4j.bridge.SLF4JBridgeHandler 需要配置到jdk的logger.properties中 handlers = org.slf4j.bridge.SLF4JBridgeHandler 3、log4j2->slf4j Log4j2到slf4j的桥接包,是log4j2官方提供的。叫做: log4j-to-slf4j-2.x.jar 但是log4j2也提供了占位符的形式的日志,所以日志消息也可能会在传递给slf4j之前要先对日志进行格式化,官方文档说这个过程可能存在一定的性能损失。所以不建议这么做。 日志框架切换举例 这样,通过slf4j,我们可以实现任何日志框架之间的切换,而不需要修改代码。举例说明: 1、之前是commons-logging+log4j 打日志,现在想用slf4j+logback打日志。 删除 commons-logging-1.x.jar log4j-1.2.xx.jar 增加 jcl-over-slf4j-1.7.xx.jar slf4j-api-1.7.xx.jar logback-classic-1.1.x.jar logback-core-1.1.x.jar 即可完成切换 2、之前直接用log4j打日志,现在想用log4j2打日志 删除 log4j-1.2.xx.jar 增加 log4j-over-slf4j-1.7.xx.jar slf4j-api-1.7.xx.jar log4j2-api-2.x.jar log4j2-core-2.x.jar 即可完成切换

2016年3月5日 · 1 分钟

JAVA日志框架分类简介

JAVA的日志框架分两类: 一类是日志门面,它定义了一组日志的接口规范,并未提供底层实现。例如slf4j 另外一类是日志实现,它实现日志具体实现,包括日志级别控制,日志格式,打印日志到文件,到屏幕,甚至到数据库等日志的种种具体功能,例如log4j。 日志门面是不能单独使用的,它必须和一种具体的日志实现框架相结合使用。日志门面和日志实现的分离,可以让业务使用不同的日志实现框架之间切换,而不需要改动任何代码,只要掌握日志门面的接口文档,也不需要新的日志实现的接口学习代价。 也就是编码模式里所谓的“门面模式”。 日志实现框架可以直接用于打印日志,但是一般不会这样做,因为这样回带来一定的麻烦,例如一个SDK包使用log4j打日志,而一个业务引用了这个SDK,但是业务开发者喜欢使用logback打日志。那么就会出现一个业务使用两款甚至多款日志框架并存,而且要维护多个日志配置文件的局面。 所以,我们都是用日志门面打日志。

2016年2月21日 · 1 分钟

slf4j(简单日志门面)日志框架介绍(一)

Slf4j的原理 值得一提的是slf4j的作者,同时也是log4j和logback的开发者,名叫Ceki Gülcü。 slf4j和commons-logging的作用和基本原理是一样的,在slf4j-api中提供了两个接口: org.slf4j.Logger org.slf4j.ILoggerFactory 但是相比commons-logging, slf4j有两个不同之处,也可以说为优点: 第一:slf4j对ILoggerFactory的寻找上,是静态绑定,它必须存在一个类org.slf4j.impl.StaticLoggerBinder,指明使用的哪个日志框架,loggerfactory是那个。 为此,slf4j对几乎所有的日志框架都提供一个适配(adaptation)包,里面包含loggerfactory和logger的实现。以及一个org.slf4j.impl.StaticLoggerBinder类 使用slf4j和其它日志框架,只要引入slf4j-api包,以及对应的适配包即可。 下图是官方提供的一张图,slf4j绑定不同的日志实现: 第二:slf4j提供了一种占位符的接口形式,从而避免对字符串不必要的拼接。 举例: 如果用commons-logging打日志: log.info(“my name is” + name +”,my age is ”+ age); 如果log的日志级别配置的是ERROR,那么这句日志就不会输出,但是代码中的字符串拼接操作还是会执行。 为此,我们必须这样写: If ( log. isInfoEnabled() ) { log.info(“my name is” + name +”,my age is ”+ age); } 如果你使用slf4j, 你就可以优雅的这样写: log.info(“my name is {}, my age is {}.”, name, age);

2016年2月21日 · 1 分钟

java通用日志框架commons-logging介绍

commons-logging简称jcl。是apache较早提供的日志通用框架,它规定了一系列的接口, 它可以自动寻找到你的工程里使用的日志框架进行打日志。前提是你要为它提供: org.apache.commons.logging.Log org.apache.commons.logging.LogFactory 两个接口或者是抽象类的实现。 其它日志框架,如果能让commons-logging识别,只能去实现它的接口 org.apache.commons.logging.Log org.apache.commons.logging.LogFactory 并且告诉commons-logging你的LogFactory。 让commons-logging加载你的LogFactory,有两种途径: 第一:设置环境变量 -Dorg.apache.commons.logging.LogFactory=com.huawei.myLogFactory 第二:把com.huawei.myLogFactory写入META-INF/services/org.apache.commons.logging.LogFactory文件中 第三:在classpath下创建一个commons-logging.properties文件写入: org.apache.commons.logging.LogFactory=com.huawei.myLogFactory 其它日志框架,一般使用第二种方式支持commons-logging。Commons-logging对logFactory的寻找过程,可以查阅org.apache.commons.logging.LogFactory类源码. 因为commons-logging是在运行时,通过类查找的方式找到logFactory的实现,并且通过反射的形式获得其实例。所以commons-logging和日志实现框架的绑定是一种动态绑定。 1、commons-logging->log4j/jul 其中:jdk log 和log4j的log和logfactory。commons-logging自己已经提供: Logfactory: org.apache.commons.logging.impl.LogFactoryImpl log: org.apache.commons.logging.impl.Log4JLogger org.apache.commons.logging.impl.Jdk14Logger 所以如果你打算用log4j打日志,只要把log4j的包引入,commons-logging就可以使用log4j打日志了。 如果你不提供log4j的包,commons-logging就默认使用JDK的java.util.logging打日志。 2、commons-logging->log4j2 log4j2 官方提供了commons-logging的Log和LogFactory的实现包。 如果使用commons-logging+log4j2打日志,需要额外引入 log4j-jcl-2.x.jar 包 org.apache.logging.log4j log4j-jcl 2.x 3、commons-logging->logback 很遗憾logback没有提供对commons-logging的支持,因为当logback出现的时候,已经有一个更好的日志门面框架出现了,就是slf4j。

2016年2月20日 · 1 分钟

使用javamail发邮件

使用javamail发邮件。 javamail是java发邮件的常用工具。方便实用。 1、首先使用maven工程,需要引入对javamail的依赖,取个最新稳定版本: javax.mail mail 1.4.5 2、构造一个Session。所谓Session,可以理解为对SMTP服务器的配置。如果发邮件的全部都是一个配置,这个是可以单例的。 Properties props = new Properties(); props.put("mail.smtp.host", "smtp.163.com");//smtp服务器 props.put("mail.smtp.auth", "true");//是否需要用户名密码鉴权 props.put("mail.transport.protocol", "smtp");//协议名称 props.put("mail.smtp.socketFactory.port", 25); //服务器端口 props.put("mail.smtp.starttls.enable", "true"); //如果是ssl端口,需要加这个属性。 //创建Session。这个取一次就好了, Session session = Session.getInstance(props, new Authenticator() //如果你只有一个发送邮箱,在这里指定就好了,如果你有多个用户名密码,也可以在正式发邮件的时候指定。 { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("zhaoyan", "zhaoyanblog.com");//用户名和密码, } }); 3、获取一个连接发送邮件: Transport transport = null; try { transport = session.getTransport("smtp"); //这里是不能单例使用的,可以看Transport的实现类SMTPTransport, //有几个成员是:private MimeMessage message; private Address[] addresses; private SMTPOutputStream dataStream; 说明每次发邮件都会存这些东西的,这个类是线程不安全的。 transport.connect(); //如果session里没有指定用户名密码,这里可以使用transport.connect("username","password"); MimeMessage message = createMessage();//构造邮件内容 transport.sendMessage(message, new InternetAddress[]{new InternetAddress("zhaoyan@zhaoyanblog.com")}); //如果你的message里指定了接收方,这里也可以写transport.sendMessage(message); } finally { transport.close(); } 4、发送不同类型的短信 ...

2015年12月23日 · 1 分钟

JAVA实现多语言

做web服务器,为了实现全球业务,服务器端就要实现多语言。针对不同的国家、语言。返回不同语言的描述。 第一:JAVA对多语言是支持的 JAVA对语言的描述使用java.util.Locale,它主要包含语言、国家信息。 例如几个常量, 中国的简体中文: Locale SIMPLIFIED_CHINESE = createConstant(“zh”, “CN”); 台湾的繁体 Locale TRADITIONAL_CHINESE = createConstant(“zh”, “TW”); 也可以只包含语言信息: Locale CHINESE = createConstant(“zh”, “”); 以上几种语言,用字符串写就是zh_CN, zh_TW, zh(也有用中划线的)。 第二:语言资源文件 你可以在i18n目录(或者是com/zhaoyanblog/i18n, com/zhaoyanblog/i18n/resource, 这个是类路径)下为每一种语言创建一个资源文件。命名格式如下: lang_zh_CN.properties lang_zh_TW.properties lang_zh.properties 再创建一个默认的语言对应的资源文件: lang.properties 注:这里的文件名lang和路径都可以随便起。为什么路径要包含i18n,这个就是个习惯,随便你。i18n的意思就是国际化(internationalization, i和n之间有18个字符)。 资源文件里配置不同的配置 lang_zh_CN.properties里: TITLE=我的苹果 lang_en.properties里: TITLE=my apple 配置文件为了防止乱码,properties文件里3个字节以上表示的字符(例如中文)一般使用unicode配置。 TITLE=\u6211\u7684\u82f9\u679c 第三:读取配置文件的内容 JAVA加载语言资源文件使用java.util.ResourceBundle类。 ResourceBundle lang = ResourceBundle.getBundle(“com.zhaoyanblog.i18n.resource.lang”, Locale.SIMPLIFIED_CHINESE); lang.getString(“TITLE”) 返回的就是我的苹果 第四:HTTP协议的多语言支持。 HTTP协议规定了一个header:Accept-Language支持多语言。多少客户端期望返回的语言类型。 格式如下: Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 q=0.8代表前面语言的比重。如果有中文就返回中文,没有中文就返回美式英文。再没有就返回中文。 第五:java web应用,获取客户端需要的语言。 servlet的HttpServletRequest参数对象里可以得到对Accept-Language的解析结果。 protected void service(HttpServletRequest req, HttpServletResponse resp) { Locale locale = req.getLocale(); }

2015年12月15日 · 1 分钟

java产生随机uuid的性能

在java中产生uuid的方式是使用java.util.UUID。 UUID.randomUUID().toString(); 我在测试redis性能时,使用uuid产生测试数据,发现多线程测试redis的rpush接口的时候,性能老是上不去。 查看cpu利用率也不高,网卡流量也不大。就是tps上不去。但是如果用两台client去测,又可以达到更高的tps。 后来直接用jstack查看了下堆栈,发现大多数线程停留在: java.lang.Thread.State: BLOCKED (on object monitor) at java.security.SecureRandom.nextBytes(Unknown Source) - waiting to lock <0x00000005ffe1c548> (a java.security.SecureRandom) at java.util.UUID.randomUUID(Unknown Source) 原来uuid的生成遇到了性能瓶颈。于是我单独测试了下生成随机uuid的性能,发现无论是1个线程还是32个线程还是300个线程,它的tps只能到10万级别。 甚至是线程数越大,tps越低。tps在每个机器上都不一样,有的机器上测试tps只有5万。我们就以一台双核4G内存的虚拟机为例: tps在 140000+ 我们看randomUUID方法的javadoc的描述是: The UUID is generated using a cryptographically strong pseudo random number generator 也就是说uuid使用了一个强随机数,也也保证了uuid的不重复性。 public static UUID randomUUID() { SecureRandom ng=numberGenerator; if(ng == null) numberGenerator=ng=new SecureRandom(); byte[] randomBytes=new byte[16]; ng.nextBytes(randomBytes); return new UUID(randomBytes); } 再看SecureRandom的javadoc Note: Depending on the implementation, the generateSeed and nextBytes methods may block as entropy is being gathered, for example, if they need to read from /dev/random on various unix-like operating systems. ...

2015年7月10日 · 1 分钟

maven关于打包的那些插件

在工作中使用maven创建java工程,管理jar包依赖,方便快捷。根据需要,需要把工程打包成各种需要的形式,这些打包插件就用到了。 现将各种打包用到的插件总结到这里,你可以参考官方文档,修改或者增加适合你的参数。 第一个:打源码包 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.1</version> <configuration> <attach>true</attach> </configuration> <executions> <execution> <phase>compile</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> 第二个:打全量包 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.zhaoyanblog.Launcher</mainClass> </transformer> <!--防止spring多个包同名配置文件覆盖--> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer </transformers> </configuration> </execution> </executions> </plugin> 第三个:copy依赖jar包 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <includeScope>runtime</includeScope> <outputDirectory>target/lib/</outputDirectory> </configuration> </execution> </executions> </plugin> 第四个:指定main方法,修改manifest文件 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.2</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib</classpathPrefix> <mainClass>com.zhaoyanblog.Launcher</mainClass> </manifest> <manifestEntries> <Class-Path>conf/</Class-Path> </manifestEntries> </archive> <finalName>tools-${project.version}</finalName> </configuration> </plugin> 第五个:打压缩包。 ...

2015年3月12日 · 1 分钟

使用Spring最简单的定时任务

我不喜欢用注解,我也不喜欢用quartz,我就要一个类似于Linux下面crontab的定时任务的功能。 下面的方式,我认为是最简单最直接的方式: 配置文件 <?xml version="1.0" encoding="UTF-8"?> 写一个定时任务 package com.zhaoyanblog class Myjob { public void run() { //TODO } } 注意: 第一:cron是六个位置,和linux下面的crontab 不一样,多了一个秒位。 第二:任务是阻塞式的,当前面的一个任务没有完成,或者scheduler线程数被占光了,都会导致后面的任务阻塞。

2015年3月8日 · 1 分钟

Jline实现java输入自动补全

java的控制台输入是很局限的,我们仅仅可得到它的输入,对于输入的过程是很难操控的,所以当我们想写一个人性化的输入体验的时候,是比较难实现的。还好java中有种jni的技术,它允许Java代码和其他语言写的代码进行交互。 Jline就是一个使用了C/C++实现的java类库,它可以让你更方便的处理控制台输入。 引入Jline,使用下面的maven配置 <dependency> <groupId>jline</groupId> <artifactId>jline</artifactId> <version>2.9</version> </dependency> 我们平常处理输入一般是这样的: BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = null; do { line = br.readLine(); if(line != null) { //todo } } while(line!=null && !line.equals("exist")) 在使用了Jline之后,一样的书写形式: ConsoleReader reader = new ConsoleReader(); String line = null; do { line = reader.readLine("input>"); if(line != null) { //TODO } } while(line!=null && !line.equals("exist")) 你便拥有了一个比较友好的输入体验了。它可以实现光标的移动,通过上下方向键切换历史命令等操作。 如果你希望有更强大的自动补全功能。你可以给ConsoleReader 设一个jline.console.completer.Completer接口实现类 public interface Completer int complete(String buffer, int cursor, List candidates); } 其中buffer是当前用户输入的内容,cursor表示光标的位置,candidates表示你想补全的候选项。返回值很重要,表示你要再哪个位置补全你的内容 假设用户的输入是ls my xxxxxx 目前用户的光标在my后面,你想帮用户补全为myfoler或者myfile。你要这么写: ...

2015年2月7日 · 1 分钟