好库文摘 http://doc.okbase.net/ 简单的注解剖析 http://doc.okbase.net/qpf1/archive/264131.html 永不言Qi 2017/7/19 18:19:46

  周边有许多同事只会使用注解,并不了解注解的原理。于是随手写一个小Demo,普及下注解的使用原理,顺便加深自己的理解。如有错误,欢迎大牛指正。编写的代码会放到附件里,main函数在AnnotationTest.java文件下。

 

一、如何创建一个注解类

 

1、注解类基本样式

/**
 * 
 * @author qpf
 * 此注解用于对表名的设置
 *
 */

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

2、注解类的元素解析

总共有四个元注解,分别是:@Target,@Retention,@Documented,@Inherited,属于jdk注解,作用如下:
      @Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括: 
          ElemenetType.CONSTRUCTOR----------------------------构造器声明 
          ElemenetType.FIELD --------------------------------------域声明(包括 enum 实例) 
          ElemenetType.LOCAL_VARIABLE------------------------- 局部变量声明 
          ElemenetType.METHOD ----------------------------------方法声明 
          ElemenetType.PACKAGE --------------------------------- 包声明 
          ElemenetType.PARAMETER ------------------------------参数声明 
          ElemenetType.TYPE--------------------------------------- 类,接口(包括注解类型)或enum声明 
           
     @Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy 中,包括: 
          RetentionPolicy.SOURCE ---------------------------------注解将被编译器丢弃 
          RetentionPolicy.CLASS -----------------------------------注解在class文件中可用,但会被VM丢弃 
          RetentionPolicy.RUNTIME -------将在运行期也保留注释,因此可以通过反射机制读取注解的信息。 

 @Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param 等。
 @Inherited 允许子类继承父类中的注解。

注解使用方法:

 1 @Table("StudentInfo")
 2 public class StudentInfo extends BaseTable 
 3 {
 4     //学生姓名
 5     @Column("name")
 6     private String name;
 7     
 8     //性别
 9     @Column("sex")
10     private String sex;
11 }

注意:@Target设置了注解放在什么地方,设置为ElemenetType.FIELD,则需要将注解放置在属性的上方。设置为ElemenetType.TYPE,则需要放置在class上方。放错位置,编译器会报错。

原理:把public @interface Table {String value();}和@Table("StudentInfo")

放在一起看,其实注解就是返回你所设置的指定类型的值。其实就是在调用注解的value()方法,会返回String类型的值,值为注解中设置的“StudentInfo”。后面例子中会有详细介绍。还需注意一点,注解中返回的类型是有限定的,且方法一定要是无参的,不懂的可自行百度。

二、利用注解实现小Demo

1、需求内容

1)有一张学生信息表,字段包括姓名,年级,班级,教师名,性别等;

2)、利用注解,对表实例的每个字段进行检查,并打印出查询sql

 

2、代码结构

annotation:存放创建的注解。

bean:存放数据库表所用到的javaBean

test:main函数,用于测试并打印查询sql;

 

 

3、注解类

 

 

/**
 * 
 * @author qpf
 * 此注解用于对表字段的设置
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String value();
}

 

/**
 * 
 * @author qpf
 * 此注解用于对表名的设置
 *
 */

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

 

 

 4学生信息表的javabean

下面的代码删去了getset方法,附件包里的代码是完整的,可下载后对比一下!可以自己写一写,注意观察注解的放置位置!

/**
 * 
 * @author qpf
 * Table标签里为表名
 * Column标签里为数据库字段名
 */
@Table("StudentInfo")
public class StudentInfo extends BaseTable 
{
    //学生姓名
    @Column("name")
    private String name;
    
    //性别
    @Column("sex")
    private String sex;
    
    //年级
    @Column("grade")
    private String grade;
    
    //班级
    @Column("class_num")
    private String classNum;
    
    //班主任
    @Column("teacher")
    private String teacher;
    
    //总分
    @Column("total_scores")
    private String totalScores;
    
    //微信号
    @Column("weChat_num")
    private String weChatNum;
    
    //手机号
    @Column("tel_num")
    private String telNum;
    
    //父亲名
    @Column("father_name")
    private String fatherName;
    
    //母亲名
    @Column("mother_name")
    private String motherName;
}

 

 

5、AnnotationTest方法

 

方法已经写好了注释,方便大家观看。注解是通过反射来获取的,所以不懂反射原理的,可以先去学一下反射。Main函数中已经提供了一个StudentInfo 对象,以供大家参考。

 1 public class AnnotationTest {
 2 
 3     /**
 4      * 需求设计:
 5      *    1、有一张学生信息表,字段包括姓名,年级,班级,教师名,性别等;
 6      *    2、利用注解,对每个字段的组合条件进行检查,并打印出sql        
 7      */
 8     public static void main(String[] args)
 9     {
10         // TODO Auto-generated method stub
11         StudentInfo studentInfo=new StudentInfo();
12         studentInfo.setName("Tom");
13         studentInfo.setSex("men");
14         studentInfo.setTelNum("18255005147,18255005148,18255005149");
15         studentInfo.setWeChatNum("qpf123456");
16         String sql=querySql(studentInfo);
17         System.out.println((sql==null)?"":sql);
18     }
19 
20     public static String querySql(BaseTable table)
21     {
22         //获取类加载器
23         Class<?> cTable=table.getClass();
24         //判断类中是否包含Table的注解
25         boolean tableIsExists=cTable.isAnnotationPresent(Table.class);
26         
27         //判断是否为表实例
28         if(!tableIsExists)
29         {
30             return null;
31         }
32         
33         //获取表名
34         String tableName=cTable.getAnnotation(Table.class).value();
35         
36         //初始化表查询语句
37         StringBuilder builder=new StringBuilder("select * from");
38         builder.append(" "+tableName+" where 1=1");
39         
40         //获取表实例中的所有字段
41         Field[] fields=cTable.getDeclaredFields();
42         
43         
44         try
45         {
46             for (Field field : fields) 
47             {
48                 //判断是否为表数据字段
49                 boolean columnIsExist=field.isAnnotationPresent(Column.class);
50                 
51                 if(!columnIsExist)
52                 {
53                     continue;
54                 }
55                 
56                 //判断表数据字段的值是否为null
57                 field.setAccessible(true);
58                 String fieldValue=(String)field.get(table);
59                 if(null==fieldValue || "".equals(fieldValue))
60                 {
61                     continue;
62                 }
63                 
64                 //检测到需要拼接字段时,再获取数据库字段名
65                 String columnName=field.getAnnotation(Column.class).value();
66                 
67                 //需要用in的字段处理
68                 if(fieldValue.contains(","))
69                 {
70                     builder.append(" and "+columnName+" in(");
71                     String[] strs=fieldValue.split(",");
72                     
73                     for (int i = 0; i < strs.length; i++) 
74                     {
75                         builder.append("'"+strs[i]+"'");
76                         if(i!=strs.length-1)
77                         {
78                             builder.append(",");
79                         }
80                     }
81                         
82                     builder.append(")");
83                 }
84                 else
85                 {
86                     builder.append(" and "+columnName+"="+"'"+fieldValue+"'");
87                 }
88                 
89             }
90         } 
91         catch (Exception e) 
92         {
93             // TODO Auto-generated catch block
94             e.printStackTrace();
95         }
96         
97         return builder.toString();
98     }
99 }

 

上面方法打印出的查询sql语句为:

 

select * from StudentInfo where 1=1 and name='Tom' and sex='men' and weChat_num='qpf123456' and tel_num in('18255005147','18255005148','18255005149')

 

