• 2017技术书单

    读书对于每个行业的人来说都是一个能提升个人见识和能力的途径。一本好书浓缩了作者对于一门技术或者一个事物吸收后的想法和见解。现在越来越多人都习惯了碎片化阅读,即通过微信公众号或者博客来阅读文章。这对于时间利用率来说,是一种好的习惯。但是对于去学习一门技术却不是体系的学习方法(其实学习技术最好的方法是有了一定入门知识后阅读文档debug源码)。自己就试过工作中同时用到Mina和Netty,而且都是碎片化学习,在一次面试中面试官问Netty的东西自己答到了Mina上面。

    特别是纸质书,每次看纸质书都有一种面朝大海,心旷神怡的感觉。看纸质书也成了自己减压的一种方式。而且喜欢在书上做笔记加一些自己的理解,当然也在用有道云笔记做笔记。只是有时候工作上或者生活中的事情会把看书的节奏打乱,很少完整地看完一本书,所以在这里立了个《2017技术书单》的flag,目标是2017年12月31日前所有红色的item都消失:

    1.《Netty实战》,和《Netty权威指南》都是值得推荐的书,平时自己会看一些Netty的源码实现,不过缺少Netty框架静脉疏通。

    2.《Java微服务》网站架构趋势:单体应用架构 —> 垂直MVC架构 —> 分布式架构 —> 微服务架构。

    3.《Java并发编程的艺术》,从底层方面讲解Java并发,例如volatile关键字从汇编lock指令讲解到CPU通过嗅探技术令到自己的局部缓存失效,还有Doug Lea大神用了15个无关对象来刚好填充满高速缓存行(64K)的LinkedTransferQueue。

    4.《深入理解Java虚拟机》,第2次阅读,第1次阅读加有标志不是很了解的地方是重点对象。

    5.《深入理解计算机系统(原书第2版)》,这本书其实买了一年多(现在已经出到第3版),只是选读了部分,现在重新完整看一遍。

    6.《TCP/IP详解:卷1》,之前也是选读了一部分

    7.《数据挖掘与机器学习》,虽然做的是Java开发,不过一直对数据方面充满敏锐性;大学期间也有一门选修课选了《人工智能》,不过学的都是比较简单的内容,希望进一步学习来扩展自己的视野。

    8.《机器学习》,JD上面机器学习评价最好之一的书(其实技术类书籍评价在china-pub上面更权威一些,毕竟JD的评价还有物流等。

    9.《TensorFlow: 实战Google深度学习框架》,  相信大家还记得Google的 AlphaGo。

    booklist2

  • Redis的缓存策略和主键失效机制

    作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略。

    >>EXPIRE主键失效机制

    在Redis当中,有生存期的key被称为volatile,
    在创建缓存时,要为给定的key设置生存期,当key过期的时候(生存期为0),它可能会被删除。

    (1)影响生存时间的一些操作

    生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆盖原来的数据,

    也就是说,修改key对应的value和使用另外相同的key和value来覆盖以后,当前数据的生存时间不同。

    比如说,对一个 key 执行INCR命令,对一个列表进行LPUSH命令,或者对一个哈希表执行HSET命令,这类操作都不会修改 key 本身的生存时间。

    另一方面,如果使用RENAME对一个 key 进行改名,那么改名后的 key 的生存时间和改名前一样。

    RENAME命令的另一种可能是,尝试将一个带生存时间的 key 改名成另一个带生存时间的 another_key ,这时旧的 another_key (以及它的生存时间)会被删除,然后旧的 key 会改名为 another_key ,因此,新的 another_key 的生存时间也和原本的 key 一样。
    使用PERSIST命令可以在不删除 key 的情况下,移除 key 的生存时间,让 key 重新成为一个persistent key 。

    (2)如何更新生存时间

    可以对一个已经带有生存时间的 key 执行EXPIRE命令,新指定的生存时间会取代旧的生存时间。
    过期时间的精度已经被控制在1ms之内,主键失效的时间复杂度是O(1),
    EXPIRE和TTL命令搭配使用,TTL可以查看key的当前生存时间
    设置成功返回 1;当 key 不存在或者不能为 key 设置生存时间时,返回 0 。

    >>最大缓存配置

    在 redis 中,允许用户设置最大使用内存大小

    1
    server.maxmemory

    默认为0,没有指定最大缓存,如果有新的数据添加,超过最大内存,则会使redis崩溃,所以一定要设置。
    redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。

    redis 提供 6种数据淘汰策略:

    volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
    volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
    volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
    allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
    allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
    no-enviction(驱逐):禁止驱逐数据

    注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,
    后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。

    使用策略规则:

    (1)如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru。
    (2)如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random。

    三种数据淘汰策略:

    ttl和random比较容易理解,实现也会比较简单。主要是Lru最近最少使用淘汰策略,设计上会对key 按失效时间排序,然后取最先失效的key进行淘汰。

    >>失效的内部实现

    Redis 删除失效主键的方法主要有两种:

    消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它
    积极方法(active way),周期性地从设置了失效时间的主键中选择一部分失效的主键删除
    主键具体的失效时间全部都维护在expires这个字典表中。

    1
    2
    3
    4
    5
    6
    7
    8
    typedef struct redisDb {
        dict *dict; //key-value
        dict *expires;  //维护过期key
        dict *blocking_keys;
        dict *ready_keys;
        dict *watched_keys;
        int id;
    } redisDb;

    (1)passive way 消极方法

    在passive way 中, redis在实现GET、MGET、HGET、LRANGE等所有涉及到读取数据的命令时都会调用 expireIfNeeded,它存在的意义就是在读取数据之前先检查一下它有没有失效,如果失效了就删除它。
    expireIfNeeded函数中调用的另外一个函数propagateExpire,这个函数用来在正式删除失效主键之前广播这个主键已经失效的信息,这个信息会传播到两个目的地:
    一个是发送到AOF文件,将删除失效主键的这一操作以DEL Key的标准命令格式记录下来;
    另一个就是发送到当前Redis服务器的所有Slave,同样将删除失效主键的这一操作以DEL Key的标准命令格式告知这些Slave删除各自的失效主键。从中我们可以知道,所有作为Slave来运行的Redis服务器并不需要通过消极方法来删除失效主键,它们只需要执行Master的删除指令即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    int expireIfNeeded(redisDb *db, robj *key) {
       // 获取主键的失效时间
        long long when = getExpire(db,key);
        //假如失效时间为负数,说明该主键未设置失效时间(失效时间默认为-1),直接返回0
        if (when < 0) return 0;
       // 假如Redis服务器正在从RDB文件中加载数据,暂时不进行失效主键的删除,直接返回0
        if (server.loading) return 0;
       // 假如当前的Redis服务器是作为Slave运行的,那么不进行失效主键的删除,因为Slave
      //  上失效主键的删除是由Master来控制的,但是这里会将主键的失效时间与当前时间进行
       // 一下对比,以告知调用者指定的主键是否已经失效了
        if (server.masterhost != NULL) {
            return mstime() > when;
        }
        //如果以上条件都不满足,就将主键的失效时间与当前时间进行对比,如果发现指定的主键
       // 还未失效就直接返回0
        if (mstime() <= when) return 0;
       // 如果发现主键确实已经失效了,那么首先更新关于失效主键的统计个数,然后将该主键失
       // 效的信息进行广播,最后将该主键从数据库中删除
        server.stat_expiredkeys++;
        propagateExpire(db,key);
        return dbDelete(db,key);
    }
     
    void propagateExpire(redisDb *db, robj *key) {
        robj *argv[2];
       // shared.del是在Redis服务器启动之初就已经初始化好的一个常用Redis对象,即DEL命令
        argv[0] = shared.del;
        argv[1] = key;
        incrRefCount(argv[0]);
        incrRefCount(argv[1]);
      //  检查Redis服务器是否开启了AOF,如果开启了就为失效主键记录一条DEL日志
        if (server.aof_state != REDIS_AOF_OFF)
            feedAppendOnlyFile(server.delCommand,db->id,argv,2);
        //检查Redis服务器是否拥有Slave,如果是就向所有Slave发送DEL失效主键的命令,这就是
       // 上面expireIfNeeded函数中发现自己是Slave时无需主动删除失效主键的原因了,因为它
      //  只需听从Master发送过来的命令就OK了
        if (listLength(server.slaves))
            replicationFeedSlaves(server.slaves,db->id,argv,2);
        decrRefCount(argv[0]);
        decrRefCount(argv[1]);
    }

    (2)Active Way 积极方法

    消极方法的缺点是,如果key 迟迟不被访问,就会占用很多内存空间,所以就出现了积极的方式(Active Way),

    此方法利用了redis的时间事件,即每隔一段时间就中断一下完成一些指定操作,其中就包括检查并删除失效主键。

    A.时间事件

    创建时间事件, 回调函数就是serverCron,它在Redis服务器启动时创建,每秒的执行次数由宏定义REDIS_DEFAULT_HZ来指定,默认每秒钟执行10次。

    1
    2
    3
    4
    5
    //该代码在redis.c文件的initServer函数中。实际上,serverCron这个回调函数不仅要进行失效主键的检查与删除,还要进行统计信息的更新、客户端连接超时的控制、BGSAVE和AOF的触发等等,这里我们仅关注删除失效主键的实现,也就是函数activeExpireCycle。
    if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
            redisPanic(“create time event failed”);
            exit(1);
    }

    B.使用activeExpireCycle 清除失效key

    其实现原理是从Redis中每个数据库的expires字典表中,随机抽样REDIS_EXPIRELOOKUPS_PER_CRON(默认值为10)个设置了失效时间的主键,检查它们是否已经失效并删除掉失效的主键,如果失效主键个数占本次抽样个数的比例超过25%,它会继续进行下一轮的随机抽样和删除,直到刚才的比例低于25%才停止对当前数据库的处理,转向下一个数据库。

    注意,activeExpireCycle函数不会试图一次性处理Redis中的所有数据库,而是最多只处理REDIS_DBCRON_DBS_PER_CALL(默认值为16),此外activeExpireCycle函数还有处理时间上的限制,不是想执行多久就执行多久,凡此种种都只有一个目的,那就是避免失效主键删除占用过多的CPU资源。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    void activeExpireCycle(void) {
        /*因为每次调用activeExpireCycle函数不会一次性检查所有Redis数据库,所以需要记录下
            每次函数调用处理的最后一个Redis数据库的编号,这样下次调用activeExpireCycle函数
            还可以从这个数据库开始继续处理,这就是current_db被声明为static的原因,而另外一
            个变量timelimit_exit是为了记录上一次调用activeExpireCycle函数的执行时间是否达
            到时间限制了,所以也需要声明为static
        */
        static unsigned int current_db = 0;
        static int timelimit_exit = 0;
        unsigned int j, iteration = 0;
     
        /**
            每次调用activeExpireCycle函数处理的Redis数据库个数为REDIS_DBCRON_DBS_PER_CALL
            unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
            long long start = ustime(), timelimit;
            如果当前Redis服务器中的数据库个数小于REDIS_DBCRON_DBS_PER_CALL,则处理全部数据库,
            如果上一次调用activeExpireCycle函数的执行时间达到了时间限制,说明失效主键较多,也
            会选择处理全部数据库
        */
        if (dbs_per_call > server.dbnum || timelimit_exit)
            dbs_per_call = server.dbnum;
     
        /*
            执行activeExpireCycle函数的最长时间(以微秒计),其中REDIS_EXPIRELOOKUPS_TIME_PERC
            是单位时间内能够分配给activeExpireCycle函数执行的CPU时间比例,默认值为25,server.hz
            即为一秒内activeExpireCycle的调用次数,所以这个计算公式更明白的写法应该是这样的,即
                (1000000 * (REDIS_EXPIRELOOKUPS_TIME_PERC / 100)) / server.hz
        */
        timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/server.hz/100;
        timelimit_exit = 0;
        if (timelimit <= 0) timelimit = 1;
     
        //遍历处理每个Redis数据库中的失效数据
        for (j = 0; j < dbs_per_call; j++) {
            int expired;
            redisDb *db = server.db+(current_db % server.dbnum);
          // 此处立刻就将current_db加一,这样可以保证即使这次无法在时间限制内删除完所有当前
          // 数据库中的失效主键,下一次调用activeExpireCycle一样会从下一个数据库开始处理,
           //从而保证每个数据库都有被处理的机会
            current_db++;
           // 开始处理当前数据库中的失效主键
            do {
                unsigned long num, slots;
                long long now;
               // 如果expires字典表大小为0,说明该数据库中没有设置失效时间的主键,直接检查下
              // 一数据库
                if ((num = dictSize(db->expires)) == 0) break;
                slots = dictSlots(db->expires);
                now = mstime();
              //  如果expires字典表不为空,但是其填充率不足1%,那么随机选择主键进行检查的代价
               //会很高,所以这里直接检查下一数据库
                if (num && slots > DICT_HT_INITIAL_SIZE &&
                    (num*100/slots < 1)) break;
                expired = 0;
                //如果expires字典表中的entry个数不足以达到抽样个数,则选择全部key作为抽样样本
                if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
                    num = REDIS_EXPIRELOOKUPS_PER_CRON;
                while (num–) {
                    dictEntry *de;
                    long long t;
                  //  随机获取一个设置了失效时间的主键,检查其是否已经失效
                    if ((de = dictGetRandomKey(db->expires)) == NULL) break;
                    t = dictGetSignedIntegerVal(de);
                    if (now > t) {
               // 发现该主键确实已经失效,删除该主键
                        sds key = dictGetKey(de);
                        robj *keyobj = createStringObject(key,sdslen(key));
                        //同样要在删除前广播该主键的失效信息
                        propagateExpire(db,keyobj);
                        dbDelete(db,keyobj);
                        decrRefCount(keyobj);
                        expired++;
                        server.stat_expiredkeys++;
                    }
                }
               // 每进行一次抽样删除后对iteration加一,每16次抽样删除后检查本次执行时间是否
              // 已经达到时间限制,如果已达到时间限制,则记录本次执行达到时间限制并退出
                iteration++;
                if ((iteration & 0xf) == 0 &&
                    (ustime()-start) > timelimit)
                {
                    timelimit_exit = 1;
                    return;
                }
            //如果失效的主键数占抽样数的百分比大于25%,则继续抽样删除过程
            } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
        }
    }

    >>Redis 的主键失效机制对系统性能的影响

    Redis 会定期地检查设置了失效时间的主键并删除已经失效的主键,但是通过对每次处理数据库个数的限制、activeExpireCycle 函数在一秒钟内执行次数的限制、分配给 activeExpireCycle 函数CPU时间的限制、继续删除主键的失效主键数百分比的限制,Redis 已经大大降低了主键失效机制对系统整体性能的影响,但是如果在实际应用中出现大量主键在短时间内同时失效的情况还是会产生很多问题,
    也就是缓存穿透的情况。

    >>如何避免大量主键在同一时间同时失效造成数据库压力过大

    合理的配置缓存可以增加系统的健壮性,避免缓存失效造成的事故。
    1.在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
    2.可以通过缓存reload机制,预先去更新缓存.
    2.不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
    3.做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。

    >>Memcached删除失效主键的方法与Redis有何异同?

    Memcached 在删除失效主键时采用的消极方法,即 Memcached 内部不会监视主键是否失效,而是在通过 Get 访问主键时才会检查其是否已经失效。
    其次,Memcached 与 Redis 在主键失效机制上的最大不同是,Memcached 不会像 Redis 那样真正地去删除失效的主键,而只是简单地将失效主键占用的空间回收。

    这样当有新的数据写入到系统中时,Memcached 会优先使用那些失效主键的空间。
    如果失效主键的空间用光了,Memcached 还可以通过 LRU 机制来回收那些长期得不到访问的空间,因此 Memcached 并不需要像 Redis 中那样的周期性删除操作,这也是由 Memcached 使用的内存管理机制决定的。

    同时, Redis 在出现 OOM时同样可以通过配置 maxmemory-policy 这个参数来决定是否采用 LRU 机制来回收内存空间。

    参见原文:

    https://yq.aliyun.com/articles/38441?spm=5176.100239.blogrightarea.11.yP0aqU

  • 使用Spring提供远程和WEB服务

    作者:xiuson, 原文发表于并发编程网:http://ifeve.com/docs-spring-remotin/

    24.1 介绍

    Spring提供了使用多种技术实现远程访问支持的集成类。远程访问支持使得具有远程访问功能的服务开发变得相当简单,而这些服务由普通的 (Spring) POJO实现。目前,Spring支持以下几种远程技术:

    • 远程方法调用(RMI)。通过使用RmiProxyFactoryBean和RmiServiceExporter,Spring同时支持传统的RMI(与java.rmi.Remote接口和java.rmi.RemoteException配合使用)和通过RMI调用器的透明远程调用(透明远程调用可以使用任何Java接口)。
    • Spring的HTTP调用器。Spring提供了一个特殊的远程处理策略,允许通过HTTP进行Java序列化,支持任何Java接口(就像RMI调用器)。相应的支持类是HttpInvokerProxyFactoryBean和HttpInvokerServiceExporter。
    • Hessian。通过HessianProxyFactoryBean和HessianServiceExporter,可以使用Caucho提供的基于HTTP的轻量级二进制协议来透明地暴露服务。
    • JAX-WS。Spring通过JAX-WS为web服务提供远程访问支持。(JAX-WS: 从Java EE 5 和 Java 6开始引入,作为JAX-RPC的继承者)
    • JMS。通过JmsInvokerServiceExporter和JmsInvokerProxyFacotryBean类,使用JMS作为底层协议来提供远程服务。
    • AMQP。Spring AMQP项目支持AMQP作为底层协议来提供远程服务。

    在讨论Spring的远程服务功能时,我们将使用以下的域模型和对应的服务:

    public class Account implements Serializable{
        private String name;
    
        public String getName(){
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    
    public interface AccountService {
    
        public void insertAccount(Account account);
    
        public List<Account> getAccounts(String name);
    
    }
    
    // the implementation doing nothing at the moment
    public class AccountServiceImpl implements AccountService {
    
        public void insertAccount(Account acc) {
            // do something...
        }
    
        public List<Account> getAccounts(String name) {
            // do something...
        }
    
    }

    我们将从使用RMI把服务暴露给远程客户端开始,同时讨论使用RMI的一些缺点。然后我们将继续演示一个使用Hessian的例子。

    24.2 使用RMI暴露服务

    使用Spring的RMI支持,你可以通过RMI基础架构透明地暴露你的服务。完成Spring的RMI设置后,你基本上具有类似于远程EJB配 置,除了没有对安全上下文传递和远程事务传递的标准支持。当使用RMI调用器时,Spring对这些额外的调用上下文提供了钩子,你可以在此插入安全框架 或者自定义的安全凭证。

    24.2.1 使用RmiServiceExporter导出服务

    使用RmiServiceExporter,我们可以把AccountService对象的接口暴露成RMI对象。可以使用RmiProxyFactoryBean或者在传统RMI服务中使用普通RMI来访问该接口。RmiServiceExporter明确支持使用RMI调用器暴露任何非RMI的服务。

    当然,我们首先需要在Spring容器中设置我们的服务:

    <bean id="accountService" class="example.AccountServiceImpl">
        <!-- any additional properties, maybe a DAO? -->
    </bean>

    下一步我们需要使用RmiServiceExporter来暴露我们的服务:

    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <!-- does not necessarily have to be the same name as the bean to be exported -->
        <property name="serviceName" value="AccountService"/>
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
        <!-- defaults to 1099 -->
        <property name="registryPort" value="1199"/>
    </bean>

    正如你所见,我们覆盖了RMI注册的端口号。通常你的应用服务器还维护一个RMI注册表,明智的做法是不要和它冲突。此外,服务名是用来绑定服务的。现在服务绑定在‘rmi://HOST:1199/AccountService’。我们将在客户端使用这个URL来链接到服务。

    Note:servicePort属性被省略了(默认值为0).这表示在与服务通信时将使用匿名端口.

    24.2.2 在客户端链接服务

    我们的客户端是一个使用AccountService来管理account的简单对象:

    public class SimpleObject {
    
        private AccountService accountService;
    
        public void setAccountService(AccountService accountService) {
            this.accountService = accountService;
        }
    
        // additional methods using the accountService
    
    }

    为了把服务链接到客户端上,我们将创建一个单独的Spring容器,包含这个简单对象和链接配置位的服务:

    <bean class="example.SimpleObject">
        <property name="accountService" ref="accountService"/>
    </bean>
    
    <bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    这就是我们为支持远程account服务在客户端所需要做的。Spring将透明地创建一个调用器并且通过RmiServiceExporter使得account服务支持远程服务。在客户端,我们用RmiProxyFactoryBean连接它。

    24.3 使用Hessian通过HTTP远程调用服务

    Hessian提供一种基于HTTP的二进制远程协议。它由Caucho开发的,可以在 http://www.caucho.com 找到更多有关Hessian的信息。

    24.3.1 为Hessian和co.配置DispatcherServlet

    Hessian使用一个自定义Servlet通过HTTP进行通讯。使用Spring的DispatcherServlet原理,从Spring Web MVC使用中可以看出,可以很容易的配置这样一个Servlet来暴露你的服务。首先我们要在你的应用里创建一个新的Servlet(以下摘录自web.xml):

    <servlet>
        <servlet-name>remoting</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>remoting</servlet-name>
        <url-pattern>/remoting/*</url-pattern>
    </servlet-mapping>

    你可能对Spring的DispatcherServlet很熟悉,这样你将需要在’WEB-INF’目录中创建一个名为’remoting-servlet.xml'(在你的servlet名称后) 的Spring容器配置上下文。这个应用上下文将在下一节中里使用。

    或者,可以考虑使用Spring中更简单的HttpRequestHandlerServlet。这允许你在根应用上下文(默认是’WEB-INF/applicationContext.xml’)中嵌入远程exporter定义。每个servlet定义指向特定的exporter bean。在这种情况下,每个servlet的名称需要和目标exporter bean的名称相匹配。

    24.3.2 使用HessianServiceExporter暴露你的bean

    在新创建的remoting-servlet.xml应用上下文里,我们将创建一个HessianServiceExporter来暴露你的服务:

    <bean id="accountService" class="example.AccountServiceImpl">
        <!-- any additional properties, maybe a DAO? -->
    </bean>
    
    <bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    现在我们准备好在客户端连接服务了。不必显示指定处理器的映射,所以使用BeanNameUrlHandlerMapping把URL请求映射到服务上:因此,服务将通过其包含的bean名称指定的URL导出 DispatcherServlet’s mapping (as defined above): ’http://HOST:8080/remoting/AccountService’ 或者, 在你的根应用上下文中创建一个HessianServiceExporter(比如在’WEB-INF/applicationContext.xml’中):

    <bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    在后一情况下, 在’web.xml’中为这个导出器定义一个相应的servlet,也能得到同样的结果:这个导出器映射到request路径/remoting/AccountService。注意这个servlet名称需要与目标导出器bean的名称相匹配。

    <servlet>
        <servlet-name>accountExporter</servlet-name>
        <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>accountExporter</servlet-name>
        <url-pattern>/remoting/AccountService</url-pattern>
    </servlet-mapping>

    24.3.3 在客户端上链接服务

    使用HessianProxyFactoryBean,我们可以在客户端链接服务。与RMI示例一样也适用相同的原理。我们将创建一个单独的bean工厂或者应用上下文,并指明SimpleObject使用AccountService来管理accounts的以下bean:

    <bean class="example.SimpleObject">
        <property name="accountService" ref="accountService"/>
    </bean>
    
    <bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
        <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    24.3.4 对通过Hessian暴露的服务使用HTTP基本认证

    Hessian的优点之一是,我们可以轻松应用HTTP基本身份验证,因为这两种协议都是基于HTTP的。你的正常HTTP 服务器安全机制可以通过使用web.xml安全功能来应用。通常,你不会为每个用户都建立不同的安全证书,而是在Hessian/BurlapProxyFactoryBean级别共享安全证书(类似一个JDBCDataSource)。

    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
        <property name="interceptors" ref="authorizationInterceptor"/>
    </bean>
    
    <bean id="authorizationInterceptor"
            class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
        <property name="authorizedRoles" value="administrator,operator"/>
    </bean>

    这个是我们显式使用了BeanNameUrlHandlerMapping的例子,并设置了一个拦截器,只允许管理员和操作员调用这个应用上下文中提及的bean。

    Note: 当然,这个例子并不表现出灵活的安全架构。有关安全性方面的更多选项,请查看Spring Security项目http://projects.spring.io/spring-security/。

    24.4 使用HTTP调用器暴露服务

    与使用自身序列化机制的轻量级协议Hessian相反,Spring HTTP调用器使用标准Java序列化机制通过HTTP暴露业务。如果你的参数或返回值是复杂类型,并且不能通过Hessian的序列化机制进行序列化,HTTP调用器就很有优势(请参阅下一节,以便在选择远程处理技术时进行更多考虑)。

    在底层,Spring使用JDK提供的标准工具或Commons的HttpComponents来实现HTTP调用。如果你需要更先进和更易用的功能,请使用后者。你可以参考 hc.apache.org/httpcomponents-client-ga/ 以获取更多信息。

    24.4.1 暴露服务对象

    为服务对象设置HTTP调用器基础架构类似于使用Hessian进行相同操作的方式。就象为Hessian支持提供的HessianServiceExporter,Spring的HTTP调用器提供了org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter。 为了在Spring Web MVC的DispatcherServlet中暴露AccountService(之前章节提及过), 需要在调度程序的应用程序上下文中使用以下配置:

    <bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    如Hessian章节部分所述,这个导出器定义将通过DispatcherServlet的标准映射工具暴露出来。 或者, 在你的根应用上下文中(比如’WEB-INF/applicationContext.xml’)创建一个HttpInvokerServiceExporter:

    <bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    此外,在’web.xml’中为该导出器定义相应的servlet ,其中servlet名称与目标导出器的bean名称相匹配:

    <servlet>
        <servlet-name>accountExporter</servlet-name>
        <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>accountExporter</servlet-name>
        <url-pattern>/remoting/AccountService</url-pattern>
    </servlet-mapping>

    如果你在一个servlet容器之外运行程序和使用Oracle的Java6, 那么你可以使用内置的HTTP服务器实现。你可以配置SimpleHttpServerFactoryBean和SimpleHttpInvokerServiceExporter在一起,像下面这个例子一样:

    <bean name="accountExporter"
            class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>
    
    <bean id="httpServer"
            class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
        <property name="contexts">
            <util:map>
                <entry key="/remoting/AccountService" value-ref="accountExporter"/>
            </util:map>
        </property>
        <property name="port" value="8080" />
    </bean>

    24.4.2 在客户端连接服务

    同样,从客户端连接业务与你使用Hessian所做的很相似。使用代理,Spring可以将你的HTTP POST调用请求转换成被暴露服务的URL。

    <bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
        <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    如前所述,你可以选择要使用的HTTP客户端。默认情况下,HttpInvokerProxy使用JDK的HTTP功能,但你也可以通过设置httpInvokerRequestExecutor属性来使用ApacheHttpComponents客户端:

    <property name="httpInvokerRequestExecutor">
        <bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/>
    </property>

    24.5 Web 服务

    Spring提供了对标准Java Web服务API的全面支持:

    • 使用JAX-WS暴露Web服务
    • 使用JAX-WS访问Web服务

    除了在Spring Core中支持 JAX-WS,Spring portfolio也提供了一种特性Spring Web Services,一种为契约优先和文档驱动的web服务所提供的方案,强烈建议用来创建现代化的,面向未来的web服务。

    24.5.1使用JAX- WS暴露基于servlet的web服务

    Spring为JAX-WS servlet的端点实现提供了一个方便的基类 – SpringBeanAutowiringSupport. 为了暴露我们的AccountService,我们扩展Spring的SpringBeanAutowiringSupport类并在这里实现了我们的业务逻辑,通常委派调用业务层。我们在Spring管理的bean里面简单地使用Spring的@Autowired 注解来表达这样的依赖关系。

    /**
     * JAX-WS compliant AccountService implementation that simply delegates
     * to the AccountService implementation in the root web application context.
     *
     * This wrapper class is necessary because JAX-WS requires working with dedicated
     * endpoint classes. If an existing service needs to be exported, a wrapper that
     * extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through
     * the @Autowired annotation) is the simplest JAX-WS compliant way.
     *
     * This is the class registered with the server-side JAX-WS implementation.
     * In the case of a Java EE 5 server, this would simply be defined as a servlet
     * in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting
     * accordingly. The servlet name usually needs to match the specified WS service name.
     *
     * The web service engine manages the lifecycle of instances of this class.
     * Spring bean references will just be wired in here.
     */
    import org.springframework.web.context.support.SpringBeanAutowiringSupport;
    
    @WebService(serviceName="AccountService")
    public class AccountServiceEndpoint extends SpringBeanAutowiringSupport {
    
        @Autowired
        private AccountService biz;
    
        @WebMethod
        public void insertAccount(Account acc) {
            biz.insertAccount(acc);
        }
    
        @WebMethod
        public Account[] getAccounts(String name) {
            return biz.getAccounts(name);
        }
    
    }

    我们的AccountServletEndpoint需要和Spring在同一个上下文的web应用里运行,以允许访问Spring的功能。为JAX-WS servlet端点部署使用标准规约是Java EE 5 环境下的默认情况。

    24.5.2 使用JAX-WS暴露单独web服务

    Oracle JDK 1.6附带的内置JAX-WS provider 使用内置的HTTP服务器来暴露web服务。Spring的SimpleJaxWsServiceExporter类检测所有在Spring应用上下文中配置有@WebService注解的bean,然后通过默认的JAX-WS服务器(JDK 1.6 HTTP服务器)导出。

    在这种场景下,端点实例将被作为Spring bean来定义和管理。它们将使用JAX-WS引擎来注册,但其生命周期将由Spring应用程序上下文决定。这意味着Spring的显示依赖注入可用于端点实例。当然通过@Autowired来进行注解驱动的注入也会起作用。

    <bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
        <property name="baseAddress" value="http://localhost:8080/"/>
    </bean>
    
    <bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint">
        ...
    </bean>
    
    ...

    AccountServiceEndpoint可能来自于Spring的SpringBeanAutowiringSupport,也可能不是。因为这里的端点是由Spring完全管理的bean。这意味着端点实现可能像下面这样没有任何父类定义 – 而且Spring的@Autowired配置注解仍然能够使用:

    @WebService(serviceName="AccountService")
    public class AccountServiceEndpoint {
    
        @Autowired
        private AccountService biz;
    
        @WebMethod
        public void insertAccount(Account acc) {
            biz.insertAccount(acc);
        }
    
        @WebMethod
        public List<Account> getAccounts(String name) {
            return biz.getAccounts(name);
        }
    
    }

    24.5.3 使用JAX-WS RI的Spring支持来暴露服务

    Oracle的JAX-WS RI被作为GlassFish项目的一部分来开发,它使用了Spring支持来作为JAX-WS Commons项目的一部分。这允许把JAX-WS端点作为Spring管理的bean来定义。这与前面章节讨论的单独模式类似 – 但这次是在Servlet环境中。注意这在Java EE 5环境中是不可迁移的,建议在没有EE的web应用环境如Tomcat中嵌入JAX-WS RI。 与标准的暴露基于servlet的端点方式不同之处在于端点实例的生命周期将被Spring管理。这里在web.xml将只有一个JAX-WS servlet定义。在标准的Java EE 5风格中(如上所示),你将对每个服务端点定义一个servlet,每个服务端点都代理到Spring bean (通过使用@Autowired,如上所示)。 关于安装和使用详情请查阅https://jax-ws-commons.dev.java.net/spring/

    24.5.4 使用JAX-WS访问web服务

    Spring提供了2个工厂bean来创建JAX-WS web服务代理,它们是LocalJaxWsServiceFactoryBean和JaxWsPortProxyFactoryBean。前一个只能返回一个JAX-WS服务对象来让我们使用。后面的是可以返回我们业务服务接口的代理实现的完整版本。这个例子中我们使用后者来为AccountService端点再创建一个代理:

    <bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
        <property name="serviceInterface" value="example.AccountService"/>
        <property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/>
        <property name="namespaceUri" value="http://example/"/>
        <property name="serviceName" value="AccountService"/>
        <property name="portName" value="AccountServiceEndpointPort"/>
    </bean>

    serviceInterface是我们客户端将使用的远程业务接口。wsdlDocumentUrl是WSDL文件的URL. Spring需要用它作为启动点来创建JAX-WS服务。namespaceUri对应.wsdl文件中的targetNamespace。serviceName对应.wsdl文件中的服务名。portName对应.wsdl文件中的端口号。 现在我们可以很方便的访问web服务,因为我们有一个可以将它暴露为AccountService接口的bean工厂。我们可以在Spring中这样使用:

    <bean id="client" class="example.AccountClientImpl">
        ...
        <property name="service" ref="accountWebService"/>
    </bean>

    从客户端代码上我们可以把这个web服务当成一个普通的类进行访问:

    public class AccountClientImpl {
    
        private AccountService service;
    
        public void setService(AccountService service) {
            this.service = service;
        }
    
        public void foo() {
            service.insertAccount(...);
        }
    }

    .

    Note: 上面例子被稍微简化了,因为JAX-WS需要端点接口及实现类来使用@WebService,@SOAPBinding等注解。 这意味着你不能简单地使用普通的Java接口和实现来作为JAX-WS端点,你需要首先对它们进行相应的注解。这些需求详情请查阅JAX-WS文档。

    24.6 JMS

    使用JMS来作为底层的通信协议透明暴露服务也是可能的。Spring框架中对JMS的远程支持也很基础 – 它在同一线程和同一个非事务 Session上发送和接收,这些吞吐量将非常依赖于实现。需要注意的是这些单线程和非事务的约束仅适用于Spring的JMS远程处理支持。请参见 第26章, JMS (Java消息服务),Spring对基于JMS的消息的丰富支持。 下面的接口可同时用在服务端和客户端。

    package com.foo;
    
    public interface CheckingAccountService {
    
        public void cancelAccount(Long accountId);
    
    }

    对于上面接口的使用在服务的端简单实现如下:

    package com.foo;
    
    public class SimpleCheckingAccountService implements CheckingAccountService {
    
        public void cancelAccount(Long accountId) {
            System.out.println("Cancelling account [" + accountId + "]");
        }
    
    }

    这个包含JMS设施的bean的配置文件可同时用在客户端和服务端: <?xml version=”1.0″ encoding=”UTF-8″?>

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="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">
    
        <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
            <property name="brokerURL" value="tcp://ep-t43:61616"/>
        </bean>
    
        <bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
            <constructor-arg value="mmm"/>
        </bean>
    
    </beans>

    24.6.1 服务端配置

    在服务端你只需要使用JmsInvokerServiceExporter来暴露服务对象。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="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">
    
        <bean id="checkingAccountService"
                class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
            <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
            <property name="service">
                <bean class="com.foo.SimpleCheckingAccountService"/>
            </property>
        </bean>
    
        <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
            <property name="connectionFactory" ref="connectionFactory"/>
            <property name="destination" ref="queue"/>
            <property name="concurrentConsumers" value="3"/>
            <property name="messageListener" ref="checkingAccountService"/>
        </bean>
    
    </beans>

    .

    package com.foo;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Server {
    
        public static void main(String[] args) throws Exception {
            new ClassPathXmlApplicationContext(new String[]{"com/foo/server.xml", "com/foo/jms.xml"});
        }
    
    }

    24.6.2 客户端配置

    客户端只需要创建一个客户端代理来实现上面的接口(CheckingAccountService)。根据后面的bean定义创建的结果对象可以被注入到其它客户端对象中,而这个代理会负责通过JMS将调用转发到服务端。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="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">
    
        <bean id="checkingAccountService"
                class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
            <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
            <property name="connectionFactory" ref="connectionFactory"/>
            <property name="queue" ref="queue"/>
        </bean>
    
    </beans>

    .

    package com.foo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Client {
    
        public static void main(String[] args) throws Exception {
            ApplicationContext ctx = new ClassPathXmlApplicationContext(
                    new String[] {"com/foo/client.xml", "com/foo/jms.xml"});
            CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService");
            service.cancelAccount(new Long(10));
        }
    
    }

    24.7 AMQP

    有关更多信息,请参考 Spring AMQP Reference Document ‘Spring Remoting with AMQP’ section 。

    24.8 不实现远程接口自动检测

    对远程接口不实现自动探测的主要原因是为了避免向远程调用者打开了太多的大门。目标对象有可能实现的是类似InitializingBean或者DisposableBean这样的内部回调接口,而这些是不希望暴露给调用者的。

    提供一个所有接口都被目标实现的代理通常和本地情况无关。但是当暴露一个远程服务时,你应该只暴露特定的用于远程使用的服务接口。除了内部回调接口,目标有可能实现了多个业务接口,而往往只有一个是用于远程调用的。出于这些原因,我们要求指定这样的服务接口。

    这是在配置方便性和意外暴露内部方法的危险性之间作的权衡。始终指定一个服务接口并不需要花太大代价,并可以令控制具体方法暴露更加安全。

    24.9 选择技术时的注意事项

    这里提到的每种技术都有它的缺点。你在选择一种技术时,应该仔细考虑你的需要和所暴露的服务及你在远程访问时传送的对象。

    当使用RMI时,通过HTTP协议访问对象是不可能的,除非你正在HTTP通道传输RMI流量。RMI是一种重量级协议,因为它支持整个对象的序列化,当要求网络上传输复杂数据结构时这是非常重要的。然而,RMI-JRMP与Java客户端相关:它是一种Java-to-Java的远程访问解决方案。

    如果你需要基于HTTP的远程访问而且还要求使用Java序列化,Spring的HTTP调用器是一个不错的选择。它和RMI调用器共享相同的基础设施,只需使用HTTP作为传输。注意HTTP调用器不仅限于Java-to-Java的远程访问,而且还限于使用Spring的客户端和服务器端。(后者也适用于Spring的RMI调用器,用于非RMI接口。)

    Hessian可以在异构环境中运行时提供重要的价值,因为它们明确允许非Java客户端。然而,非Java支持仍然有限。已知问题包括将Hibernate对象与延迟初始化的集合相结合的序列化。如果您有这样的数据模型,请考虑使用RMI或HTTP调用者而不是Hessian。

    在使用服务集群和需要JMS代理(JMS broker)来处理负载均衡及发现和自动-失败恢复服务时JMS是很有用的。缺省情况下,在使用JMS远程服务时使用Java序列化,但是JMS提供者也可以使用不同的机制例如XStream来让服务器用其他技术。

    最后但并非最不重要的是,EJB比RMI具有优势,因为它支持标准的基于角色的身份认证和授权,以及远程事务传递。用RMI调用器或HTTP调用器来支持安全上下文的传递是可能的,虽然这不由核心core Spring提供:Spring提供了合适的钩子来插入第三方或定制的解决方案。

    24.10 在客户端访问RESTful服务

    RestTemplate是客户端访问RESTful服务的核心类。它在概念上类似于Spring中的其他模板类,例如JdbcTemplate、 JmsTemplate和其他Spring组合项目中发现的其他模板类。

    RestTemplate’s behavior is customized by providing callback methods and configuring the `HttpMessageConverter用于将对象打包到HTTP请求体中,并将任何响应解包成一个对象。通常使用XML作为消息格式,Spring提供了MarshallingHttpMessageConverter,它使用了的Object-to-XML框架,也是org.springframework.oxm包的一部分。这为你提供了各种各样的XML到对象映射技术的选择。

    本节介绍如何使用RestTemplate它及其关联 的HttpMessageConverters。

    24.10.1 RestTemplate

    在Java中调用RESTful服务通常使用助手类(如Apache HttpComponents)完成HttpClient。对于常见的REST操作,此方法的级别太低,如下所示。

    String uri = "http://example.com/hotels/1/bookings";
    
    PostMethod post = new PostMethod(uri);
    String request = // create booking request content
    post.setRequestEntity(new StringRequestEntity(request));
    
    httpClient.executeMethod(post);
    
    if (HttpStatus.SC_CREATED == post.getStatusCode()) {
        Header location = post.getRequestHeader("Location");
        if (location != null) {
            System.out.println("Created new booking at :" + location.getValue());
        }
    }

    RestTemplate提供了更高级别的方法,这些方法与六种主要的HTTP方法中的每一种相对应,这些方法使得调用许多RESTful服务成为一个单行和执行REST的最佳实践。

    Note: RestTemplate具有异步计数器部分:请参见第24.10.3节“异步RestTemplate”

    Table 24.1. RestTemplate方法概述

    RestTemplate方法名称遵循命名约定,第一部分指出正在调用什么HTTP方法,第二部分指出返回的内容。例如,该方法getForObject()将执行GET,将HTTP响应转换为你选择的对象类型并返回该对象。方法postForLocation() 将执行POST,将给定对象转换为HTTP请求,并返回可以找到新创建的对象的响应HTTP Location头。在异常处理HTTP请求的情况下,RestClientException类型的异常将被抛出; 这个行为可以在RestTemplate通过插入另一个ResponseErrorHandler实现来改变。

    exchange和execute方法是上面列出的更具体的方法的广义版本,并且可以支持额外的组合和方法,例如HTTP PATCH。但是,请注意,底层HTTP库还必须支持所需的组合。JDK HttpURLConnection不支持该PATCH方法,但Apache HttpComponents HttpClient4.2或更高版本支持。他们还能够通过使用一个能够捕获和传递通用类型信息的新类ParameterizedTypeReference来使得RestTemplate能够读取通用类型的HTTP响应信息(例如List)。

    对象通过HttpMessageConverter实例传递给这些方法并从这些方法返回被转换为HTTP消息。主要mime类型的转换器默认注册,但你也可以编写自己的转换器并通过messageConverters()实体属性注册它 。模板默认注册的转换器实例是ByteArrayHttpMessageConverter,StringHttpMessageConverter,FormHttpMessageConverter和SourceHttpMessageConverter。如果使用MarshallingHttpMessageConverter或者MappingJackson2HttpMessageConverter,你可以使用messageConverters()实体属性覆盖这些默认值。

    每个方法以两种形式使用URI模板参数,作为String可变长度参数或Map<String,String>。例如,使用可变长参数如下:

    String result = restTemplate.getForObject(
        "http://example.com/hotels/{hotel}/bookings/{booking}", String.class,"42", "21");

    使用一个Map<String,String>如下:

    Map<String, String> vars = Collections.singletonMap("hotel", "42");
    String result = restTemplate.getForObject(
            "http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

    要创建一个实例,RestTemplate可以简单地调用默认的无参数构造函数。这将使用java.net包中的标准Java类作为底层实现来创建HTTP请求。这可以通过指定实现来覆盖ClientHttpRequestFactory。Spring提供了HttpComponentsClientHttpRequestFactory使用Apache HttpComponents HttpClient创建请求的实现。HttpComponentsClientHttpRequestFactory通过使用一个可以配置凭证信息或连接池功能的org.apache.http.client.HttpClient实例来配置。

    Note: HTTP请求的java.net实现可能会在访问表示错误的响应状态(例如401)时引发异常。如果这是一个问题,请切换到HttpComponentsClientHttpRequestFactory。

    前面使用Apache HttpCOmponentsHttpClientdirectly的例子用RestTemplate重写如下:

    uri = "http://example.com/hotels/{id}/bookings";
    
    RestTemplate template = new RestTemplate();
    
    Booking booking = // create booking object
    
    URI location = template.postForLocation(uri, booking, "1");

    使用Apache HttpComponents, 而不是原生的java.net功能,构造RestTemplate如下:

    RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

    .

    Note: Apache HttpClient 支持gzip编码,要使用这个功能,构造HttpCOmponentsClientHttpRequestFactory如下:
    HttpClient httpClient = HttpClientBuilder.create().build();
    ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
    RestTemplate restTemplate = new RestTemplate(requestFactory);

    当execute方法被调用,通用的回调接口是RequestCallback并且会被调用。

    public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
            ResponseExtractor<T> responseExtractor, String... uriVariables)
    
    // also has an overload with uriVariables as a Map<String, String>.

    RequestCallback接口定义如下:

    public interface RequestCallback {
     void doWithRequest(ClientHttpRequest request) throws IOException;
    }

    允许您操作请求标头并写入请求主体。当使用execute方法时,你不必担心任何资源管理,模板将始终关闭请求并处理任何错误。有关使用execute方法及它的其他方法参数的含义的更多信息,请参阅API文档。

    使用URI

    对于每个主要的HTTP方法,RestTemplate提供的变体使用String URI或java.net.URI作为第一个参数。
    String URI变体将模板参数视为String变长参数或者一个Map<String,String>。他们还假定URL字符串不被编码且需要编码。例如:

    restTemplate.getForObject("http://example.com/hotel list", String.class);

    将在 http://example.com/hotel%20list执行一个GET请求。这意味着如果输入的URL字符串已被编码,它将被编码两次 – 即将 http://example.com/hotel%20list变为http://example.com/hotel%2520list。如果这不是预期的效果,则使用java.net.URI方法变体,假设URL已经被编码,如果要重复使用单个(完全扩展)URI多次,通常也是有用的。

    UriComponentsBuilder类可用于构建和编码URI包括URI模板的支持。例如,你可以从URL字符串开始:

    UriComponents uriComponents = UriComponentsBuilder.fromUriString( "http://example.com/hotels/{hotel}/bookings/{booking}").build()
            .expand("42", "21")
            .encode();
    
    URI uri = uriComponents.toUri();

    或者分别制定每个URI组件:

    UriComponents uriComponents = UriComponentsBuilder.newInstance()
            .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
            .expand("42", "21")
            .encode();
    
    URI uri = uriComponents.toUri();

    处理请求和响应头

    除了上述方法之外,RestTemplate还具有exchange() 方法,可以用于基于HttpEntity 类的任意HTTP方法执行。
    也许最重要的是,该exchange()方法可以用来添加请求头和读响应头。例如:

    HttpHeaders requestHeaders = new HttpHeaders();
    requestHeaders.set("MyRequestHeader", "MyValue");
    HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
    
    HttpEntity<String> response = template.exchange( "http://example.com/hotels/{hotel}",
            HttpMethod.GET, requestEntity, String.class, "42");
    
    String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
    String body = response.getBody();

    在上面的例子,我们首先准备了一个包含MyRequestHeader 头的请求实体。然后我们检索返回和读取MyResponseHeader和消息体。

    Jackson JSON 视图支持

    可以指定一个 Jackson JSON视图来系列化对象属性的一部分,例如:

    MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
    value.setSerializationView(User.WithoutPasswordView.class);
    HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(value);
    String s = template.postForObject("http://example.com/user", entity, String.class);

    24.10.2 HTTP 消息转换

    通过HttpMessageConverters,对象传递到和从getForObject(),postForLocation(),和put()这些方法返回被转换成HTTP请求和HTTP相应。HttpMessageConverter接口如下所示,让你更好地感受它的功能:

    public interface HttpMessageConverter<T> {
    
        // Indicate whether the given class and media type can be read by this converter.
        boolean canRead(Class<?> clazz, MediaType mediaType);
    
        // Indicate whether the given class and media type can be written by this converter.
        boolean canWrite(Class<?> clazz, MediaType mediaType);
    
        // Return the list of MediaType objects supported by this converter.
        List<MediaType> getSupportedMediaTypes();
    
        // Read an object of the given type from the given input message, and returns it.
        T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
    
        // Write an given object to the given output message.
        void write(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
    
    }

    框架中提供主要媒体(mime)类型的具体实现,默认情况下,通过RestTemplate在客户端和 RequestMethodHandlerAdapter在服务器端注册。

    HttpMessageConverter的实现下面章节中描述。对于所有转换器,使用默认媒体类型,但可以通过设置supportedMediaTypesbean属性来覆盖。

    StringHttpMessageConverter

    一个HttpMessageConverter的实现,实现从HTTP请求和相应中读和写Strings。默认情况下,该转换器支持所有的文本媒体类型(text/*),并用text/plain的Content-Type来写。

    FormHttpMessageConverter

    一个HttpMessageConverter的实现,实现从HTTP请求和响应读写表单数据。默认情况下,该转换器读写application/x-www-form-urlencoded媒体类型。表单数据被读取并写入MultiValueMap<String, String>。

    ByteArrayHttpMessageConverter

    一个HttpMessageConverter的实现,实现从HTTP请求和响应中读取和写入字节数组。默认情况下,此转换器支持所有媒体类型(/),并使用其中的一种Content-Type进行写入application/octet-stream。这可以通过设置supportedMediaTypes属性和覆盖getContentType(byte[])来重写。

    MarshallingHttpMessageConverter

    一个HttpMessageConverter的实现,从org.springframework.oxm包中使用Spring的Marshaller和Unmarshaller抽象实现读取和写入XML。该转换器需要Marshaller和Unmarshaller才能使用它。这些可以通过构造函数或bean属性注入。默认情况下,此转换器支持( text/xml)和(application/xml)。

    MappingJackson2HttpMessageConverter

    一个HttpMessageConverter的实现,使用Jackson XML扩展的ObjectMapper实现读写JSON。可以根据需要通过使用JAXB或Jackson提供的注释来定制XML映射。当需要进一步控制时,XmlMapper 可以通过ObjectMapper属性注入自定义,以便需要为特定类型提供自定义XML序列化器/反序列化器。默认情况下,此转换器支持(application/xml)。

    MappingJackson2XmlHttpMessageConverter

    一个HttpMessageConverter的实现,可以使用Jackson XML扩展的XmlMapper读取和写入XML。可以根据需要通过使用JAXB或Jackson提供的注释来定制XML映射。当需要进一步控制时,XmlMapper 可以通过ObjectMapper属性注入自定义,以便需要为特定类型提供自定义XML序列化器/反序列化器。默认情况下,此转换器支持(application/xml)。

    SourceHttpMessageConverter

    一个HttpMessageConverter的实现,从HTTP请求和响应中读写 javax.xml.transform.Source。仅支持DOMSource、SAXSource和StreamSource。默认情况下,此转换器支持(text/xml)和(application/xml)。

    BufferedImageHttpMessageConverter

    一个HttpMessageConverter的实现,从HTTP请求和响应中读写java.awt.image.BufferedImage。此转换器读写Java I/O API支持的媒体类型。

    24.10.3 异步RestTemplate

    Web应用程序通常需要查询外部REST服务。当为这些需求扩张应用程序时,HTTP和同步调用的性质带来挑战:可能会阻塞多个线程,等待远程HTTP响应。

    AsyncRestTemplate和第24.10.1节“RestTemplate”的API非常相似; 请 参见表24.1“RestTemplate方法概述”。这些API之间的主要区别是AsyncRestTemplate返回ListenableFuture 封装器而不是具体的结果。

    前面的RestTemplate例子翻译成:

    // async call
    Future<ResponseEntity<String>> futureEntity = template.getForEntity(
        "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
    
    // get the concrete result - synchronous call
    ResponseEntity<String> entity = futureEntity.get();

    ListenableFuture 接受完成回调:

    ListenableFuture<ResponseEntity<String>> futureEntity = template.getForEntity(
        "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
    
    // register a callback
    futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
        @Override
        public void onSuccess(ResponseEntity<String> entity) {
            //...
        }
    
        @Override
        public void onFailure(Throwable t) {
            //...
        }
    });

    .

    Note: 默认AsyncRestTemplate构造函数为执行HTTP请求注册一个SimpleAsyncTaskExecutor 。当处理大量短命令请求时,线程池的TaskExecutor实现ThreadPoolTaskExecutor 可能是一个不错的选择。

    有关更多详细信息,参考ListenableFuture的javadoc and AsyncTestTmeplate的javadoc.

  • JVM调优之:JDK自带工具大全——工欲善其事,必先利其器!

    本文主要讲解JDK1.8自带工具用途和用法,因为绝大部分Java程序都是部署在Linux平台,所以讲解Linux工具(Windows平台工具可以参考Oracle文档https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html)。 

    按用途分为12类:基础工具、安全工具、国际化工具、远程方法调用(RMI)工具、Java IDL and RMI-IIOP工具、Java部署工具、Java Web启动工具、Java诊断分析监控管理工具、Java Web服务工具、监控工具、分析工具和脚本工具。
    JDK1.8u20下的工具目录图: 

    java_tools_full

    (迟些时候再写一篇文章说明常用工具用法和技巧)

  • jstack: Java占用高CPU分析之- C2 CompilerThread

    1. 现象

    应用刚上线时发现Java进程占用了大量的CPU份额,但过了几分钟后会降下来(流量没变的情况下),因为已经做了负载均衡,于是拿一台实例重新部署代码上线来分析。具体分析步骤参考另外一篇文章《jstack: Java占用高CPU分析之- GC task thread》。这里简单说一下步骤,重点是分析结果后的解决方法,不过强调一点:当发现Java应用占用高CPU的时候,先把Java的线程号和JVM的堆栈信息记录到文件(这个可以用脚本实现),因为堆栈信息随时在变

     

    2.定位

    top | grep java

    cpu2_1

    cpu2_2

    cpu2_3

    cpu2_4

     

    3.原因和解释

    定位到 C2 CompilerThread0这个线程占用了比较高的CPU。C2 Compiler 是JVM在server模式下字节码编译器,JVM启动的时候所有代码都处于解释执行模式,当某些代码被执行到一定阈值次数,这些代码(称为热点代码)就会被 C2 Compiler编译成机器码,编译成机器码后执行效率会得到大幅提升。

    流量进来后,大部分代码成为热点代码,这个过程中C2 Compiler需要频繁占用CPU来运行,当大部分热点代码被编译成机器代码后,C2 Compiler就不再长期占用CPU了,这个过程也可以看作抖动。

    4.解决方案

    (1)最直接有效的方法是“预热(warm up)”:可以使用Jmeter等压测工具模拟线上访问流量,让C2 Compiler预先将热点代码编译成机器码, 减少对正式环境流量的影响。

    warmup

    (2) 设置JVM启动参数:-XX:CICompilerCount=threads

    默认是2, 可以设置4或6。在默认值下抖动时CPU已经满载,设置成更多的线程也不一定起作用,但对于CPU“高而不满”的情况会有用,能减少抖动时间。

    CICompilerCount

     

    参考文章:

    http://www.javaworld.com/article/2078635/enterprise-middleware/jvm-performance-optimization-part-2-compilers.html

    https://answers.atlassian.com/questions/22651310/c1c2-compiler-thread-eats-a-lot-of-cpu

    http://www.cnblogs.com/LBSer/p/3703967.html

    http://qa.blog.163.com/blog/static/190147002201392221426372/

    https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html

     

  • 白话TCP/IP之:图解三次握手和四次挥手

    tcp_header TCP头部格式,对于理解各种连接状态很有帮助,下面讲解(引用自RFC 793: Transmission Control Protocol
     
    TCP, 即传输控制协议(Transmission Control Protocol),是一种面向连接的协议,特点是可靠和面向字节流传输。其中可靠的保证就是著名的三次握手和四次挥手。要想了解其中的过程和原理,动手实践是最好的方式,因为根据现象来理解原理能更深刻。推荐使用的是tcpdump,这是最常用的抓包工具之一。对于访问量大的网络程序可能会经常出现连接等问题,所以熟练使用tcpdump,能很好地分析出问题的原因。

    tcpdump用法:更多参数用法参考 man tcpdump 命令
    tcpdump [ -AbdDefhHIJKlLnNOpqRStuUvxX ] [ -B buffer_size ] [ -c count ]
    [ -C file_size ] [ -G rotate_seconds ] [ -F file ]
    [ -i interface ] [ -j tstamp_type ] [ -m module ] [ -M secret ]
    [ -P in|out|inout ]
    [ -r file ] [ -V file ] [ -s snaplen ] [ -T type ] [ -w file ]
    [ -W filecount ]
    [ -E spi@ipaddr algo:secret,… ]
    [ -y datalinktype ] [ -z postrotate-command ] [ -Z user ]
    [ expression ]
    下面是一次完整的三次握手、数据交互和四次挥手过程: tcpdump -nS port 10129 (10129: 监听端口;-n:不将ip地址转换成服务器名;-S:打印完整seq和ack序号;另外如果客户端和服务器在同一台机器需要加 -i lo 参数)                                tcp_screenshot

    表示客户端Client(172.31.1.22:60755)通过内网请求服务器Server(172.31.3.178:10129),流程图如下:                     

    其中1,2,3是三次握手过程,8,9,10,11是四次挥手过程,4,5,6,7是数据传输过程。
    对于关闭连接的交叉挥手是因为tcp既可以其中一方先发送FIN执行主动关闭,也可以双方都执行主动关闭。
  • Java8 集合框架 collections framework

    一.概述

    Java8发布已经有两年半了(2014年03月),是Java5以来改动最大的一次版本发布(具体新特性http://openjdk.java.net/projects/jdk8/features )。公司项目也已经用了两年(不得不佩服国外同事在新技术使用方面的先进性),不过国内大部分公司还是在使用Java7,甚至一些传统ERP公司还在使用Java6。Dzone上面有一篇ZK关于开发者使用JDK版本使用情况调查的文章https://dzone.com/articles/current-development-trends-of-java-web-programmers

                                                                        jdk_version

    • 注:开发使用的JDK版本情况和生产上会有差别,开发中新版本比例要比生产高,一般建议开发和生产使用同版本号(包括update的小版本号)。

    本文主要介绍Java8集合框架的架构、常用集合特点用法和工具类,因为集合框架是Java中被使用最多的类库。Java8对集合框架进行了大改进,支持lambda表达式、streams 和聚合操作,以提升对Collection对象功能的增强(其中streams支持List、Queue和Set, 不支持Map)。直接使用Java集合框架的类,而不是自己编写类和方法,有以下几个好处:

    • 减少编程量
    • 提高编程速度和质量
    • 减少学习和使用新APIs的工作量
    • 促进软件重用

    一个例子就是用BlockingQueue实现生产消费者模型(BlockingQueue的javadoc里有例子),而不是自己写wait和notifyAll,因为容易出错,当然其中的原理还是要懂。

    二.集合分类和实现

    Java 集合框架中Collection是最基础的接口,另一个基础接口是Map。Collection主要由Set、Queue和List这三种接口继承,并形成三种基本的数据结构。

    1.实现了集合接口的类通常具有<Implementation-style><Interface>形式的名称,下表总结了常用的实现:

    Interface Hash Table Resizable Array Balanced Tree Linked List Hash Table + Linked List
    Set HashSet TreeSet LinkedHashSet
    List ArrayList LinkedList
    Deque ArrayDeque LinkedList
    Map HashMap TreeMap LinkedHashMap

     

    2. Collection接口及子类子接口继承实现图:

    java_collections_framework

    3.Map接口及子类子接口继承实现图:

    map  

    三.集合类在一些流行框架里面的使用情况

    虽然有些集合类(如 WeakHashMap)我们自己写代码可能会比较少用,但在一些流行框架里面使用却非常广泛,了解这些集合的特点和适用场景对于学习这些框架的设计思想和源码会很有帮助。

    1.线程池框架:

    Executors是java.util.concurrent包里面的工厂类,可以用它来返回一个制定功能(包括执行策略、扩容、调度和拒绝策略等)的线程池,而这些线程池就用到了集合框架,了解这些集合的特性能更好地使用线程池和处理可能会出现的问题:

    • newFixedThreadPool:
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); 
    }

    • newCachedThreadPool:
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    }

    • newScheduledThreadPool:
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
    }
    • newSingleThreadExecutor:
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(
            new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
    }
    • newSingleTheadScheduledExecutor:
    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
    }

    可以看到这些线程池内部的集合类都是BlockingQueue的子类(ScheduledThreadPoolExecutor的内部集合类也是继承了AbstractQueue
    <Runnable>和实现了BlockingQueue<Runnable>)。所以了解BlockingQueue的特性对于了解线程池很有帮助。

    2.几种常用Java服务,在JVM heap中数量和占用内存top 15的情况:

    (1). 基于Spring MVC 4.x + Jetty 9.x的RESTful webservice

    SpringMVC  

    (2).基于Netty 5.x的RPC server

    netty  

    (3). 基于mina 2.x 的push服务

    mina  

     

  • Maven’s common problems and solutions

    1. No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?

    As the error message display,perhaps you used JRE for maven, but it needs JDK(in fact, javac) as compiler. JRE is just the Java runtime environment, while JDK is the Java development kit containing the the compiler javac for java code. In fact, Eclipse uses JRE as its default environment, and used its own compiler JDT. So when using maven to compile, the operation fails.

    Solution: go into  Windows -> Perferences -> Java -> Installed JREs. Note the underlined part is jdk not jre.

    .jres

     

    2. Could not resolve dependencies for project XXX:jar:0.1: Failure to find XXX:jar:0.0.1 in https://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced.

    There are some files with the names ending in the .lastUplated suffix in your local repository, so maven will not compile again.

    Solution:

    1) for maven command: mvn clean install -U

    2) for Eclipse:

    mvn

     

    3. error reading C:\Users\Administrator\.m2\repository\XXX.jar; invalid LOC header (bad signature)

    That is because there are some incomplete in maven local repository. May be you can see some files with names ending in -in-progress suffix.

    123
    Solution: delete the whole folder which contains the incomplete jar file, and compile again.
  • jstack: Java占用高CPU分析之- GC task thread

    jstack 是JDK自带的堆栈跟踪工具,作用有两个:

    1. 为Java 进程或者核心文件打印出线程的堆栈信息;
    2. 远程调试服务器。

    查看用法:

    jstack -help
    Usage:
    jstack [-l] <pid>
    (to connect to running process)
    jstack -F [-m] [-l] <pid>
    (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
    (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
    (to connect to a remote debug server)

    Options:
    -F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m to print both java and native frames (mixed mode)
    -l long listing. Prints additional information about locks
    -h or -help to print this help message

    下面开始具体的调试步骤:

    1. top 命令可以看到具体的Java进程号:(注意这里的PID是进程ID)

    pid

    2. 然后按 Shift + h 直接列出线程号:注意这里的PID是线程ID(对应的是native 线程的id, 即nid)

    tid

    3. 可以看到主要是19226和19227这两个线程消耗大量的CPU,而且时间累占用了一个小时左右,需要把线程号转换成十六进制,因为打印出来的堆栈信息里面的线程号是十六进制形式: printf “%x/n” 19226

    printf

    4. 获得占用CPU的Java进程号和线程号后可以用jstack工具来打印堆栈信息了:

    (1) 先把整个进程堆栈信息打印预览: jstack 19214

    stack1

    stack2

    (2)如果要快速定位线程造成的问题用:jstack [PID(进程号)] | grep [PID(线程号十六进制)] -A 10

    stack3

     

    5. 从进程和线程的堆栈信息可以看出是JVM的GC线程一直在占用大量CPU, 定位代码得出的结论是:Java程序连MySQL频繁new connection而且没有调用close方法,导致GC线程一直占用CPU。

     

  • 由深圳限外引起的一点思考(附限外摄像头分布点)

    在深圳这个一直以开放出名的城市居然也搞起了限外(排外),不得不让人对ZF的管理能力只是出这种最简单粗暴的政策产生怀疑。实际效果是限行后以前没打算买车的都参加到摇号买车了,以前不在深圳开的粤B车也开回来深圳了。另外如果真的是想限制新车增长,全部指标摇号就可以了,想不明白还要增加竞拍(按每期3000个竞拍指标,均价5万来算,一期的竞拍收入是1.5亿)。

    其实想说的是,如果大家都欣然接受这种现象,那接下来就可能会出现“单双号限行”,甚至是“摇号出行”,“市内按区限行”,“上下班高峰期只允许公共交通和政府机关车辆行驶”等等。不要觉得不可能,以前我们觉得“限行限外”,“单双号限行”都是不可能,后来都一一“实现”了,有了开头自然会有其他的出来。但是在地球的另一边欧洲国家之间开车通行都没有太多限制,就像人家国家之间打电话都不算长途,我们市与市之间已经是长途。对于不开车的人来说,如果汽车都限行了,公共交通工具的压力就会倍增,到时候就不是要挤几趟地铁才能挤上去这么简单了。当然站在社会资源的角度是提倡大家尽量使用公共交通工具出行的,前提是这个城市的公共交通足够发达和公平,要知道有深圳“硅谷”之称的科技园却只有一条地铁通过,这样的资源分配怎么能让大家都用公共交通出行?

    在IT界里面也有关于封闭和开放的思辨:技术垄断、闭源、开源和黑客。技术垄断的代表是高通,闭源是微软和Oracle,开源是Github、Linux、MySQL、Google、Facebook和Alibaba,黑客是阿桑奇。总的趋势都是向着开源和自由方向发展。

     

    附:深圳限外车牌识别监控摄像头分布点(数据更新至2015年2月4日,后续可能有新增,欢迎留言补充)

     

    48 福田区  (高清) 梅观公路南坪立交南侧南行
    49 福田区  (高清) 梅观公路彩田路口北行
    50 福田区  (标清) 南坪快速路1.3公里东行
    51 福田区  (标清) 南坪快速路1.3公里西行
    52 福田区  (标清) 南坪快速路5.9公里东行
    53 福田区  (标清) 南坪快速路5.9公里西行
    54 福田区  (标清) 北环大道新洲立交东行
    55 福田区  (标清) 北环大道新洲立交西行
    56 福田区  (标清) 北环大道彩田立交东行
    57 福田区  (标清) 北环大道皇岗路口西行
    58 福田区  (标清) 滨河大道爱华南人行天桥东行
    59 福田区  (标清) 滨河大道爱华南人行天桥西行
    60 福田区  (标清) 滨河大道彩田立交东行
    61 福田区  (标清) 滨河大道福滨人行天桥西行
    62 福田区  (标清) 滨河大道红岭南人行天桥东行
    63 福田区  (标清) 滨河大道香蜜湖立交东行
    64 福田区  (标清) 红荔路福莲人行天桥东行
    65 福田区  (标清) 红荔路皇岗路口西行
    66 福田区  (标清) 上步中路百花人行天桥南行
    67 福田区  (标清) 上步中路同德人行天桥北行
    68 福田区  (标清) 深南大道彩田立交东行
    69 福田区  (标清) 深南大道广深高速桥东行
    70 福田区  (标清) 深南大道广深高速桥西行
    71 福田区  (标清) 深南大道皇岗立交西行
    72 福田区  (标清) 深南大道新洲立交东行
    73 福田区  (标清) 深南大道新洲立交西行
    74 福田区  (标清) 深南中路中航人行天桥东行
    75 福田区  (标清) 深南中路中航人行天桥西行
    76 福田区  (标清) 笋岗西路笔架山公园人行天桥西行
    77 福田区  (标清) 笋岗西路莲花立交东行
    78 福田区  (标清) 笋岗东路圆岭人行天桥东行
    79 福田区  (高清) 新洲路福新立交南行
    80 福田区  (高清) 北环大道侨香村人行天桥东行
    81 福田区  (高清) 北环大道侨香村人行天桥西行
    82 福田区  (高清) 福龙路横龙山隧道入口北行
    83 福田区  (高清) 福龙路横龙山隧道北行南坪出口
    84 福田区  (高清) 福龙路横龙山隧道出口南行
    85 福田区  (高清) 新洲路福新立交北行
    86 福田区  (高清) 福强路福强人行天桥东行
    87 福田区  (高清) 福强路福强人行天桥西行
    88 福田区  (高清) 福荣路新洲路口东侧东行
    89 福田区  (高清) 福荣路新洲路口东侧西行
    90 福田区  (高清) 彩田路新彩隧道管理中心人行天桥北行
    91 福田区  (高清) 彩田路皇岗立交北行
    92 福田区  (高清) 彩田路皇岗立交北侧北行
    93 福田区  (高清) 深南大道皇岗立交东侧西行
    94 福田区  (高清) 深南大道皇岗立交北行跨线桥西行
    95 福田区  (高清) 深南大道皇岗立交南行跨线桥西行
    96 罗湖区  (高清) 沿河路北斗人行天桥东行
    97 罗湖区  (标清) 深南东路蔡屋围人行天桥东行
    98 罗湖区  (标清) 深南东路蔡屋围人行天桥西行
    99 罗湖区  (标清) 笋岗东路松园北人行天桥西行
    100 罗湖区  (标清) 沿河路罗芳立交南行
    101 罗湖区  (标清) 罗沙路仙湖立交西行
    102 罗湖区  (标清) 罗沙路长岭人行天桥东行
    103 罗湖区  (标清) 罗沙路罗芳人行天桥西行
    104 罗湖区  (标清) 罗沙路罗芳人行天桥东行
    105 罗湖区  (高清) 延芳路罗沙边防通道桥北行
    106 罗湖区  (高清) 延芳路罗沙边防通道桥南行
    107 罗湖区  (高清) 罗沙路辅道仙湖立交西行
    108 南山区  (标清) 月亮湾大道深南立交北行
    109 南山区  (标清) 北环大道深南立交西行
    110 南山区  (标清) 北环大道沙河东立交东行
    111 南山区  (标清) 北环大道沙河东立交西行
    112 南山区  (标清) 滨海大道后海立交西行
    113 南山区  (标清) 滨海大道侨城东立交东行
    114 南山区  (高清) 滨海大道侨城东立交西行
    115 南山区  (标清) 滨海大道沙河东立交东行
    116 南山区  (高清) 滨海大道沙河东立交西行
    117 南山区  (标清) 南海大道学府人行天桥北行
    118 南山区  (标清) 南海大道学府人行天桥南行
    119 南山区  (标清) 深南大道南海立交东行
    120 南山区  (标清) 深南大道南海立交西行
    121 南山区  (标清) 深南大道沙河西立交西行
    122 南山区  (高清) 北环大道深云立交东行
    123 南山区  (高清) 北环大道深云立交西行
    124 南山区  (高清) 滨海大道沙河西路出口西行
    125 盐田区  (高清) 深盐二通道恩上立交西行
    126 盐田区  (高清) 深盐二通道恩上立交东行
    127 罗湖区 (高清) 东门路湖贝路口北行
    128 罗湖区 (高清) 东门路晒布路口南行
    129 福田区 (高清) 红荔路香蜜湖天桥东行
    130 福田区 (高清) 红荔路香蜜湖天桥西行