年度归档: 2016 年

  • Travis CI使用Ubuntu trusty

    之前Travis CI支持的Ubuntu 12.X,一般使用还好,但是对于有UI的,需要xvfb的有时候会有点问题。

    现在直接支持Ubuntu Trusty了。使用方法

    sudo: required
    dist: trusty

    其他照旧。…

  • 使用Travis CI的Env特性测试版本兼容性

    Libgdx一直没有官方的UI编辑器,而cocostudio作为编辑器的能力让人羡慕。

    有一个简单的办法就是使用cocostudio,导出项目,然后构建一个LIbgdx的runtime解析器,比如这个项目https://github.com/tianqiujie/cocostudio-ui-for-libgdx

    Libgdx有很多版本,如果能够长久保持一定版本的支持(比如最新版为1.9.2,可以考虑支持1.7.0及以上)。

    Travis提供了Env功能,可以配置多个环境变量,比如这里以版本为变量

    language: java
    
    jdk:
      - oraclejdk7
    
    env:
     - GDX_VERSION=1.9.2
     - GDX_VERSION=1.9.1
     - GDX_VERSION=1.9.0
     - GDX_VERSION=1.8.0
     - GDX_VERSION=1.7.0
    
    before_install:
     - chmod +x gradlew
    

    然后在Gradle配置中修改一下,改成先判断环境变量,如果没有就是用指定版本1.9.2,这样开发人员在本地不需要其他配置也可以让项目正常运行。

    ext {
        gdxVersion = System.env.GDX_VERSION != null ? System.env.GDX_VERSION : '1.9.2';
    }
    

    然后在Travis CI中的执行效果如下…

  • 快速将csv转为Spark Dataframe

    CSV,或者叫逗号分隔值,是以逗号为分隔符,简单而使用。虽然并没有真正的标准,但是RFC 4180中有一个大致的表述。

    很多时候我们拿到的原始数据都是csv的,而快速将其转为Spark的Dataframe做进一步分析就是一个经常遇到的问题。

    先来一个简单的例子,这里以手淘的数据为例子

    spark-dataframe-1

    共有六列。

    先建立一个简单对象Record,然后直接用Spark的createDataFrame方法

    JavaRDD<Record> list = sc.textFile(userFile).map(new Function<String, Record>() {
    	@Override
    	public Record call(String v1) throws Exception {
    		String[] split = v1.split(",");
    		return new Record(split[0], split[1], split[2], split[3], split[4], split[5]);
    	}
    });
    DataFrame dataFrame = sqlContext.createDataFrame(list, Record.class);
  • 创建展示项目基本信息的Endpoint

    Spring Boot中的Endpoint可以提供很多便利信息,但是有时候自带的信息不能满足需要,就需要我们自己创建一个Endpoint。

    对于一个项目而言我们希望能够了解到一些基本的信息,比如项目的构建环境,git仓库信息,CI的build number等等。

    Netflix的nebula项目提供了很多gradle的插件,而搜集信息我们可以使用gradle-info-plugin。

    buildscript {
        repositories { jcenter() }
        dependencies { classpath 'com.netflix.nebula:gradle-info-plugin:3.+' }
    }
    
    apply plugin: 'nebula.info'

    这样在gradle构建项目的时候就会自动搜集信息,并存储在Properties文件中。这样就可以自定义一个Endpoint来展示这些信息。

    import com.google.common.collect.Maps;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
    
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    import java.util.HashMap;
    import java.util.Map;
    import 
  • Spring Framework 4.3 注入机制的改进

    Spring Framework 4.3有两个有趣的改动,第一个是单一构造函数的自动注入,第二个就是注入依赖条件的配置。

    来看看之前版本的代码

    @Service
    public class FooService {
    
        private final FooRepository repository;
    
        @Autowired
        public FooService(FooRepository repository) {
            this.repository = repository
        }
    }

    @Autowired注解是一个很关键的东西,如果你忘记了,那么运行就会出现null错误。在新版本中,你无需这个注解了,Spring会自动处理这种情况。当然,这个功能同样对@Configuration等有效。

    新的ObjectProvider可以提供两个特别的方法getIfAvailable和getIfUnique。比如这种

    @Service
    public class FooService {
    
        private final FooRepository repository;
    
        public FooService(ObjectProvider<FooRepository> repositoryProvider) {
            this.repository = 
  • Spring Security从表单验证到token验证

    对于任何需要认证的系统,Spring Security无疑是一个很好的选择。

    无论是从认证本身,还是权限控制,Spring Security都有极好的支持。

    Form登录是一种极为常见的方式,用户输入用户名和密码,post登录信息,认证之后登录信息保存在session之中。

    但是随着架构演进,前后端逐渐分离,之前的大部分逻辑都变成了API供前端调用,而Form登录逐渐成为累赘,调试上的困难。这个时候就可以考虑换为token模式,进而将后端API stateless化。

    Token的生成和解析

    第一步是选择token的生成和解析,我们来个简单的,token中只包含加密后的用户名,具体生成才是JWT的HS512加密。

    先配置一个登录成功后的响应AuthSuccessHandler

    public class AuthSuccessHandler implements AuthenticationSuccessHandler {
        public static final String SECRET_KEY = "0Qx*@S7q";
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                            Authentication authentication) throws IOException, ServletException {
            response.setStatus(HttpStatus.OK.value());
            JsonNodeFactory factory = 
  • Spring Boot starter

    Spring Boot推出后取得了巨大的成功,方便快速上手,还附带了很多product-ready特性。对微服务架构也很友好。

    随着Spring Boot一起推出的还有Spring Boot众多的starter。当你喜欢使用某些组件时,试试starter,只需要使用构建工具引用一个依赖,你就可以快速获得它。

    了解starter之后你也可以自己实现自己的starter,以便其他同事快速使用公司内部特有的组件。

    spring.factories

    每个starter都有一个spring.factories文件,位于META-INF目录下。

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\

    随便开一个文件来看

    @Configuration
    @ConditionalOnClass({ RabbitTemplate.class, Channel.class })
    @EnableConfigurationProperties(RabbitProperties.class)
    @Import(RabbitAnnotationDrivenConfiguration.class)
    public class RabbitAutoConfiguration {
    
    	@Bean
    	@ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
    	@ConditionalOnMissingBean(AmqpAdmin.class)
    	public 
  • Spring Boot输出日志到文件

    Spring Boot对于日志的支持是比较到位的,默认logback实现,输出到console。

    如果你的console支持ANSI,那么还可以选择彩色输出。

    很多时候可能并不只是希望日志能够输出到console,还希望能够保存到目录,甚至后期配合splunk等等做分析统计。

    Spring Boot会检测logging.file和logging.path是否存在,如果存在的话就会输出到文件中。这个配置可以在application.properties中设置。

    如果应用打包已经完成了,可以直接在命令行配置

    java -jar app.jar -Dlogging.path=/alidata/log

     …

  • Spring Boot的Banner

    Spring Boot应用在启动的时候会输出一个Banner,同时还会输出使用的Spring Boot的版本。

    默认的Banner如下

    spring-default-banner

    如果你需要自己替换的话可以放置一个banner.txt文件,这样Spring Boot就会使用自定义的banner,而不是默认的banner。

    Spring Banner的选择逻辑

    Banner的优先级和具体逻辑在SpringApplication中。

    	private Banner selectBanner(Environment environment) {
    		String location = environment.getProperty(BANNER_LOCATION_PROPERTY,
    				BANNER_LOCATION_PROPERTY_VALUE);
    		ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
    				: new DefaultResourceLoader(getClassLoader());
    		Resource resource = resourceLoader.getResource(location);
    		if (resource.exists()) {
    			return new ResourceBanner(resource);
    		
  • 包含Spring Data Rest到mappings endpoint中

    Spring Boot提供了一套方便使用的endpoint,暴露了常用的一些应用信息,比如路由情况,堆栈情况等等,灵活的使用不仅可以加快调试,也可以方便运维。

    RequestMappingEndpoint类提供了地址为mappings的endpoint,提供了是当前的路由情况,什么样的路径解析到什么样的执行器中。大概的输出如下

    mapping-endpoint-sample

    当然,任何你自己定义的Controller等等,都会被自动包含到这个mappings中。但是如果你使用Spring Data Rest,你就会发现它的相关信息并没有被包含到这个里面。

    Mappings Endpoint原理

    要解决这个问题,就需要先来看看RequestMappingEndpoint是如何工作的。

    首先它并没有缓存,这意味着每次访问的时候它都会调用invoke方法来获取信息

    public Map<String, Object> invoke() {
         ....
    }

    而Spring中的路由的映射大致讲只有两种,一种是UrlHandlerMapping,一种是MethodMapping。我们最常用的Controller配合RequestMapping的属于后者。

    而处理这两种情况都是从applicationContext中获取所有实例,然后提取信息。唯一的不同是两种映射的对应信息保持方式不同而已。

    Map <String,AbstractUrlHandlerMapping > mappings = applicationContext.getBeansOfType(AbstractUrlHandlerMapping.class);
    for (Entry < String, AbstractUrlHandlerMapping > mapping: mappings.entrySet()) {
        Map < String, Object > handlers