代码下载地址:

http://note.youdao.com/noteshare?id=1b5e2090585e5812b0a6bdcd32d67ba1&sub=70426410FD29453792ABADBC576A96E2

 

]]>
Java版 QQ空间自动登录无需拷贝cookie一天抓取30WQQ说说数据&流程分析 http://doc.okbase.net/bianqi/archive/264130.html 灯下轻敲 2017/7/19 17:58:50

QQ空间说说抓取难度比较大,花了一个星期才研究清楚!

 代码请移步到GitHub GitHub地址:https://github.com/20100507/Qzone 【没有加入多线程,希望你可以参与进来加入多线程不过 单个QQ请求频率不可以太高  过多的线程就需要 更多的QQ小号轮流登录】 不要忘了点一个赞 哈哈哈~~       

1. 截图看一看效果:

      1.1抓取执行过程:         

     1.2 部分数据截图:

 

1.3 每一个说说的具体内容

 1.4 70W说说数据文件大小:

        

 

2.接下来分析整个执行的流程

  2.1代码结构图【项目是采用Maven构建】:

       

   简单的说一下整个代码的结构:

  1. QQBean         是对QQ密码和账号的简单的一个封装,
  2. VerifyCode      提取验证码的【如果你的QQ号码是异地登录就需要输入验证码或者腾讯检测到你不安全】
  3. QzoneEncrypt 加密 俩个重要的参数 一个是加密QQ密码【腾讯在传输密码是做得很安全】,一个是加密g_tk,这个在获取说说时必须携带
  4. GetQQByProperties  读取num.properties文件中的QQ账号和密码
  5. KeyWord   对QQ账号密码 和关键参数的一个提取
  6. Login 使用加密后的密码和QQ账号 登录 获取相关重要的cookie 
  7. InputNameAndPwd 在login基础上再次调用其他登录接口直到真正登录到平常打开的QQ空间好友动态页面从页面中提取到 g_qzonetoken
  8. GetMsgBoard 获取QQ用户留言
  9. GetQNum 获取你的好友的列表
  10. GetShuoShuoData 获取说说数据
  11. login.js 是网上的一位高人,提取加密QQ密码的js精华
  12. entry.js是对g_tk的加密的获取
  13. num.properties 是存放QQ和密码的地方 如果你真的想保存数据的话 你最好放上6个无须验证码登录的QQ小号 就临时注册几个就完事了,切记一定不要使用一个,否则封号了就吉吉了。

   2.2Visio分析流程

      

 

    上面的Vsio的图,和代码中基本上都对应了,对于保存留言也很简单,换一个路径就好,所有的必要参数和cookie都已经拿到。如果想要保存一个人所有的QQ说说数据,切换分页的参数即可,but,我的代码中没有加入分页,仅仅保存每个QQ用户的首页的说说数据,懒得弄了,就是那么回事,就是在研究参数和cookie真的很费劲!!!!

 2.3 测试代码中 请注意:

    1. 注意我写的 try-catch,之前程序没有使用try-catch,程序在执行中 ,会由于网络原因抛出异常 ,如果抛给了虚拟机 ,JVM就停止,程序就挂了,干脆我就catch异常,如果catch到异常,我就在递归再次调用你,防止网络原因,导致程序极容易停止。

    2.注意一点,我的在  %25 ,也就是轮询QQ号登陆,每个QQ号采集25个用户我就重新换其他的QQ用户登录,num.properties中的QQ数量越多越好【封号率越低】,%多少看自己的喜好!

    

 1 package qzone_enter;
 2 
 3 import java.io.IOException;
 4 import java.util.List;
 5 
 6 import org.bianqi.enter.bean.QQBean;
 7 import org.bianqi.enter.key.GetQQByProperties;
 8 import org.bianqi.enter.key.KeyWord;
 9 import org.bianqi.enter.login.InputNameAndPwd;
10 import org.bianqi.getdata.GetShuoShuoData;
11 
12 public class GetMyShuoShuo {
13 
14     public static List<QQBean> listQQ = null;
15     public static int k = 0;
16     
17     static {
18         try {
19             listQQ = GetQQByProperties.getQQNumAndPwd();
20             KeyWord.uin = listQQ.get(k).getNum();
21             KeyWord.password = listQQ.get(k).getPwd();
22             System.out.println("初始化"+KeyWord.uin+"登录~~~~~~~~~~~~~");
23             InputNameAndPwd.login();
24         } catch (IOException e) {
25             e.printStackTrace();
26         }
27     }
28 
29     public static void getShuoshuoDemo(long i, long j) throws Exception {
30         try {
31             for (; i <= j; i++) {
32                 if (i % 25 == 0) {
33                     if (k == listQQ.size()) {
34                         k = 0;
35                     }
36                     KeyWord.uin = listQQ.get(k).getNum();
37                     KeyWord.password = listQQ.get(k).getPwd();
38                     System.out.println("切换到"+KeyWord.uin+"登录");
39                     InputNameAndPwd.login();
40                     k++;
41                 }
42                 GetShuoShuoData.getShuoData(Long.toString(i));
43                 System.out.println(KeyWord.uin+"正在采集==============QQ用户" + i + "数据=======================");
44             }
45         } catch (Exception e) {
46             e.printStackTrace();
47             GetMyShuoShuo.getShuoshuo(i, j);
48         }
49     }
50 
51     public static void getShuoshuo(long i, long j) throws Exception {
52         try {
53             for (; i <= j; i++) {
54                 if (i % 25 == 0) {
55                     if (k == listQQ.size()) {
56                         k = 0;
57                     }
58                     KeyWord.uin = listQQ.get(k).getNum();
59                     KeyWord.password = listQQ.get(k).getPwd();
60                     System.out.println("切换到"+KeyWord.uin+"登录");
61                     InputNameAndPwd.login();
62                     k++;
63                 }
64                 GetShuoShuoData.getShuoData(Long.toString(i));
65                 System.out.println(KeyWord.uin+"正在采集==============QQ用户" + i + "数据=======================");
66             }
67         } catch (Exception e) {
68             e.printStackTrace();
69             GetMyShuoShuo.getShuoshuoDemo(i, j);
70         }
71     }
72 
73     public static void main(String[] args) throws Exception {
74         // 开始QQ号 结束QQ号
75         long i = 669424;
76         long j = 2000000000;
77         getShuoshuo(i, j);
78     }
79 }

 

 3.1总结

                   就目前来看,我的网速可能不是很好,但是一天依然可以保存30W说说数据,但是有时候程序也会假死,也许是网络不好。

                   还有,有时候 莫名的需要你输入验证码,没有办法你就在浏览器输入这个QQ账号密码,手动多输入几次验证码,他就一般不会检测了。不在需要验证码

                  PS【验证码需要打码平台 但是我没有整,but 需要money哦~还有 , 验证码图片会自动保存到项目路径下但是,手动输入验证码目前程序也不通过,可以修复的帮我修                     复一下 谢谢~~~】

                 num.properties中 QQ号码低于12个也有可能封号,再次强调QQ小号一定要多,让他检测不出你是爬虫。如果你就一个QQ号就不要尝试玩 ,封号不要怪我!

                  没有采用多线程技术,一个原因我也没有那么多的QQ小号。如果每一个QQ访问频率太高就会封号!!

                  记得在GitHub点一个赞!哈哈哈

 

]]>
Vue2.0 生产环境部署 http://doc.okbase.net/mylly/archive/264129.html 不动峰 2017/7/19 17:58:43

简要:继上次搭建vue环境后,开始着手vue的学习;为此向大家分享从开发环境部署到生产环境(线上)中遇到的问题和解决办法,希望能够跟各位VUE大神学习探索,如果有不对或者好的建议告知下;*~*!

一、如何打包,部署到生产环境(线上)

npm run build

 

1. dist 文件结构(编译后,生成在vue目录下)

- static (存放css/js/image)
- index.html (编译后页面)

2. 部署生产环境(以Thinkphp为例)

  (1) 建立子项目,创建控制器建立index方法;实例化模板;将编译后index.html存放在对应的视图下;

  (2) 修改vue配置文件(dist下status文件,默认存放在项目的根目录;若无须改变请忽略此步骤!)   

  

(index.html 文件目录)

  

(config文件static存在路径修改,配置文件路径:config\index.js)

 

二、不加载组件(非根目录)

原因:路由path错误

 

(路由配置图)

三、刷新页面(刷新路由)出现404

1. 为什么

vue路由设置 HTML5 History 模式,直接访问/刷新url会被http server直接解析到该文件路径(被相应的框架接管),但vue的路由是虚拟的(只是告知编译后index.html文件追寻到相应的路径),并不能直接找到这个file,所以会404;

2. 怎么做(针对nignx;具体参考:HTML5 History 模式

location / {
  root (index.html存在目录路径,比如:/admin/tpl/index/) try_files $uri $uri/ /index.html; }

单配置此步骤,任意不存在页面(404)都会跳转到上述指定路径;详细解决办法请看下文;

四、任意不存在页面(404)都跳转到指定路径(针对解决404页面后)

 

个人解决方法:制作404页面的组件;

在routes.js配置,配置增加一下路由

{ path: '*', component: defaults }
以上就是我的分享,如果我的这篇文章解决了你的问题,麻烦你点个赞让更多同样情况的朋友,能更快的解决问题!
]]>
分栏报表-物品清单报表实现 http://doc.okbase.net/深巷好酒/archive/264128.html 深巷好酒 2017/7/19 17:39:53

分栏报表数据组织形式,就如同小时候使用的数学作业本一样,将数据分为多栏显示,当然写作业时有的人习惯横向写,有的人则喜欢竖着写完,在写另一栏。

 

所有的电子技术都是采用隐喻的手法,从物理世界而来,分栏报表也是源于生活,分栏报表也可以行分栏(竖向分栏)和列分栏(横向分栏)。分栏数可以分为两栏或多栏。 行分栏数据流从上往下的方向显示数据,超过固定的行数就另起一栏显示,列分栏的数据的流向是从左往右,超过固定的列数就另起一栏显示。

  

在商业报表系统中,常见的分栏报表有商品标签、员工工卡、条码打印等。由此可见,分栏报表常用于需要重复显示结构相同的条目信息,如商品标签中,标签结构相同,只是填充的商品信息不同等。

 

采用分栏报表可以很大程度上的节省空间且美观。如此重要的报表分类要是实现操作能够简便易操作设置,就更好,葡萄城报表提供了非常便捷的分栏设置,只需要一个属性即可解决分栏报表需求。

 

 

分栏报表的实现步骤

1. 新建RDL报表

 

 

2. 绑定数据

 

 

3. 新建数据集
SELECT * FROM 物品清单
ORDER BY 行号

 

 

4. 添加表格控件

 

 

5. 设置分栏属性
选择报表的灰色区域,点击属性对话框命令,注意设置分栏数量,一定要保证报表的纸张宽度> 单栏宽度* 分栏数量,否则分栏效果不会出来。

 

 

6. 预览

 

转载请注明出自:葡萄城报表

]]>
TP5 Session和Cookie http://doc.okbase.net/onestopweb/archive/264127.html onestopweb 2017/7/19 17:39:44

application\index\controller\Index.php

<?php 
namespace app\index\controller;  
use think\Controller;

class Index extends Controller
{
    public function index()
    {
        /*
         * session 函数助手
         * 
        // 赋值(当前作用域)
        session('name', 'thinkphp');
        // 赋值think作用域
        session('name', 'thinkphp', 'think');
        // 判断(当前作用域)是否赋值
        session('?name');
        // 取值(当前作用域)
        echo session('name');
        // 取值think作用域
        echo session('name', '', 'think');
        // 删除(当前作用域)
        session('name', null);
        // 清除session(当前作用域)
        session(null);
        // 清除think作用域
        session(null, 'think');
        */
        session('session_name', 'onestopweb1');
        session('sessionname.item', 'onestopweb2');
        
        /*
         * cookie 函数助手
         * 
        // 初始化
        cookie(['prefix' => 'think_', 'expire' => 3600]);
        // 设置
        cookie('name', 'value', 3600);
        // 判断
        cookie('?name');
        // 获取
        echo cookie('name');
        // 删除
        cookie('name', null);
        // 清除
        cookie(null, 'think_');
        */
        cookie('cookieName', 'onestopwebCookie', 3600);
        return $this->fetch();
    }
}

 

application\index\view\index\index.html

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Session和Cookie</title>
<!-- TP5内置标签:资源文件加载  -->
{css href="/asset/css/base.css" /}
{js href="/asset/js/jquery.js" /}
<style type="text/css">
p{ text-align: center;}
</style>
</head>
<body>
	<p>session 取值</p>
	<p>{$Request.session.session_name}</p>
	<p>{$Request.session.sessionname.item}</p>
	<p>cookie 取值</p>
	<p>{$Request.cookie.cookieName}</p>
</body>
</html>

 

效果图:

 

 

 

 

 

 

 

 

 

 

  • 大小: 7.5 KB
  • tp5.rar (33.7 KB)
  • 下载次数: 0
]]> 设计模式解密(11)- 命令模式 http://doc.okbase.net/JsonShare/archive/264126.html _Json 2017/7/19 17:35:53

1、简介

定义:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操作;

主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种紧耦合的设计就不太合适;

英文:Command

类型:行为型模式

2、类图及组成

(引)类图:

组成:
  ● Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。

  ● ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。

  ● Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。

  ● Receiver(接收者):接收者请求与执行相关的操作,它具体实现对请求的业务处理。

  ● Client(客户端角色类):最终的客户端调用类。

代码结构:

抽象命令角色类
public interface Command {
    /**
     * 执行方法
     */
    void execute();
}

具体命令角色类
public class ConcreteCommand implements Command {
    //持有相应的接收者对象
    private Receiver receiver = null;
    /**
     * 构造方法
     */
    public ConcreteCommand(Receiver receiver){
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        //通常会转调接收者对象的相应方法,让接收者来真正执行功能
        receiver.action();
    }
}

接收者角色类
public class Receiver {
    /**
     * 真正执行命令相应的操作
     */
    public void action(){
        System.out.println("执行操作");
    }
}

请求者角色类
public class Invoker {
    /**
     * 持有命令对象
     */
    private Command command = null;
    /**
     * 构造方法
     */
    public Invoker(Command command){
        this.command = command;
    }
    /**
     * 行动方法
     */
    public void action(){
        command.execute();
    }
}

客户端角色类
public class Client {
    public static void main(String[] args) {
        //创建接收者
        Receiver receiver = new Receiver();
        //创建命令对象,设定它的接收者
        Command command = new ConcreteCommand(receiver);
        //创建请求者,把命令对象设置进去
        Invoker invoker = new Invoker(command);
        //执行方法
        invoker.action();
    }
}

3、实例引入

下面以电视机的开、关命令为例,进行讲解:

package com.designpattern.Command;
/**
 * 抽象命令角色类
 * @author Json
*/
public interface Command {
     /**
     * 执行方法
     */
    void execute();
}
package com.designpattern.Command;
/**
 * 接收者角色类  -- 电视
 * @author Json
*/
public class TV {
    /**
     * 打开方法
     */
    public void on(){
        System.out.println("电视打开");
    }
    /**
     * 关闭方法
     */
    public void off(){
        System.out.println("电视关闭");
    }
}
package com.designpattern.Command;
/**
 * 具体命令角色类  -- 电视打开命令
 * @author Json
*/
public class TVOnCommand implements Command {
    private TV tv;
    public TVOnCommand(TV tv){
        this.tv = tv;
    }
    
    @Override
    public void execute() {
        tv.on();
    }
}
package com.designpattern.Command;
/**
 * 具体命令角色类  -- 电视关闭命令
 * @author Json
*/
public class TVOffCommand implements Command {
    private TV tv;
    public TVOffCommand(TV tv){
        this.tv = tv;
    }
    
    @Override
    public void execute() {
        tv.off();
    }
}
package com.designpattern.Command;
/**
 * 请求者角色类  -- 遥控器Invoker
 * @author Json
*/
public class RemoteControl {
    private Command onCommand,offCommand;
    
    public RemoteControl(Command _on,Command _off){
        this.onCommand = _on;
        this.offCommand = _off;
    }
    
    public void turnOn(){
        onCommand.execute();
    }
    
    public void turnOff(){
        offCommand.execute();
    }
}

 测试:

package com.designpattern.Command;
/**
 * 客户端角色类
 * @author Json
*/
public class Client {
    public static void main(String[] args) {
        //创建接收者
        TV receiver = new TV();
        //创建命令对象,设定它的接收者
        Command on_command = new TVOnCommand(receiver);
        //创建命令对象,设定它的接收者
        Command off_command = new TVOffCommand(receiver);
        //命令控制对象Invoker,把命令对象通过构造方法设置进去 或者 增加set方法set进去是一样的(setCommand方法未写)
        RemoteControl invoker = new RemoteControl(on_command,off_command);
        //执行方法  -- 打开电视
        invoker.turnOn();
        //执行方法 -- 关闭电视
        invoker.turnOff();
    }
}

 结果:

电视打开
电视关闭

关于例子的补充说明:

虽然代码看似挺多,但其实命令模式的结构还是比较清晰的,总的来说命令模式的使用流程就是首先创建一个抽象命令,然后创建多个具体命令实现抽象命令接口,然后创建一个命令接受者角色,它包含各种的行为的具体实现,然后再有一个命令调用者角色,提供给客户端,用于接收客户端的参数;

4、命令模式的扩展

 由于篇幅太长,这里单独拆分出去了;

传送门:

  命令模式扩展篇 - 宏命令:http://www.cnblogs.com/JsonShare/p/7206395.html

  命令模式扩展篇 - 撤销命令:http://www.cnblogs.com/JsonShare/p/7206513.html

  命令模式扩展篇 - 命令队列:http://www.cnblogs.com/JsonShare/p/7206607.html

  命令模式扩展篇 - 请求日志:http://www.cnblogs.com/JsonShare/p/7206665.html

5、优缺点

优点:

  1、命令模式将行为调用者和各种行为分隔开,降低程序的耦合,便于程序扩展;

  2、命令模式将行为的具体实现封装起来,客户端无需关心行为的具体实现;

  3、命令模式可为多种行为提供统一的调用入口,便于程序对行为的管理和控制;

缺点:

  使用命令模式的话,不用管命令多简单,都需要写一个命令类来封装,使用命令模式可能会导致系统有过多的具体命令类;

     (上面的例子就可以看出,每个命令都要有一个具体的命令类);

6、应用场景

  1、希望将行为请求者和行为实现者解耦,不直接打交道;

  2、希望分离掉行为请求者一部分的责任,行为请求者只需要将命令发给调用者,不再主动的去让行为实现者产生行为,符合单一职责原则;

  3、希望可以控制执行的命令列表,方便记录,撤销/重做以及事务等功能;

  4、期待可以将请求排队,有序执行;

  5、希望可以将请求组合使用,即支持宏命令;

7、总结

  命令模式最大的好处就是实现了行为请求者与行为实现者的解耦;

  在实际场景中的使用:

      Struts2中action中的调用过程中存在命令模式;

      数据库中的事务机制的底层实现;

      命令的撤销和恢复:增加相应的撤销和恢复命令的方法(比如数据库中的事务回滚);

  例如:Java的Runnable就是命令模式的变形应用:

public class Test {
    public static void main(String[] args) {
        //范例
        Runnable runnable = () -> System.out.println("具体命令"); // Command cmd = ConcreteCommand
        Thread thread1 = new Thread(runnable); // 将 cmd 交给 Thread (Invoker)
        thread1.start(); // Invoker 调用 cmd 的执行方法
    }
} 

 

 

PS:源码地址   https://github.com/JsonShare/DesignPattern/tree/master 

     

]]>
nodejs-ORM 操作数据库中间件waterline的使用 http://doc.okbase.net/stulzq/archive/264125.html 辰晓晨 2017/7/19 16:51:02

waterline和Sails.js同一团队开发,支持几乎所有的主流数据库,是nodejs下一款非常强大的orm,可以显著提升开发效率

 

一.waterline支持的数据库

 

 

二.waterline的配置

Waterline 之所以可以使用一样的代码来操作多种数据库,奥妙在于其适配器。在进行配置的时候,需要设置两方面的内容,一是具体使用哪些适配器,二是建立数据库连接的时候,使用哪个适配器。下面是使用 MongoDB/Mysql 的适配器创建一个数据库连接的配置

MongoDB:

 1 var mongoAdapter = require('sails-mongo');
 2 var wlconfig = {
 3   adapters: {
 4     'default': mongoAdapter,
 5     'mongo': mongoAdapter
 6   },
 7   connections: {
 8     'mongo': {
 9       // adapters 中的适配器代码
10       adapter: 'mongo',
11       url: 'mongodb://localhost/waterline-sample'
12     }
13   }
14 };
sails-mongo 为mongo的适配器,执行命令安装:
npm i sails-mongo --save

Mysql:

 1 var mysqlAdapter = require('sails-mysql');
 2 var Waterline = require('waterline');
 3 
 4 var icbcgold = require('../models/IcbcGold')
 5 
 6 //适配器与连接设置
 7 var wlconfig = {
 8     adapters: {
 9         mysql: mysqlAdapter, //mysql适配器
10         default: 'mysql' //默认的适配器
11     },
12     connections: {
13         //mysql连接
14         mysql: {
15             adapter: 'mysql',//指定适配器为mysql
16             url: 'mysql://root:@localhost/IcbcGold' //连接字符串
17         }
18     }
19 }
sails-mysql 为mysql适配器,执行命令安装:
1 npm i sails-mysql --save

 连接字符串说明:数据库类型://用户名:密码@数据库地址/数据库名

三.waterline的Model的定义

执行命令安装waterline:

 

npm i waterline --save

 

 1 var Waterline = require('waterline');
 2 
 3 module.exports = Waterline.Collection.extend({
 4     identity: 'icbcgold',  //模型名,如果没有设置 tableName 属性,那么waterline默认将模型名设置为表名
 5     tableName: 'tb_IcbcGold',//指定表名
 6     connection: 'mysql',//指定数据库连接
 7     // 是否强制模式
 8     schema: false,
 9     attributes: {
10         Id: {
11             type: 'string',
12             primaryKey: true
13         },
14         DataTime: {
15             type: 'datetime'
16         },
17         DataNumber: {
18             type: 'float'
19         }
20     }
21 });

 

配置相当简单方便,类似于 Mongoose 中的 Schema。但要注意,指定属性的字段时,使用的是一个字符串值,而不是 JavaScript 中的具体类型,目前支持的数据类型有 string / text / integer / float / date /time / datetime / boolean / binary / array / json,这个范围要比 JavaScript 的类型范围大。

除了这四个基本配置,还可以配置校验器,添加自定义的方法,设置生命周期回调方法等。

 

注意:

  1.如果没有定义主键,那么waterline会为你默认创建名为id的主键,类型是整型自增长

  2.waterline自动创建表时会为你添加 createdAt、updatedAt两个字段,类型为datetime,分别在insert和update操作更新字段代表的是记录的创建时间和更新时间

  3.如果不想自动创建列createdAt、updatedAt,那么请设置autoCreatedAt,autoUpdatedAt的值为false,如下图

  

  4.waterline会自动根据定义的model创建表,但是如果你已经创建好了表,但是与model定义有所区别,比如字段名不一样,那么一定要注意了,请按照下图设置,否则waterline将会删除已经存在表且根据model重新创建,以前的数据就完蛋了

  

 

 

 

四.校验器

校验器是在创建数据集合的时候指定给具体的属性的

更多设置请查看:https://www.npmjs.com/package/waterline

 1 attributes: {
 2         title: {
 3             type: 'string',
 4             required: true,//必须的字段
 5             maxLength: 100,//最大长度100
 6             minLength: 5 //最小长度5
 7         },
 8         views: {
 9             type: 'integer',
10             min: 0
11         },
12         createTime: {
13             type: 'date',
14             // 在某个时间点之前
15             before: '2017-12-31', 
16             // 在某个时间点之后
17             after: function () {
18                 return new Date();
19             }
20         }
21     }

 

五.生命周期回调

 可以通过waterline,来实现在进行特定操作的时候,调用自定义的方法。,在 create / update / destory时,均有多种回调。直接提供对应的方法名,分别是:

  • 创建时:beforeValidate / afterValidate / beforeCreate / afterCreate
  • 更新时:beforeValidate / afterValidate / beforeUpdate / afterUpdate
  • 删除时:beforeDestroy / afterDestroy

这些方法,需要在初始化数据集合的时候进行定义。

 1 //values值 cb回调方法
 2 beforeCreate: function(values, cb) {
 3  
 4     // 加密password字段的值
 5     encrypt(values.password, function(err, password) {
 6       if(err) return cb(err);
 7  
 8       values.password = password;
 9       cb();
10     });
11   },

 

六.查询方法

waterline有以下查询方法

 

1.查询 name 等于 foo 的记录

Model.find({ name: 'foo' })

 2.多条件查询 查询 name 等于 water 并且 state 等于new mexico 的记录

1 Model.find({ name: 'walter', state: 'new mexico' })
User.find()
.where({ id: { '>': 100 }})
.where({ age: 21 })
.limit(100)
.sort('name')
.exec(function(err, users) {
  // Do stuff here 
});

 

下面的修饰符可用于构建查询时使用。

  • '<' / '小于'
  • '<=' / '小于等于'
  • '>' / '大于'
  • '>=' / '大于等于'
  • '!' / '非'
  • 'like'/'模糊匹配'
  • 'contains'/'包含'
  • 'startsWith'/'以某字符开头'
  • 'endsWith'/'以某字符结尾'

 3.分页查询

1 User.find().skip(10).limit(10); //跳过10条记录 取10条记录

 

1 Model.find({ where: { name: 'foo' }, skip: 20, limit: 10, sort: 'name DESC' });//带条件分页查询

 

User.find().paginate({page: 2, limit: 10});//根据页数分页查询

 4.新增记录

User.create({Id:'xxx',Name:'xxx'});

 

5.更新记录

User.update({ name: 'Walter Jr' }, { name: 'Flynn' })

 

6.删除记录

User.destroy({ name: 'Flynn' })

Promises

 1 User.findOne()
 2 .where({ id: 2 })
 3 .then(function(user){
 4     var comments = Comment.find({userId: user.id}).then(function(comments){
 5         return comments;
 6     });
 7     return [user.id, user.friendsList, comments];
 8 }).spread(function(userId, friendsList, comments){
 9     // Promises are awesome! 
10 }).catch(function(err){
11     // An error occurred 
12 })

 

七.使用示例

这里使用的数据库是mysql创建一个数据库名为:IcbcGold

1.新建一个js文件:waterline.js,代码如下:

 1 var mysqlAdapter = require('sails-mysql');
 2 var Waterline = require('waterline');
 3 
 4 var icbcgold = require('../models/IcbcGold')
 5 
 6 //适配器与连接设置
 7 var wlconfig = {
 8     adapters: {
 9         mysql: mysqlAdapter, //mysql适配器
10         default: 'mysql' //默认的适配器
11     },
12     connections: {
13         //mysql连接
14         mysql: {
15             adapter: 'mysql',//指定适配器为mysql
16             url: 'mysql://root:@localhost/IcbcGold' //连接字符串
17         }
18     }
19 }
20 
21 var orm = new Waterline();
22 
23 //加载model集合
24 orm.loadCollection(icbcgold);
25 
26 exports.orm = orm;
27 exports.wlconfig = wlconfig;

 

2.新建文件 index.js

var waterline = require('./app/config/waterline');
var uuid = require('uuid');


var ormmodels = null;

//初始化waterline
waterline.orm.initialize(waterline.wlconfig, function (err, models) {
    if (err) {
        return;
    }
    ormmodels = models.collections;
})

 

//执行查询
ormmodels.icbcgold.count({ DataTime: dataTime }).exec(function (err, found) {
        if (err) {
            return;
        }
        if (found === 0) {
            ormmodels.icbcgold.create({ Id: uuid.v1(), DataTime: dataTime, DataNumber: dataNumber }, function (err, models) {
                if (err) {
                    return;
                }
            });
        }
    });

 呼~~~~终于写完了,下面帖几个网址,如果你有什么问题可以先查看

http://sailsjs.com/documentation/reference/waterline-orm/models

https://www.npmjs.com/package/waterline

waterline使用是非常简单的,目前用nodejs写了一个爬虫,使用waterline存储数据到mysql,已经部署到服务器上,使用pm2运行,抓取数据用的是superagent,后面我会继续写如果用nodejs写爬虫,欢迎关注!

]]>
Android后门GhostCtrl,完美控制设备任意权限并窃取用户数据 http://doc.okbase.net/alisecurity/archive/264124.html 阿里聚安全 2017/7/19 16:50:55

Android系统似乎已经成为世界各地病毒作者的首选目标,每天都有新的恶意软件在感染更多的设备。 这一次,安全公司趋势科技发布警告,他们发现了一个新的Android后门——GhostCtrl

 

GhostCtrl被发现有3个版本,第一代窃取信息并控制一些设备的功能,第二代增加了更多的功能来劫持设备,第三代结合了早期的版本特性,功能更加强大,黑客可以完全控制设备,并访问和传输本地存储的任何数据的权利。

 


 

恶魔般的GhostCtrl 

GhostCtrl实际上是在2015年底被发现OmniRAT的一个变种。OmniRAT是一款非常流行的远程控制工具,它的终身许可证服务以及客户端服务仅售25美金和50美金,并且运营商还提供终身维护服务。OmniRAT可以通过Android设备上的一个按钮远程控制Windows,Linux和Mac系统。

 

该恶意软件会伪装成一个合法或流行的应用程序,它会命名为APP,彩信,WhatsApp,甚至是Pokemon GO。当它启动时,看上去它和正常的APP一样,但是实际上恶意软件已隐秘的保存于设备中。

 

当用户点击伪装APK后,它会要求用户执行安装。用户很难逃过一劫,即使用户取消安装提示,该提示仍然会立即弹出。该恶意APK没有图标,一旦安装,恶意程序会立即在后台运行。

 

该恶意软件的后门被命名为com.android.engine,目的是误导用户认为它是一个合法的系统应用。它将连接到C&C服务器并检索3176端口的指令。

 

该恶意软件允许黑客从一个被感染的设备中窃取几乎任何东西,包括通话记录、短信记录、联系人、电话号码、sim序列号、位置和浏览器书签。此外,它还可以从摄像机、运行进程甚至墙纸中获取数据。最糟糕的是,黑客可以启动摄像机或录制音频,然后将内容上传到服务器,所有的数据都在该过程中加密。


恶意软件作者还可以向受感染的手机发送命令,以执行更特殊的任务,如重新设置已配置帐户的密码或使手机播放不同的声音效果。


正如遇到的其他恶意软件,避免下载不受信任的来源的应用程序是最好的防护手段

 

-----------------------------------------

* 本文阿里聚安全编译,原文地址:http://news.softpedia.com/news/android-backdoor-ghostctrl-can-steal-everything-from-a-phone-spy-on-users-517015.shtml

更多安全资讯及知识分享,请关注阿里聚安全的官方博客

]]>
如何快速轻松学习bootstrap http://doc.okbase.net/shaoshuai0305/archive/264123.html 陆海空大元帅 2017/7/19 16:05:19

我以前也是通过看一些视频教程来学的,比如慕课网上的,比如51cto上的那些零基础入门bootstrap什么的,还有一些培训班里流传出来的,感觉晕乎乎的,不知所云。

还是在后面不断使用的过程中慢慢体会到它的价值。相信很多没有什么基础的初学者学习它定会感到无从下手、云里雾里、难以把控。最后实在是看不出它好在哪里,学了也就相当于白学了,也就逐渐的放弃了。

你学不好它是因为对自己要求太完美,同时对它又缺少整体意识。你不知道它是怎么来的,目的是干什么,也不知道什么时候该用它,甚至即使用了,也达不到你想要的100%的效果,如此不完美而又难学的东西,学了又有什么意义?这样不是怀疑自己的能力就是怀疑它,导致难以继续。

其实bootstrap非常有用。我结合自己对它的使用经验总结一些关于如何轻松学习它的方法技巧。包含:对它的全面重新认识、学习方法,学习内容、以及它的常用功能和对待它的正确态度等方面内容。

首先来看看它是什么?

学了它有什么用呢?如果不学行不行?要回答这个,那就是要说说它是什么了。它是在html,css,js,jquery基础上进一步封装出的一个东西,定位为前端框架,具有跨平台兼容功能,非常善于做自适应的效果和提供常用便捷的效果。

所以,它自然包含了很多功能。如:下拉功能。所以如果你想要深入学会它,你首先得会js和jquery。那么使用它必然就是会和js,jquery一起结合使用,方能应用自如。如果真正是零基础,我建议你先不要来学它,还是先了解

一些html,css,js,jquery这些支撑它的内容。

2:bootstrap的学习方法

一开始你不要试着学习它的所有功能,包括某个功能的所有实现效果,不要贪,不要想着一口气吃成个胖子。这样很容易打击你的信心。比如我一开始使用的时候就只使用它的样式。这样容易上手,如给a标签添加个class="btn btn-primary",确实可以看到按钮变的好看的效果。又如:面包屑导航。只要写上去就可以看见效果,这些小效果会给自己增加信心。而很多的时候你用的最多的也是这些小效果......

至于想要的行为,你可以试着先自己用js或jquery来实现,能实现就更好了,至少你知道了那些行为的来源,此时你再慢慢调用它的js来实现功能,毕竟它做了兼容性处理。

一开始可以实现那些小一点的功能,如:下拉功能。你可以自己写,可以用原生态js自己写,用jquery自己写。这样当你写的和它的差不多了,你其实就在慢慢摸索它做的那些行为的来源了,当你使用它的时候,自然就有据可依知道它的来源了,也就变得得心应手了,兴趣也跟着增加起来。

要等你用它比较多了,你就可以深入的去看它的源码了,它的html,css,js,相信这时候你会对它更深入的认识了,而且能随心所欲的使用和修改。此时你就可以把它派上你的项目的各个用场。

3:bootstrap的学习内容

它很重要的一个东西就是布局:栅格布局。这个很有用,但也很抽象,很多初学者学了很久不知道怎么用它,什么时候用它,用来干什么。于是就经常被初学者放弃了,有点“捡芝麻、丢西瓜”的味道。它的神奇之处当然就没感觉不到了。

一些常用的class,如:btn,btn-primary,如:.table 等

然后再看一下代码稍微多一点的功能如:

面包屑导航

导航条

面板

注意:会用一两个都会让你兴趣慢慢培养起来,所以不要放弃那些只要稍微用一下就能学会的小功能的影响力。

4:对待bootstrap的态度

不要要求太完美,能用多少就用多少。它只是帮你快速的架出某个功能的架子,并不是一调用它就一定会完美。很有可能要修改它,包括修改它的css,js行为,或者添加个没有的效果。这样才能让它实用在各种场合。

比如它的面包屑导航,通常你复制过来了粘贴到自己的项目中就可以看到一个导航条,但是颜色、字体大小,文字和符号直接按的分隔距离等可能都不适合你的需求。

此时你千万不要认为你还不会用bootstrap,其实你已经会用了。你要做的而且必须要做的就是要在它的基础之上重新调整它。添加新样式,并结合你需要的功能添加其他的行为效果。

所以对待bootstrap,一定不要迷信它,不要认为它是万能的和完美的。因此也就不要认为一旦调用了它的什么功能,它就应该100%的满足你需要的任何要求。

它没有什么标准答案,只是匹配程度是多少而已。你总需要修改的。因此,应该不断加强css,js,jquery的学习,因为你迟早要修改,因为它的意义就是帮你搭建90%,而你总要细调那10%。

如果对它要求太高了,你就会失望太大,就会怀疑自己,这样的效果就不好了。

5:要多使用它

勤能补拙,熟能生巧。在你的项目中都试图去使用它,不要怕错。慢慢总结和积累经验。不能因为怕错就不用它了。能用多少就用多少,多试着修改和调整,一定要记录使用经验。用的越来越多,也就越来越顺手了。

如果不知道自适应是什么效果,你可以用电脑把浏览器的宽度缩小,然后慢慢的拉大,去访问这个用bootstrap做的自适应的网站:www.phpkhbd.com,以及用手机去访问...同一个网站针对不同的客户端,会显示不同的效果的。

欢迎前来交流,探讨更多bootstrap的魅力和奥秘。

【转载】

]]>
编写高质量代码改善C#程序的157个建议:第17个建议之多数情况下使用foreach进行循环遍历 http://doc.okbase.net/PatrickLiu/archive/264122.html PatrickLiu 2017/7/19 16:05:00

      今天是我看《编写高质量代码:改善C#程序的157个建议》第二遍的时候了,看完这本书的确是受益匪浅,学到了很多东西,也明白了很多道理。

     里面的代码我每个都调试了一遍,有时候是有些出入的,可能是作者写的书比较早,使用的开发环境比较旧,也许是我的学习还不到家,今天在看建议17的时候,发现了一些小问题,不是很大,是小问题,记录下来,当别人看到的时候可以起到修正的作用。

     可能很多人和我一样,没有太在乎for和foreach的区别,也没有深究其底层发生的一些东西,看了文章才发现里面的东西还真是不少。

    好了,我们从头聊一下,我对foreach的认识。

     Gof的23种设计模式,我相信大家都看过,我现在也正在写有关《设计模式》的一些文章。在这些设计模式种,有一个设计模式叫“迭代器”,这种设计模式就是针对集合对象的迭代而产生的。具体的模式我就不介绍了,FCL框架中也有针对的此模式的具体实现,具体的接口分别是IEnumerable和IEnumerator接口。迭代器模式屏蔽了各种集合的内部实现,为客户对集合的迭代生成了一个统一接口。foreach的使用语法就是针对FCL框架中实现的迭代器的统一操作实现,简化了语法,方便了客户的实现。

    通过该文章和对IL代码的分析,foreach除了可以提供简化语法外,还有另外两个有事:

      1、自动将代码置入try-finally块。

      2、若类型实现了IDisposable接口,他会在循环结束后自动调用Dispose方法。

   这是书里的原话,但是这两大优点是有出入的,并不是所有的集合类型都会将代码自动放入try-finally块中。

    我们先来了解一下集合概况吧,集合类型主要分为两种,一种是线性集合,另一种是非线性集合,如图:

     

  1、我们先针对线性集合测试,看看结果怎么样:

      1)、线性集合里面的直接存取类型的代表:数组

     

int[] arra = new int[] { 1, 2, 3, 4, 5, 6, 7 };

 foreach (int item in arra)
 {
     Console.WriteLine(item);
 }

  代码运行结果是:

  

我们再来看看他们所生成的IL代码:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       56 (0x38)
  .maxstack  3
  .locals init ([0] int32[] arra,
           [1] int32[] V_1,
           [2] int32 V_2,
           [3] int32 item)
  IL_0000:  nop
  IL_0001:  ldc.i4.7
  IL_0002:  newarr     [mscorlib]System.Int32
  IL_0007:  dup
  IL_0008:  ldtoken    field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=28' '<PrivateImplementationDetails>'::'447E020739FB3351B9350DB35F297A3AD27669A4'
  IL_000d:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array,
                                                                                                      valuetype [mscorlib]System.RuntimeFieldHandle)
  IL_0012:  stloc.0
  IL_0013:  nop
  IL_0014:  ldloc.0
  IL_0015:  stloc.1
  IL_0016:  ldc.i4.0
  IL_0017:  stloc.2
  IL_0018:  br.s       IL_002b
  IL_001a:  ldloc.1
  IL_001b:  ldloc.2
  IL_001c:  ldelem.i4
  IL_001d:  stloc.3
  IL_001e:  nop
  IL_001f:  ldloc.3
  IL_0020:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0025:  nop
  IL_0026:  nop
  IL_0027:  ldloc.2
  IL_0028:  ldc.i4.1
  IL_0029:  add
  IL_002a:  stloc.2
  IL_002b:  ldloc.2
  IL_002c:  ldloc.1
  IL_002d:  ldlen
  IL_002e:  conv.i4
  IL_002f:  blt.s      IL_001a
  IL_0031:  call       int32 [mscorlib]System.Console::Read()
  IL_0036:  pop
  IL_0037:  ret
} // end of method Program::Main

   我们针对数组执行foreach迭代,但是在所生成的IL代码中并没有看到try-finally中,最起码连try和finally这个两个字样都没看到,书中说foreach遍历集合会自动生成try-finally块,这是不准确的,最起码数组没有生成try-finally代码块。

    2)、然后我们再测试一下string类型,看看他的运行结果怎么样!

       代码如下:

string dd = "我是中国人";
foreach (var item in dd)
{
    Console.WriteLine(item);
 }

 执行效果如图:
 

让我们再来看看它所生成的IL代码吧,不要惊讶:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       51 (0x33)
  .maxstack  2
  .locals init ([0] string dd,
           [1] string V_1,
           [2] int32 V_2,
           [3] char item)
  IL_0000:  nop
  IL_0001:  ldstr      bytearray (11 62 2F 66 2D 4E FD 56 BA 4E )                   // .b/f-N.V.N
  IL_0006:  stloc.0
  IL_0007:  nop
  IL_0008:  ldloc.0
  IL_0009:  stloc.1
  IL_000a:  ldc.i4.0
  IL_000b:  stloc.2
  IL_000c:  br.s       IL_0023
  IL_000e:  ldloc.1
  IL_000f:  ldloc.2
  IL_0010:  callvirt   instance char [mscorlib]System.String::get_Chars(int32)
  IL_0015:  stloc.3
  IL_0016:  nop
  IL_0017:  ldloc.3
  IL_0018:  call       void [mscorlib]System.Console::WriteLine(char)
  IL_001d:  nop
  IL_001e:  nop
  IL_001f:  ldloc.2
  IL_0020:  ldc.i4.1
  IL_0021:  add
  IL_0022:  stloc.2
  IL_0023:  ldloc.2
  IL_0024:  ldloc.1
  IL_0025:  callvirt   instance int32 [mscorlib]System.String::get_Length()
  IL_002a:  blt.s      IL_000e
  IL_002c:  call       int32 [mscorlib]System.Console::Read()
  IL_0031:  pop
  IL_0032:  ret
} // end of method Program::Main

  这里面也没有生成try-finally代码块,说明并不是所有的集合都会自动生成try-finally代码块的。

3)、我们继续测试一下Dictionary<TKey,TValue>,List<T>,Queue<T>,Stack<T>,ArrayList类型:

     (1)、Dictionary<TKey,TValue>测试代码:

            Dictionary<string, string> dictionary = new Dictionary<string, string>();
            dictionary.Add("1", "11");
            dictionary.Add("2", "22");
            dictionary.Add("3", "33");
            dictionary.Add("4", "44");
            dictionary.Add("5", "55");
            dictionary.Add("6", "66");
            dictionary.Add("7", "77");

            foreach (var item in dictionary)
            {
                Console.WriteLine(item.Key + "----" + item.Value);
            }

      字典类型所生成IL代码如下:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       209 (0xd1)
  .maxstack  3
  .locals init ([0] class [mscorlib]System.Collections.Generic.Dictionary`2<string,string> dictionary,
           [1] valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<string,string> V_1,
           [2] valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string> item)
  IL_0000:  nop
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldstr      "1"
  IL_000d:  ldstr      "11"
  IL_0012:  callvirt   instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0,
                                                                                                                 !1)
  IL_0017:  nop
  IL_0018:  ldloc.0
  IL_0019:  ldstr      "2"
  IL_001e:  ldstr      "22"
  IL_0023:  callvirt   instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0,
                                                                                                                 !1)
  IL_0028:  nop
  IL_0029:  ldloc.0
  IL_002a:  ldstr      "3"
  IL_002f:  ldstr      "33"
  IL_0034:  callvirt   instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0,
                                                                                                                 !1)
  IL_0039:  nop
  IL_003a:  ldloc.0
  IL_003b:  ldstr      "4"
  IL_0040:  ldstr      "44"
  IL_0045:  callvirt   instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0,
                                                                                                                 !1)
  IL_004a:  nop
  IL_004b:  ldloc.0
  IL_004c:  ldstr      "5"
  IL_0051:  ldstr      "55"
  IL_0056:  callvirt   instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0,
                                                                                                                 !1)
  IL_005b:  nop
  IL_005c:  ldloc.0
  IL_005d:  ldstr      "6"
  IL_0062:  ldstr      "66"
  IL_0067:  callvirt   instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0,
                                                                                                                 !1)
  IL_006c:  nop
  IL_006d:  ldloc.0
  IL_006e:  ldstr      "7"
  IL_0073:  ldstr      "77"
  IL_0078:  callvirt   instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0,
                                                                                                                 !1)
  IL_007d:  nop
  IL_007e:  nop
  IL_007f:  ldloc.0
  IL_0080:  callvirt   instance valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<!0,!1> class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::GetEnumerator()
  IL_0085:  stloc.1
  .try
  {
    IL_0086:  br.s       IL_00b0
    IL_0088:  ldloca.s   V_1
    IL_008a:  call       instance valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<!0,!1> valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<string,string>::get_Current()
    IL_008f:  stloc.2
    IL_0090:  nop
    IL_0091:  ldloca.s   item
    IL_0093:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string>::get_Key()
    IL_0098:  ldstr      "----"
    IL_009d:  ldloca.s   item
    IL_009f:  call       instance !1 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string>::get_Value()
    IL_00a4:  call       string [mscorlib]System.String::Concat(string,
                                                                string,
                                                                string)
    IL_00a9:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_00ae:  nop
    IL_00af:  nop
    IL_00b0:  ldloca.s   V_1
    IL_00b2:  call       instance bool valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<string,string>::MoveNext()
    IL_00b7:  brtrue.s   IL_0088
    IL_00b9:  leave.s    IL_00ca
  }  // end .try
  finally
  {
    IL_00bb:  ldloca.s   V_1
    IL_00bd:  constrained. valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<string,string>
    IL_00c3:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_00c8:  nop
    IL_00c9:  endfinally
  }  // end handler
  IL_00ca:  call       int32 [mscorlib]System.Console::Read()
  IL_00cf:  pop
  IL_00d0:  ret
} // end of method Program::Main

  哈哈哈,终于出现了,自动生成try-finally代码块,并且调用了Dispose方法。

(2)List<T>,Queue<T>,Stack<T>,ArrayList测试代码如下:

    

List<int> list = new List<int>() { 1, 2 };

foreach (var item in list)
 {
       Console.WriteLine(item);
}
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       83 (0x53)
  .maxstack  3
  .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> list,
           [1] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> V_1,
           [2] int32 item)
  IL_0000:  nop
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
  IL_0006:  dup
  IL_0007:  ldc.i4.1
  IL_0008:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
  IL_000d:  nop
  IL_000e:  dup
  IL_000f:  ldc.i4.2
  IL_0010:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
  IL_0015:  nop
  IL_0016:  stloc.0
  IL_0017:  nop
  IL_0018:  ldloc.0
  IL_0019:  callvirt   instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
  IL_001e:  stloc.1
  .try
  {
    IL_001f:  br.s       IL_0032
    IL_0021:  ldloca.s   V_1
    IL_0023:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
    IL_0028:  stloc.2
    IL_0029:  nop
    IL_002a:  ldloc.2
    IL_002b:  call       void [mscorlib]System.Console::WriteLine(int32)
    IL_0030:  nop
    IL_0031:  nop
    IL_0032:  ldloca.s   V_1
    IL_0034:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
    IL_0039:  brtrue.s   IL_0021
    IL_003b:  leave.s    IL_004c
  }  // end .try
  finally
  {
    IL_003d:  ldloca.s   V_1
    IL_003f:  constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
    IL_0045:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_004a:  nop
    IL_004b:  endfinally
  }  // end handler
  IL_004c:  call       int32 [mscorlib]System.Console::Read()
  IL_0051:  pop
  IL_0052:  ret
} // end of method Program::Main

其他的代码我就不贴了,都生成了try-finally代码块,如果类型实现了IDisposeable接口,也会调用Dispose方法。

2、我们再测试一下非线性集合是否会自动产生相关代码

    我们测试一下HashSet<T>代码,看看效果怎么样。

   

  HashSet<string> hash = new HashSet<string>();
  hash.Add("1");
  hash.Add("2");
  hash.Add("2");
  hash.Add("3");
  hash.Add("4");
  hash.Add("5");

  foreach (var item in hash)
  {
    Console.WriteLine(item);
  }

   运行效果图:

  

 最后我们看看IL代码:

  

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       139 (0x8b)
  .maxstack  2
  .locals init ([0] class [System.Core]System.Collections.Generic.HashSet`1<string> hash,
           [1] valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<string> V_1,
           [2] string item)
  IL_0000:  nop
  IL_0001:  newobj     instance void class [System.Core]System.Collections.Generic.HashSet`1<string>::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldstr      "1"
  IL_000d:  callvirt   instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0)
  IL_0012:  pop
  IL_0013:  ldloc.0
  IL_0014:  ldstr      "2"
  IL_0019:  callvirt   instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0)
  IL_001e:  pop
  IL_001f:  ldloc.0
  IL_0020:  ldstr      "2"
  IL_0025:  callvirt   instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0)
  IL_002a:  pop
  IL_002b:  ldloc.0
  IL_002c:  ldstr      "3"
  IL_0031:  callvirt   instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0)
  IL_0036:  pop
  IL_0037:  ldloc.0
  IL_0038:  ldstr      "4"
  IL_003d:  callvirt   instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0)
  IL_0042:  pop
  IL_0043:  ldloc.0
  IL_0044:  ldstr      "5"
  IL_0049:  callvirt   instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0)
  IL_004e:  pop
  IL_004f:  nop
  IL_0050:  ldloc.0
  IL_0051:  callvirt   instance valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<!0> class [System.Core]System.Collections.Generic.HashSet`1<string>::GetEnumerator()
  IL_0056:  stloc.1
  .try
  {
    IL_0057:  br.s       IL_006a
    IL_0059:  ldloca.s   V_1
    IL_005b:  call       instance !0 valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<string>::get_Current()
    IL_0060:  stloc.2
    IL_0061:  nop
    IL_0062:  ldloc.2
    IL_0063:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_0068:  nop
    IL_0069:  nop
    IL_006a:  ldloca.s   V_1
    IL_006c:  call       instance bool valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<string>::MoveNext()
    IL_0071:  brtrue.s   IL_0059
    IL_0073:  leave.s    IL_0084
  }  // end .try
  finally
  {
    IL_0075:  ldloca.s   V_1
    IL_0077:  constrained. valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<string>
    IL_007d:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0082:  nop
    IL_0083:  endfinally
  }  // end handler
  IL_0084:  call       int32 [mscorlib]System.Console::Read()
  IL_0089:  pop
  IL_008a:  ret
} // end of method Program::Main

 

好了,总结一些吧,如果不涉及到修改集合元素,一般情况下使用Foreach比较好,在我测试的代码中,数组和String数据类型不会生成try-finally代码块,里面有一些出入而已,但是这并不影响Foreach的使用。所以我们在具体的编码过程中,尽量多的使用Foreach吧,没关系。

 

]]>