自研工具框架本工具研发的目的就是在产品需求未出之前，但是仍然有需要的场景下快速实现相关工具的可视化研发1.GUI采用python的亲儿子tkinter来实现，简单快捷。程序主文件为**App.py** 2.使用本框架可以很快速的开发属于自己业务的GUI界面,只需要继承BaseWin即可。 3.本项目git地址:https://github.com/yxx1912008/python-gui.git 1.程序主界面2.程序关于界面3.配置加解密4.ak对应权限查询5.查询token权限界面基础框架完整演示动画1.项目技术栈1.httpx用于进行网络请求 2.PyYAML用于读取yaml文件 3.pyinstaller用于编译exe文件在windows系统执行 4.GUI使用python自带的tkinter框架实现 2.项目运行#建议使用python3.7或者以上版本 pip install -r requirements.txt3.编译成windows支持的exe文件pyinstaller有两种编译模式,一种支持命令行同步显示,一种直接显示，如果不了解，那就直接用第一种模式[编译不带命令行]#1.编译不带命令行 pyinstaller -F -i favicon.ico App.py # 2.编译带命令行 pyinstaller -F -i -w favicon.ico App.py4.内网离线使用因为我们公司的特殊性，很多时候需要在内网开发，因此可以通过下面的方式把相关依赖导出,内网使用。#1.安装组件导出引入的依赖 pip install pipreqs #2.导出安装包到指定目录 pip download -d ./lib -r requirements.txt -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com #3.内网离线导入 pip install --no-index --find-links=lib/ -r requirements.txt #--find-links就是存放安装文件的目录 #-r是指按照requirements.txt这个文件去安装文件目录中找需要的安装包5.python导出依赖清单#requirements.txt用来记录项目所有的依赖包和版本号。 pip freeze >requirements.txt
盯盘助手正式发布1.成功驱动小米11 ultra那块126*294的屏幕。2.可以使用背屏进行股票盯盘,省电又快捷。3.软件关于页面使用webview直接渲染，可以直达我的博客，后续版本将改成股票资讯页面4.平常太忙了,没时间优化界面美观度,这个空了再优化吧。背屏真实演示效果点击查看演示视频当前最新版本:V1.0.0下载地址: https://www.pgyer.com/JA7O使用方法:先去设置股票,输入股票信息即可搜索到你需要的股票中途可能需要使用邮箱登录，使用邮箱登录即可点击小窗显示，点击投屏到小窗，因为系统需要初始化,所以要等待十秒反过来手机,双击小窗，可以看到股票信息各位老铁帮忙多点点广告，开发不易。你的支持是我前进的动力！我的博客: https://blog.huochuankeji.com
在公司开发中,遇到一个很有意思的需求。我们需要使用一个jar包，启动jar包后读取包内jar资源，释放出来并启动其他服务。期间涉及到jar包内读取资源,并且释放出来，很简单。1.将需要捆绑的包放入程序目录新建一个与src目录同级目录将捆绑的包和启动脚本放入工程目录启动脚本这里只是一个demo,以自己的项目为准#!/bin/sh source /etc/profile active=$1 jar_path=$2 echo "start other app" nohup java -jar -Dspring.profiles.active=${active} -Dfile.encoding=utf-8 ${jar_path} >otherApp.log 2>&1 & echo "app start is ok"2.pom文件增加配置增加配置是用来打包的时候把第三方jar打包进去 <build> <resources> <resource> <directory>此处填写 第一个步骤创建的目录名称</directory> <filtering>false</filtering> <includes> <include>*.jar</include> <include>*.sh</include> <include>*.bat</include> </includes> </resource> </resources> </build>3.添加示例代码该代码支持linux和windows启动jar包后再启动其他的jar服务,主要就是使用了hutool现成的工具类，很简单package cn.com.xxx.xxx.xxx.config; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.RuntimeUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.system.SystemUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.File; import java.net.URL; import java.util.List; /** * 启动后启动其他捆绑服务 * * @author yuaxx * @Date 2022/2/27 0027 9:13 */ @Component @Slf4j public class SatrtOtherApp { /** * windows启动脚本 */ private static final String WIN_BAT = "cmd /c start \"{}\" cmd /k \"java -jar -Dfile.encoding=utf-8 -Dspring.profiles.active={} {} \""; /** * linux启动脚本 */ private static final String LINUX_SHELL_START = "start-other.sh"; /** * 描述:是否开启多jar启动 * 时间:2022/2/27 0027 9:13 */ @Value("${hyit.openOther:true}") private Boolean openOtherServer; /** * 当前激活的环境 * 时间:2022/2/27 0027 9:13 */ @Value("${spring.profiles.active:dev}") private String active; /** * 需要绑定的其他服务的名称 * 时间:2022/2/27 0027 9:13 */ @Value("${hyit.otherName:fin-mp-auth-exec.jar}") private String otherName; @PostConstruct public void init() { if (!openOtherServer) { log.error("其他服务不允许被启动"); return; } ThreadUtil.execute(() -> { log.info("开始启动{}服务,激活环境:{}", otherName, active); List<URL> jarFiles = ResourceUtil.getResources(otherName); List<URL> shellFiles = ResourceUtil.getResources(LINUX_SHELL_START); if (jarFiles.isEmpty() || shellFiles.isEmpty()) { log.error("当前目录未找到需要启动的服务包"); return; } String rootPath = SystemUtil.getUserInfo().getCurrentDir(); String newPath = rootPath + "/" + otherName; log.info("当前目录为 :{},jar部署路径为:{}", rootPath, newPath); FileUtil.writeFromStream(ResourceUtil.getStream(otherName), new File(newPath)); if (SystemUtil.getOsInfo().isWindows()) { run(newPath); } else { String newShellPath = rootPath + "/" + LINUX_SHELL_START; run(newShellPath, newPath); } }); } private void run(String newPath) { String cmd = StrUtil.format(WIN_BAT, otherName, active, newPath); log.info("实际执行启动脚本为:{}", cmd); RuntimeUtil.exec(cmd); } private void run(String newShellPath, String newPath) { FileUtil.writeFromStream(ResourceUtil.getStream(LINUX_SHELL_START), new File(newShellPath)); String auth = "chmod 777 " + newShellPath; RuntimeUtil.exec(auth); RuntimeUtil.exec(newShellPath, active, newPath); log.info("实际执行启动脚本路径为:{}", newShellPath); } }
存储过程和函数简单的说，存储过程就是一条或者多条SQL语句的集合。可以视为批文件，但是其作用不仅仅局限于批处理。本文主要介绍如何创建存储过程和存储函数，以及如何调用、查看、修改、删除存储过程和存储函数等。创建存储过程和函数 存储程序分为存储过程和存储函数。Mysql创建存储过程和存储函数的语句分别为CREATE PROCEDURE和CREATE FUNCTION。使用CALL语句来调用存储过程，只能用输出变量返回值。存储函数可以从语句外调用（通过引用函数名），也能返回标量值。存储过程也可以调用其他存储过程。废话少说，如下步入正文。创建存储过程 创建存储过程的基本语法格式为：CREATE PROCEDURE sp_name([proc_parameter]) [characteristics] routine_body 其中CREATE PROCEDURE为创建存储过程的关键字，sp_name为存储过程的名称，pro_parameter为指定存储过程的参数列表，其中参数列表如下：[IN|OUT|INOUT] param_name type 其中，IN表述输入参数，OUT表示输出参数，INOUT表示即可输入也可输出；param_name表述参数名称；type表示参数类型，该类型可以是MySQL数据库中的任意类型。characteristics指定存储过程的特性，有以下取值：LANGUAGE SQL:说明routine_body部分是由SQL语句组成的，当前系统支持的语言为SQL，SQL是LANGUAGE特性的唯一值。[NOT] DETERMINISTIC:指明存储过程执行的结果是否确定。DETERMINISTIC表示结果是确定的，每次执行存储过程时，相同的输入会得到相同的输出，NOT DETERMINISTIC表示结果是不确定的，相同的输入可能得到不同的输出，如果没有指定任意一个值，默认为NOT DETERMINISTIC。[CONTAINS SQL|NO SQL|READS SQL DATA|MODIFIES SQL DATA]：指明子程序使用SQL语句的限制。CONTAINS SQL表明子程序包含SQL语句，但不包含读写数据语句；NO SQL表明子程序不包含SQL语句；READS SQL DATA说明子程序包含读数据的语句；MODIFIES SQL DATA表名子程序包含写数据的语句。默认情况下，系统会指定为CONTAINS SQL。SQL SECURITY[DEFINER|INVOKER]：指明谁有权限来执行。DEFINER表示只有定义着才能执行。INVOKER表示用友权限的调用者可以执行。默认情况下，系统指定为DEFINER。COMMENT 'string'：注释信息，用来描述存储过程或函数。routine_body是SQL代码的内容，可以用BEGIN...END来表示SQL代码的开始和结束。 我们从最简单的存储过程开始说起，如下是不包含任何参数的存储过程，代码为：CREATE PROCEDURE Proc() BEGIN SELECT * FROM tb_score; END; 我们定义了一个名称为Proc的存储过程，该过程是用来查询tb_score（该表接上篇博客，已存在）数据库表中的所有数据。 第一张图是通过执行sql语句查询到tb_score表中的数据。第二张图是创建存储过程，其中第一句DELIMITER //是将MySQL的结束符设置为//，因为MySQL默认的结束符为分号，为了避免与存储过程中SQL语句结束符相冲突，需要DELIMITER改变存储过程的结束符，并以“END //”结束存储过程。第三张图是调用存储过程，在调用存储过程之前先将MySQL结束符恢复为默认的分号（DELIMITER ;）然后通过CALL Proc()调用。 接下来我们讲解MySQL存储过程中的参数IN、OUT、INOUT，IN作为输入，将输入作为参数传输到存储过程的执行当中去；OUT作为输出，将存储过程的输出通过参数传出来，而INOUT参数可以同时作为输入和输出。 还是通过存储过程查询tb_score表，不过这次我们要查询课程号为1（cID=1）的所有学生的成绩，存储过程定义为：CREATE PROCEDURE Proc_cID(IN classID INT) BEGIN SELECT * FROM tb_score WHERE cID=classID; END; 如我们需要查询课程号为1的学生的人数和平均成绩，则存储过程定义如下：CREATE PROCEDURE Proc_AVG(IN classID INT,OUT total INT,OUT a_s FLOAT) BEGIN SELECT COUNT(*),AVG(grade) INTO total,a_s FROM tb_score WHERE cID=classID; END;创建存储函数 创建存储函数需要使用CREATE FUNCTION语句，基本语法格式为：CREATE FUNCTION func_name([func_parameter]) returns type [characteristic] routine_body CREATE FUNCTION为用来创建存储函数的关键字，func_name表示存储函数的名称，func_parameter为存储过程的参数列表如下：[IN|OUT|INOUT] param_name type 其参数含义同存储过程（PROCEDURE）相同，其默认为IN参数。 RETURNS type语句表示函数返回数据的类型，characteristic指定存储函数的特性，取值与创建存储过程时相同。 查询某个学生某门课程的成绩函数代码为：CREATE FUNCTION Query_score(classID INT,studentID INT) RETURNS INT RETURN (SELECT grade FROM tb_score WHERE cID=classID AND sID=studentID); 通过SELECT Query_score(1,1)查询1号学生1号课程的成绩。 读者可能会发现存储过程的查询结果可能为多个值，而存储函数的查询结果是某一类型的单值。而且存储过程在调用时用CALL而存储函数是SELECT。那么存储过程和函数具体的区别又是什么呢？存储过程的功能更加复杂，而函数的功能针对性更强；存储过程可以返回参数（通过OUT|INOUT），而函数只能返回单一值或者表对象；存储过程作为一个独立的部分来执行，而函数可以作为查询语句的一部分来调用，由于函数可以返回一个表对象，因此它可以在查询语句中位于FROM关键字之后；存储过程是通过关键字CALL来调用，作为一个独立的执行部分。而存储函数则可作为SELECT语句的一部分调用，嵌入到SQL语句中；当存储过程和函数被执行的时候，SQLManager会到procedure cache中去取相应的查询语句，如果在procedure cache里没有相应的查询语句，SQLManager就会对存储过程和函数进行编译。变量的使用 变量可以在子程序中声明并使用，作用范围是在BEGIN...END程序中，如下将主要介绍如何定义变量和为变量赋值。定义变量。语法格式为：DECLARE var_name[,var_name]...data_type[DEFAULT value]; var_name为局部变量名称，DEFAULT value给变量提供一个默认值。值除了可以被声明为一个常数之外，还可以被指定为一个表达式。如果缺少DEFAULT子句，初始值为NULL。为变量赋值。MySQL中使用SET语句为变量赋值，语法格式为：SET var_name=expr[,var_name=expr]...流程控制的使用 流程控制与用来根据条件控制语句的执行。MySQL中的用来构造控制流程的语句有：IF语句、CASE语句、LOOP语句、LEAVE语句、ITERATE语句、REPEAT语句和WHILE语句。各语句介绍如下：IF语句。包含多个条件判断，根据判断的结果为TRUE或FALSE执行相应的语句，语法格式为 IF expr_condition THEN statement_list ESLEIF expr_condition THEN statement_list ESLE statement_list END IF 如下我们定义一个学生成绩等级评定函数，将学生成绩以参数的形式传输函数，输出学生成绩等级A(90~100)、B(75~90)、C(60~75)、D(60以下)。代码编写中需要注意，IF中如果有多个限制条件，限制条件间用AND连接，DECLARE的变量声明必须在BEGIN内，以及字符串之间拼接用CONCAT。该功能代码如下图所示：CASE语句。另外一个进行条件判断的语句，该语句有2种语句格式。第一种格式为：CASE case_expr WHEN value THEN statement_list [WHEN value THEN statement_list] [ELSE statement_list] END CASE 其中case_expr参数表示条件判断的表达式，决定哪个WHEN子句会被执行，value表示表达式可能的值，如果case_expr等于某个value，则执行相应value后的statement_list。第二种格式为：CASE WHEN expr_condition THEN statement_list [WHEN expr_condition THEN statement_list] [ELSE statement_list] END CASE 其中expr_condition参数表示条件判断语句，该格式下，WHEN语句将被逐个执行，直到某个expr_condition表达式为真，则这行对应THEN关键字后面的statement_list语句。如果没有匹配，ELSE子句里的语句被执行。LOOP语句。循环语句用来重复执行某些语句，与IF和CASE相比，LOOP只是创建一个循环操作的过程，并不进行条件判断。LOOP内的语句一直重复执行直到循环被退出。跳出循环过程使用LEAVE子句，LOOP语句基本格式为：[label] LOOP statement_list END LOOP[label] label表示LOOP语句的标注名称，该参数可以省略，statement_list表示需要执行的语句。LEAVE语句。从LOOP语句的例子中可知LEAVE语句用来退出任何被标注的流程控制构造，LEAVE语句基本格式为：LEAVE labelITERATE语句。将执行顺序转到语句段开头处，语句基本格式为：ITERATE lable ITERATE只可以出现在LOOP、REPEAT和WHILE语句内。ITERATE的意思为再次循环，label参数表示循环的标志。ITERATE语句必须跟在循环标志前面。例子中p1=0，如果p1的值小于10时，重复执行p1加1操作；p1大于等于10并且小于20时，打印消息'p1 is between 10 and 20';p1大于20时，退出循环。REPEAT语句。创建一个带条件判断的循环过程，每次语句执行完毕后，会对条件表达式进行判断，表达式为真循环结束，否则重复执行循环中的语句。语句基本格式：[label]:REPEAT statement_list UNTIL expr_condition END REPEAT [label]WHILE语句。WHILE语句创建一个带条件判断的循环过程，与REPEAT不同，WHILE在执行语句时先对指定的表达式进行判断，为真则执行循环内的语句，否则退出循环。语句基本格式：[label] WHILE expr_condition DO statement_list END WHILE [label]查看存储过程和函数 MySQL中，用户可以使用SHOW STATUS语句或SHOW CREATE语句来查看存储过程和函数，也可以直接从系统的information_schema数据库中查询。本节将通过实例来介绍这3种方法。SHOW STATUS语句查看存储过程和函数的状态，其基本语法为：SHOW{PROCEDURE|FUNCTION} STATUS [LIKE 'pattern'] 这个语句返回子程序的特征，如数据库、名字、类型、创建者及创建和修改日期。如果没有指定样式，根据使用的语句，所有存储程序或存储函数的信息都被列出。PROCEDURE和FUNCTION分别表示查看存储过程和函数，LIKE语句表示匹配存储过程或函数的名称。SHOW CREATE查看存储过程和函数语句格式为：SHOW CREATE {PROCEDURE|FUNCTION} sp_name 它返回一个可以来重新创建已命名子程序的确切字符串。PROCEDURE和FUNCTION分别表示查看存储过程和函数，同样也可是使用LIKE匹配。从information_schema.Routines表中查看存储过程和函数的信息。MySQL中存储过程和函数的信息存储在information_schema数据库的Routines表中。通过查询该表的记录查询存储过程和函数的信息。基本语法格式为：SELECT * FROM information_schema.Routines WHERE ROUTINE_NAME='sp_name' 其中ROUTINE_NAME字段存储的是存储过程和函数的名称，sp_name参数表述存储过程或函数的名称。修改存储过程和函数 使用ALTER语句可以修改存储过程或函数的特性，本节将介绍如何通过ALTER语句修改存储过程和函数。语法格式为：ALTER {PROCEDURE|FUNCTION} sp_name [characteristic ...] 其中，sp_name参数表示存储过程或函数的名称，characteristic参数指定存储函数的特性，可能的取值有：CONTAINS SQL表示子程序包含SQL语句，但是不包含读或写数据的语句；NO SQL表示子程序中不包含SQL语句；READES SQL DATA表示子程序中包含读数据的语句；MODIFIES SQL DATA表示子程序中包含写数据的语句；SQL SECURITY{DEFINER|INVOKER}指明谁有权限来执行；DEFINER表示只有定义着自己才能够执行；INVOKER表示调用者可以执行；COMMENT 'string'表示注释信息。 修改存储过程使用ALTER PROCEDURE语句，修改存储函数使用ALTER FUNCTION语句。 删除存储过程 删除存储过程和函数可以使用DROP语句，语法格式为：DROP {PROCEDURE|FUNCTION} [IF EXISTS] sp_name 这个语句被用来移除一个存储过程或函数，sp_name为要移除的存储过程或函数的名称。IF EXISTS子句是一个MySQL的扩展，如果存储过程或函数不存在，以防发生错误，产生一个用SHOW WARNINGS查看的警告。 最后再说几点值得大家注意的吧：目前MySQL不支持对已存在的存储过程代码进行修改，如果必须修改，则先使用DROP语句删除该存储过程，再重新创建新的存储过程；存储过程中包含用户定义的SQL语句集合，也可是使用CALL语句调用存储过程，但不能使用DROP删除其他存储过程；在定义存储过程参数列表时，应注意把参数名与数据库表中的字段名区别开，否则会报错。如果存储过程中需要传入中文参数，这时需要在定义存储过程的时候，在参数后加上character set gbk，不然调用存储过程使用中文参数会出错。如CREATE PROCEDURE userInfo(IN u_name VARCHAR(50) character set gbk, OUT u_age INT)。
PHP初体验 提笔写初体验总不知道从何说起，直接聊PHP中的函数、PHP网络技术、数据库操作、PHP模板等感觉又不是初体验。最后还是决定从PHP的面向对象、PHP的魔术方法、PHP的反射、PHP中的异常和错误这4个方面简单介绍一下。PHP面向对象的“形”与“本” 这里我们就不给面向对象下定义了，不过我们还是要说一下类和对象的。类是对象的抽象组织，对象是类的具体存在。接下来我们就拿PHP为例，来探讨一下对象的“形”与“本”的问题。 在PHP中，每个类的定义都是以关键字class开头，后面是类名和一对花括号，括号中包含类成员和方法的定义。如下是一个简单类的定义： class Person {public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function sayHello(){ echo "My name is $this->name and I am $this->age years old. I am a $this->sex.\r\n"; echo self::$information; echo " "; }}$person = new Person();$person->name = 'Lisi';$person->sayHello();echo serialize($person);//输出结果如下：//My name is Lisi and I am 23 years old. I am a male. I come from the earth//O:6:"Person":3:{s:4:"name";s:4:"Lisi";s:11:"Personage";i:23;s:11:"Personsex";s:4:"male";}Person类 当把类对象序列化输出时，可以看出类对象在存储时类似于数组的形式。那么类对象与数组从本质上又有什么区别与联系呢？接下来从对象“本”来分析一下PHP对对象的底层实现。如下是PHP源码中对变量的定义：变量定义typedef struct _zval_struct zeal; //存储变量的类型struct _zval_struct { /* Variable information */ zvalue_value value; /* 存储变量的值 */ zend_uint refcount; /* 变量引用数 */ zend_uchar type; /* 变量类型 */ zend_uchar is_ref; /* 变量是否被引用 */};typedef union _zvalue_value { long lval; /* 长整型 */ double dval; /* double型 */ struct { char *val; int len; } str; /* String型 */ HashTable *ht; /* array型 */ zend_object_value obj; /* 对象型 */} zvalue_value;对象底层实现typedef struct _zend_object { zend_class_entry *ce; //类入口，存储该对象的类结构，相当于类指针 HashTable *properties; //对象属性组成的HashTable HashTable *guards; /* 阻止递归调用 */} zend_object;typedef struct _zend_guard { zend_bool in_get; zend_bool in_set; zend_bool in_unset; zend_bool in_isset; zend_bool dummy; /* sizeof(zend_guard) must not be equal to sizeof(void*) */} zend_guard;//由于zend_class_entry源码较多且复杂，因此此处省略。PHP源码中变量、对象的定义 通过上面的代码我们也对PHP如何存储对象有了初步的认识，那对象与数组又是什么关系呢？通过PHP的源码可得，zvalue_object结构中有一个HashTable的类型，它就是存储数组的。PHP对象的结构体中不仅有HashTable（用于存储类对象特有的属性），而且还有对象所属类的入口等，如下是PHP对象的组成： 其中PHP源码中zend_class_entry结构体中存储的就是类的指针，该结构体中包含类常量、静态属性、标准方法、魔术方法、自定义方法等。而属性数组存储的是类对象的属性。接下来我们还是以如上的Person类为例，谈一谈对象与数组：class Person {public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; }}$person = new Person();$person_arr = array("name"=>"zhangsan", "age"=>"23", "sex"=>"male");echo serialize($person); echo ""; //输出对象序列化结果并换行符echo serialize($person_arr); echo ""; //输出数组序列化结果并换行$object = (object)$person_arr; //将数组转化为对象echo serialize($object); //输出数组序转化为对象的序列化结果/*输出结果如下：O:6:"Person":3:{s:4:"name";s:8:"zhangsan";s:11:"Personage";i:23;s:11:"Personsex";s:4:"male";}a:3:{s:4:"name";s:8:"zhangsan";s:3:"age";s:2:"23";s:3:"sex";s:4:"male";}O:8:"stdClass":3:{s:4:"name";s:8:"zhangsan";s:3:"age";s:2:"23";s:3:"sex";s:4:"male";}结果解释如下：O代表的是对象，a代表的是数组，O后的数字（6和8）代表该对象所属类名的长度，紧挨在数字后面的就是类名类名后面的数字为类对象属性的个数，如上有3个，分别为name，age，sex大括号中的为对象或数组的属性名和属性值的键值对，其中s代表字符串最后需要说明的是当把数组转化为对象时，因为没有与数组转换成对象对应的类，因此PHP中一个称为"孤儿"的类stdClass类就会收留这个对象*/PHP中的魔术方法 可能细心的你在对象组成的那张图中看到了魔术方法，但是上一节中并没有对zend_class_entry中的内容做任何介绍。那么什么又是魔术方法呢？魔术方法就是以两个下划线“__”开头、具有一些特殊作用的方法。其实如上的Person类中，我们不经意间就使用了魔术方法__construct()，这个魔术方法就是构造方法。用于在创建类对象时对属性进行赋值。接下来我们将介绍几个常见的魔术方法让大家对魔术方法有个初步了解。__sleep()：该魔术方法是在执行serialize()方法完成前被调用，该方法可以用来关闭对象可能具有的任何数据库连接、加密需要序列化对象属性的值、指定仅序列化对象的某些属性等等。__wakeup()：该魔术方法是在执行unserialize()方法完成前被调用，该方法可以用来建立对象可能的数据库连接、解密序列化对象属性的值等等。class Person {public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __sleep() { // TODO: Implement __sleep() method return array("name","age"); //该方法指定仅序列化对象的name和age属性 } public function __wakeup() { // TODO: Implement __wakeup() method. var_dump($this->name); //打印输出对象的name属性的值 var_dump($this->sex); //打印输出对象的sex属性的值，由于sex没有被序列化，因此输出null }}$serResult = serialize(new Person()); //序列化一个Person类对象，该方法完成前先调用Person类的__sleep()echo $serResult; echo ""; //输出对象序列化结果并换行符$unSerResult = unserialize($serResult); //将序列化结果反序列化，该方法完成前调用Person类的__wakeup方法echo $unSerResult->name; echo ""; //由反序列化得到的Person类对象调用对象的name属性echo var_dump($unSerResult); echo ""; //输出反序列化得到的Person类对象/*输出结果如下：O:6:"Person":2:{s:4:"name";s:8:"zhangsan";s:11:"Personage";i:23;}string(8) "zhangsan" NULL zhangsanobject(Person)#1 (3) { ["name"]=> string(8) "zhangsan" ["age":"Person":private]=> int(23) ["sex":"Person":private]=> NULL }*/__sleep()和__wakeup()方法__construct()：类的构造方法，类对象在创建时会首先调用该方法，因此该方法中可以做一些类对象创建前的初始化工作。__destruct()：类的析构方法，当类对象在销毁时会调用该方法。1 class Person { 2 public $name; 3 private $age; 4 private $sex; 5 public static $information = "I come from the earth"; 6 public function __construct($name="zhangsan", $age=23, $sex="male") { 7 $this->sex = $sex; 8 $this->age = $age; 9 $this->name = $name;10 }11 public function __destruct() {12 // TODO: Implement __destruct() method.13 echo "The object will be destructed";14 }15 }16 17 $person = new Person();18 unset($person); //unset()方法释放$person对象19 echo $person->name; //该行试图打印$person对象的name属性值，由于$person对象已经被销毁了，因此该行会报错20 /*21 输出结果如下：22 The object will be destructed23 Notice: Undefined variable: person in /Users/zhangshilei/PhpstormProjects/untitled/demo/Person.php on line 1924 25 Notice: Trying to get property of non-object in /Users/zhangshilei/PhpstormProjects/untitled/demo/Person.php on line 1926 */PHP构造函数和析构函数__get()和__set()方法，这两个方法主要是实现了可以在类的外部通过对象直接访问类的私有属性，还可以增加类中没有定义的属性，如给$person对象增加marriage属性，只需$person->marriage="married"即可。class Person {public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __get($field) { // TODO: Implement __get() method. echo "the get method is executed "; return $this->$field; } public function __set($key, $value) { // TODO: Implement __set() method. echo "The set method is executed "; $this->$key = $value; }}$person = new Person();$person->name="Lisi";echo $person->name; echo "";$person->age = 25; //age作为$person的私有属性，如果没有__set()方法此句会报错echo $person->age; //没有__get()方法，此句会报错，如果__get()方法中没有return语句，该句没有返回值/*输出结果如下：LisiThe set method is executedthe get method is executed25结果解释如下：不知道大家注意到了没有，设置公有属性name的值没有调用__set()方法，读取公有属性name的值没有调用__get()方法只有在设置和读取私有属性age的值才调用了__get()和__set()方法*/__get()和__set()方法__call()和__callStatic()：当类对象调用的方法不存在而且类中定义了__call()方法时，则会自动调用类的__call()，当类调用静态方法不存在而且类中定义了方法__callStatic()时，则会自动调用__callStatic()方法。class Person {public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __call($name, $arguments) { // TODO: Implement __call() method. echo "被调用方法的名称为：$name "; echo "传入该方法的参数为：", var_dump($arguments)," "; } public static function __callStatic($name, $arguments) { // TODO: Implement __callStatic() method. echo "被调用静态方法的名称为：$name "; echo "传入该方法的参数为：", var_dump($arguments)," "; }}$person = new Person();$person->shopping("clothes","fruit","snack");Person::travel("Beijing","Pair","London","Prussia");/*输出结果为：被调用方法的名称为：shopping传入该方法的参数为：array(3) { [0]=> string(7) "clothes" [1]=> string(5) "fruit" [2]=> string(5) "snack" }被调用静态方法的名称为：travel传入该方法的参数为：array(4) { [0]=> string(7) "Beijing" [1]=> string(4) "Pair" [2]=> string(6) "London" [3]=> string(7) "Prussia" }*/__call()和__callStatic()方法__toString()方法，该方法主要用于格式化打印一个对象，该方法的设计原型来源于Java。只有类中实现了__toString()方法才可以通过echo直接打印该对象。class Person {public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __toString() { // TODO: Implement __toString() method. return "My name is $this->name. I am $this->age years old and I am a $this->sex."; }}$person = new Person();echo $person;/*输出结果如下：My name is zhangsan.I am 23 years old and I am a male.*/__toString() 关于PHP的魔术方法我们就简单介绍到这里，由上我们可以看出从构造方法上，PHP相比于还稍有欠缺，但PHP中有__set()和__get()，使得动态增加对象的属性字段变得更加方便，而对于Java来说要实现类似的效果，就不得不借助反射API或直接修改编译后字节码的方式实现了。Java中有反射机制，那么PHP中呢？接下来让我们来看一看PHP中的反射实现。PHP中的反射机制 反射，直观的理解就是根据到达地找到出发地和来源。比如给出一个对象就可以找到对象所属的类、拥有哪些方法。反射可以在PHP运行状态中，扩展分析PHP程序，导出或提取出关于类、方法、属性、参数等的详细信息，这种动态获取信息以及动态调用对象方法的功能称为反射API。class Person {public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __sleep() { // TODO: Implement __sleep() method return array("name","age"); //该方法指定仅序列化对象的name和age属性 } public function __wakeup() { // TODO: Implement __wakeup() method. var_dump($this->name); //打印输出对象的name属性的值 var_dump($this->sex); //打印输出对象的sex属性的值，由于sex没有被序列化，因此输出null } public function __destruct() { // TODO: Implement __destruct() method. echo "The object will be destructed"; } public function __get($field) { // TODO: Implement __get() method. echo "the get method is executed "; return $this->$field; } public function __set($key, $value) { // TODO: Implement __set() method. echo "The set method is executed "; $this->$key = $value; } public function __call($name, $arguments) { // TODO: Implement __call() method. echo "被调用方法的名称为：$name "; echo "传入该方法的参数为：", var_dump($arguments)," "; } public static function __callStatic($name, $arguments) { // TODO: Implement __callStatic() method. echo "被调用方法的名称为：$name "; echo "传入该方法的参数为：", var_dump($arguments)," "; } public function __toString() { // TODO: Implement __toString() method. return "My name is $this->name. I am $this->age years old and I am a $this->sex."; } public function sayHello($other){ echo "My name is $this->name, nice to meet you $other"; }}$person = new Person();$reflect = new ReflectionObject($person);$props = $reflect->getProperties(); //获取对象的属性列表（所有属性）foreach ($props as $prop){ //打印类中属性列表echo $prop->getName(),"\r";}echo "";$methods = $reflect->getMethods(); //获取类的方法列表foreach ($methods as $method){echo $method->getName(),"\r";}echo "";//返回对象属性的关联数组var_dump(get_object_vars($person)); echo "";//返回类的公有属性的关联数组var_dump(get_class_vars(get_class($person))); echo "";//获取类的方法名组成的数组var_dump(get_class_methods(get_class($person)));/*输出结果如下：name age sex information__construct __sleep __wakeup __destruct __get __set __call __callStatic __toString sayHelloarray(1) { ["name"]=> string(8) "zhangsan" }array(2) { ["name"]=> NULL ["information"]=> string(21) "I come from the earth" }array(10) { [0]=> string(11) "__construct" [1]=> string(7) "__sleep" [2]=> string(8) "__wakeup" [3]=> string(10) "__destruct" [4]=> string(5) "__get" [5]=> string(5) "__set" [6]=> string(6) "__call" [7]=> string(12) "__callStatic" [8]=> string(10) "__toString" [9]=> string(8) "sayHello" }*/PHP中的反射应用 如上代码中介绍的是通过对象获取类的方法和属性字段，而反射不仅仅可以用于类和对象，还可以用于函数、扩展模块、异常等。既然反射可以探知类的内部结构，那么就可以利用反射机制实现插件的功能，也可以利用反射机制实现动态代理。接下来举个简单的例子看看如何通过反射机制实现动态代理。class Person {public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function sayHello($other){ echo "My name is $this->name, nice to meet you ".implode("",$other)." "; }}class Dynamicproxy{private $target; public function __construct($className) { //为动态代理传入类名称，则自动生成类对象 $this->target = new $className(); } public function __call($name, $arguments) { // TODO: Implement __call() method. $reflect = new ReflectionObject($this->target); if($method = $reflect->getMethod($name)){ //获取名称为$name的类方法 if($method->isPublic() && !$method->isAbstract()){ echo "Before the method ".$method->getName()." we should do something "; $method->invoke($this->target,$arguments); echo "After the method ".$method->getName()." we should do something "; } } }}$objProxy = new Dynamicproxy("Person");$objProxy->sayHello("Janny");/*输出结果如下：Before the method sayHello we should do somethingMy name is zhangsan, nice to meet you JannyAfter the method sayHello we should do something*/PHP反射实现动态代理 如上的代码中真正实现sayHello()动作的是Person类中的sayHello()方法，而Dynamicproxy仅是一个代理类，其中并没有定义sayHello()方法，而是通过__call()方法动态调用类Person的sayHello()方法。在DynamicProxy类中可以做sayHello()方法的前后拦截，并且可以动态的改变类中的方法和属性。很多时候，善用反射可以保持代码的优雅和简洁，但反射也会破坏类的封装性，因为反射可以使本不应该暴露的方法或属性被强制暴露了出来。PHP中的异常和错误 在语言级别通常有许多错误处理模式，但这些模式往往建立在约定俗称的基础上，也就是错误都是可预知的。不同的语言对异常和错误的定义也是不一样的，在PHP中，遇到任何自身错误都会触发一个错误，而不是抛出异常。也就是说PHP一旦遇到非正常代码，通常都会触发错误，而不是抛出异常。因此如果想使用异常处理不可预料的问题，是办不到的。比如，想在文件不存在或数据库无法建立连接时触发异常，是不可行的。PHP会把这些作为错误抛出，而不是作为异常捕获。还是回到PHP的错误处理上，PHP中的错误级别大致分为以下几类：最高级别的错误是语法解析错误 prase error。该错误属于语法检查阶段错误，这会导致PHP代码无法通过语法检查。次之的错误是fetal error。该类错误会导致PHP流程终止，其后的代码无法继续执行。warning是警告级别的错误，在语法中出现很不恰当的情况才会报此错误，如参数不匹配、除数为0等。这种错误会导致不可预期的结果。notice是通知级别的错误，这种错误是在如变量使用前未定义、数组索引是字符时没有加引号等情况。最低级别的错误是deprecated的错误，表示不推荐，不建议。如在PHP5中使用ereg系列的正则匹配函数就会报此类错误，该错误是由于使用了不推荐、过时的函数或语法造成的，不影响PHP正常流程。function showError(){$date = '2017-06-05'; if(ereg("([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})",$date,$regs)){ //deprecated级别错误 echo $regs[3].$regs[2].$regs[1]; }else{ echo "Invalid date format: $date"; } if($value > 5){ //notice级别的错误 echo "It is amazing, the variable $value is not init",PHP_EOL; } $a = array('o'=>2,4,6,8); echo $a[o]; //notice级别的错误 $sum = array_sum($a,3); //warning级别的错误，传入参数不正确 echo fun(); //fetal error echo "the code is after fetal error"; //该句不执行 //echo "prase error:",$55;}showError();PHP中的Error 接下来我们看一看针对上边介绍的各个级别的错误PHP是如何处理的。PHP中提供了set_error_handler()函数来处理错误，当然该函数也不是可以托管所有种类的错误，如E_ERROR、E_PARSE、E_CORE_ERROR等错误，这些错误会以原始的方式显示。当然也可以通过restore_error_handler()取消接管：function DivisionError($errno, $errmsg, $errfile, $errline){// 获取当前错误时间 $dt = date("Y-m-d H:i:s (T)"); $errortype = array ( E_ERROR => 'Error', E_WARNING => 'Warning', E_PARSE => 'Parsing Error', E_NOTICE => 'Notice', E_CORE_ERROR => 'Core Error', E_CORE_WARNING => 'Core Warning', E_COMPILE_ERROR => 'Compile Error', E_COMPILE_WARNING => 'Compile Warning', E_USER_ERROR => 'User Error', E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice', E_STRICT => 'Runtime Notice', E_RECOVERABLE_ERROR => 'Catchable Fatal Error' ); $user_errors = array(E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE); $err = "错误时间".$dt." "; $err .= "错误等级".$errno ." "; $err .= "错误等级名称" . $errortype[$errno] . " "; $err .= "错误消息" . $errmsg . " "; $err .= "错误发生所在文件" . $errfile . " "; $err .= "错误所在行" . $errline . " "; echo $err;}set_error_handler("DivisionError"); //当抛出错误时，直接交给DivisionError处理function showError(){$date = '2017-06-05'; if(ereg("([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})",$date,$regs)){ //deprecated级别错误 echo $regs[1].$regs[2].$regs[3]; }else{ echo "Invalid date format: $date"; } if($value > 5){ //notice级别的错误 echo "It is amazing, the variable $value is not init",PHP_EOL; } $a = array('o'=>2,4,6,8); echo $a[o]; //notice级别的错误 $sum = array_sum($a,3); echo fun(); //fetal error echo "the code is after fetal error"; //该句不执行 //echo "prase error:",$55;}showError();PHP错误处理 如上这种“曲折迂回”的处理方式也存在问题：必须依靠程序员自己来掌控对异常的处理，对于异常高发区、敏感区，如果处理不好就会出现业务数据不一致的问题，但是优点就是可以获得程序运行的上下文信息，以进行针对性补救。 对于代码中存在的异常，需要认为的进行抛出，接下来我们通过自定义一个异常类来处理抛出的异常，class DivisionException extends Exception{ //自定义异常处理类public function __toString(){ //覆写父类的__toString()，规定输出格式 $errorMessage = '错误发生于文件'.$this->getFile().'第'.$this->getLine().'行 ' .'错误原因为'.$this->getMessage(); return $errorMessage; }}class Calculate {private $num1; private $num2; private $operater; public function __construct($num1, $num2, $operater) { $this->operater = $operater; $this->num1 = $num1; $this->num2 = $num2; } public function getResult() { try { if ($this->operater == "+") { return $this->num1 + $this->num2; } else if ($this->operater == "-") { return $this->num1 - $this->num2; } else if ($this->operater == "*") { return $this->num1 * $this->num2; } else { if($this->num2 == 0){ //如果除数为0则抛出异常 throw new DivisionException("除数不能为0"); } return $this->num1 / $this->num2; } } catch (DivisionException $exception){ echo $exception; } }}$calculate = new Calculate(10,0,"/");echo $calculate->getResult();/**输出结果如下：错误发生于文件/Users/zhangshilei/PhpstormProjects/untitled/demo/Person.php第98行错误原因为除数不能为0*/PHP自定义异常处理类 初体验就为大家介绍到这里吧，以后有机会在深入的去了解PHP函数、PHP与网络、PHP与数据库等等的内容吧。
装饰模式装饰模式：一种常见的模式，动态地给一个对象添加一些额外的职责，就增加功能来看，装饰模式比生成子类更加灵活。装饰模式的通用类图如下： Component抽象构件：Component是一个接口或者是抽象类，就是我们需要装饰的核心对象。在装饰模式中，必然有一个最基本、最核心、最原始的接口或抽象类充当Component构件。ConcreteComponent是Component接口或抽象类的实现。Decrator装饰角色：一般是一个抽象类，实现接口或者抽象方法，其属性必然有一个private变量指向Component抽象构件。ConcreteDecrator具体装饰类。其通用类图源码如下：public abstract class Component {public abstract void doSomething();}public class ConcreteComponent extends Component{@Override public void doSomething() { // TODO Auto-generated method stub System.out.println("this is a ConcreteComponent"); }}public abstract class Decrator extends Component{private Component component = null; public Decrator(Component com){ this.component = com; } @Override public void doSomething(){ this.component.doSomething(); }}public class ConcreteDecrator1 extends Decrator{public ConcreteDecrator1(Component com) { super(com); // TODO Auto-generated constructor stub } public void doSomething() { // TODO Auto-generated method stub this.method1(); super.doSomething(); } private void method1(){ System.out.println("this is a ConcreteDecrator"); }}public class ConcreteDecrator2 extends Decrator{public ConcreteDecrator2(Component com) { super(com); // TODO Auto-generated constructor stub } public void doSomething(){ super.doSomething(); this.method2(); } private void method2(){ System.out.println("this is another ConcreteDecrator"); }}public class Client {/** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Component component = new ConcreteComponent(); component = new ConcreteDecrator1(component); component = new ConcreteDecrator2(component); component.doSomething(); }}View Code装饰模式的应用最近刚刚过了双十一的网上购物狂潮，购买的商品也万里迢迢的飞奔而来，拿到快递那一刻的喜悦简直感觉自己就是世界上最幸福的，然而如果你买的是服装，相信你一定深有体会，为什么自己穿上就没有那种感觉了呢，冥思苦想，应该是商家对服装的装饰比较到位，下面我们就解析一下这种“差距”。首先我们先看一下类图： Clothes类是一个抽象类，是我们在网上看到的各种服装的总称，它有一个展示其参数的方法parameter()，提供给买家参考。Coat类是服装中的具体的一件上衣。Decrator是抽象的装饰类，其中包括一个Clothes对象。Model具体装饰类，让时装模特穿上这个外套。Scarf具体的装饰类，为外套装饰一条围巾。如下是类图的通用源码实现：public abstract class Clothes {//展示服装信息 public abstract void parameter();}public class Coat extends Clothes{@Override public void parameter() { // TODO Auto-generated method stub System.out.println("this is a Coat"); System.out.println("It's size is XL"); System.out.println("It's color is dark blue"); }}public abstract class Decrator extends Clothes{private Clothes clothes = null; public Decrator(Clothes clothes){ this.clothes = clothes; } @Override public void parameter(){ this.clothes.parameter(); }}public class Model extends Decrator{public Model(Clothes clothes) { super(clothes); // TODO Auto-generated constructor stub } public void parameter(){ this.method2(); super.parameter(); } private void method2(){ System.out.println("A clothes model wears the coat"); }}public class Scarf extends Decrator{public Scarf(Clothes clothes) { super(clothes); // TODO Auto-generated constructor stub } public void parameter() { // TODO Auto-generated method stub this.method1(); super.parameter(); } private void method1(){ System.out.println("this is a scarf to decrate the coat"); }}public class Client {/** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Clothes clothes = new Coat(); clothes = new Scarf(clothes); clothes = new Model(clothes); clothes.parameter(); }}View Code可怜了我们这些小白用户，不知道还能这么玩，蓝瘦香菇。装饰模式的优点装饰类和被装饰类可以独立发展，而不会相互耦合。如实例，服装类Clothes不需要知道装饰类Decrator，装饰类是从外部来扩展服装类的，而装饰类也不用知道具体的服装类。装饰模式是一种继承关系的替代方案，装饰类Decrator不管装饰多少层，返回的对象还是Clothes，实现的还是is-a的关系。装饰类可以动态扩展一个实现类的功能。如上述例子中，上衣有多个子类（Jacket、Shirt等），如果开发到中途，需求提出要改动Coat类，那可能对Coat的子类产生影响，而此时可以用装饰模式，通过建立CoatDecrator来修饰Coat类，相当于创建了一个新类，原有程序没有变更，通过扩展满足了需求。装饰模式的缺点多层装饰比较复杂，需要尽量减少装饰类的数量，以便降低系统的复杂度。
Java序列化与反序列化Java提供了两种对象持久化的方式，分别为序列化和外部序列化序列化 在分布式环境下，当进行远程通信时，无论是何种类型的数据，都会以二进制序列的形式在网络上传输。序列化是一种将对象以一连串的字节描述的过程，用于解决在对对象流进行读写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输，或者保存到文件、数据库等系统中，并在需要时把该流读取出来重新构造一个相同的对象。 所有实现序列化的类都必须实现Serializable接口，Serializable接口位于java.lang包中，没有任何实现方法，使用一个输出流（例如FileOutputStream）来构造一个ObjectOutputStream对象，紧接着使用该对象的writeObject(Object obj)方法就可以将obj对象写出，要恢复时可以使用其对应的输入流ObjectInputStream.序列化有以下两个特点：如果一个类能被序列化，那么它的子类也能够被序列化。由于static(静态)代表类的成员，transient（Java关键字，如果用transient声明一个实例变量，当对象存储时，它的值不需要维持）代表对象的临时数据，因此被声明为这两种类型的数据成员是不能够被序列化的。 如下是序列化的代码，首先声明一个Student类继承Serializable接口，由代码中的输出（注释即为输出内容）可得静态变量SCHOOLNAME和SCHOOLID的值发生了变化，难道是静态变量也被序列化了吗？其实不是的，因为在当前的运行程序中，Student类的静态变量的值已经发生了变化，如果真的已经序列化了，那么我们将序列化的那个函数去掉，让程序从SourceFile/Student文件中反序列化，那么得到的SCHOOLNAME应该为HeBei University，SCHOOLID的值为2，然而当我们去掉序列化代码，直接从文件反序列化，输出SCHOOLNAME是Beijing University of Post and Telecommunications，SCHOOLID为1，说明static变量并没有被序列化。public class Student implements Serializable{/** * */ private static final long serialVersionUID = 1L; public String name; public int id; public static String SCHOOLNAME = "Beijing University of Post and Telecommunications"; private static int SCHOOLID = 1; public Student(){ name = "zhangsan"; id = 2018111846; } public Student(String name, int id){ this.name = name; this.id = id; } public static void setSchool(String school_name, int school_id){ SCHOOLID = school_id; SCHOOLNAME = school_name; } public static int getSchoolID(){ return SCHOOLID; }}public class Test {/** * @param args */ public static void main(String[] args) { Student student = new Student(); student.setSchool("Hebei University",2); serialize(student); deserialize(); } /** * 序列化student对象 * @param student */ private static void serialize(Student student){ try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SourceFile/Student")); oos.writeObject(student); oos.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 从文件反序列化student对象 */ private static void deserialize(){ try { FileInputStream fis = new FileInputStream("SourceFile/Student"); Student student = (Student) new ObjectInputStream(fis).readObject(); System.out.println(student.name); //输出zhangsan System.out.println(student.id); //输出2018111846 System.out.println(student.SCHOOLNAME); //输出HeBei University System.out.println(student.getSchoolID()); //输出2 } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}外部序列化 Java语言还提供了另外一种方式来实现对象持久化，即外部序列化。外部序列化与序列化的主要区别在于序列化是内置的API，只需要实现Serializable接口，开发人员不需要编写任何代码就可以实现对象的序列化，而是用外部序列化时，Externalizable接口中的方法必须有开发人员实现。因此与实现Serializable接口的方法相比，使用Externalizable编写程序的难度更大，但是由于控制权交给了开发者，在编程时有更多的灵活性，对需要持久化的那些属性可以进行控制，可能提高程序的性能。如下是外部序列化的代码，当把序列化部分的代码注释到之后，发现薪金的输出还是10000，则可以确定当执行外部序列化时，static静态变量也被序列化了，而且方法中没有序列化id属性，则反序列化后发现id并没有发生变化：public class Teacher implements Externalizable{private String name; private int id; private static int salary = 5000; public Teacher(){ name = "zhangsan"; id = 182566; } public static void setSalary(int s){ salary =s; } public int getID(){ return id; } public void setID(int id){ this.id = id; } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub Date date = (Date)in.readObject(); name = (String)in.readObject(); salary = (Integer)in.readObject(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); System.out.println(sdf.format(date)); } @Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub Date date = new Date(); out.writeObject(date); out.writeObject(name); out.writeObject(salary); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); System.out.println(sdf.format(date)); } public String toString(){ return "教师名称" + name + " 薪金" + salary; }}public class Test {/** * @param args */ public static void main(String[] args) { Teacher teacher = new Teacher(); teacher.setSalary(10000); teacher.setID(111111); System.out.println(teacher.getID()); //输出111111 serialize(teacher); Teacher teacher1 = deserialize(); System.out.println(teacher1.toString()); //输出教师名称zhangsan 薪金10000 System.out.println(teacher1.getID()); //输出182566 } /** * 外部序列化teacher对象 * @param teacher */ private static void serialize(Teacher teacher){ try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SourceFile/Teacher")); oos.writeObject(teacher); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private static Teacher deserialize(){ try { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("SourceFile/Teacher")); return (Teacher)ois.readObject(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; }}Java反序列化与序列化相对的是反序列化，它将流转换为对象，在序列化与反序列化的过程中，serialVersionUID起着重要的作用，每一个类都有一个特定的serialVersionUID，在反序列化的过程中通过serialVersionUID判定类的兼容性，自定义serialVersionUID主要由如下3个优点。提高程序运行效率。如果在类中未显示声明serialVersionUID，那么在序列化时会通过计算得到一个serialVersionUID。通过显示声明serialVersionUID的方式省去了计算的过程，提高了程序效率。提高程序不同平台上的兼容性。由于各个平台计算serialVersionUID的方式可能不同，通过显示的方式可以完全避免该问题。增强程序各个版本的可兼容性。在默认情况下每个类都有唯一的一个serialVersionUID，因此当后期对类进行修改时，类的serialVersionUID值将会发生变化，这将会导致类在修改前对象的文件在修改后无法进行反序列化操作。同样通过显示声明serialVersionUID也会解决该问题。最后想说明，反序列化也是Java创建对象的一种方式，其他的还有new 类名()、通过clone()创建对象、通过反射机制创建对象。
连接数据库操作： string s = "Data Source=服务器名称;Initial Catalog=数据库名称;Integrated Security=True"; //连接数据库所需要的字符串 SqlConnection con = new SqlConnection(); con.ConnectionString = s; con.Open();将数据库信息显示在datagridview中 string selectCommandText = "select * from 治疗信息(表名)"; DataSet ds = new DataSet(); con.ConnectionString = s; sda = new SqlDataAdapter(selectCommandText, con); sda.Fill(ds); dataGridView1.DataSource = ds.Tables[0]; //完成数据绑定，datagridview控件可以显示DataGridView动态更新数据库： string s = "Data Source=A3135;Initial Catalog=TotalNumber;Integrated Security=True"; SqlConnection con = new SqlConnection(); SqlDataAdapter sda = new SqlDataAdapter(); BindingSource bs = new BindingSource(); DataTable dt = new DataTable(); 绑定数据库按钮： con.ConnectionString = s; string selectCommandText = "select * from 数据源"; sda = new SqlDataAdapter(selectCommandText, con); sda.Fill(dt); bs.DataSource = dt; //绑定BindingSource dataGridView1.DataSource = dt; //完成数据绑定，datagridview可以显示数据动态更新数据库按钮： SqlCommandBuilder cb = new SqlCommandBuilder(sda); sda.Update((DataTable)bs.DataSource); 获取当前行的信息： RowNumber = SearchAnswer.CurrentCell.RowIndex; //SearchAnswer为DataGridview名 SickNumber = (int)SearchAnswer.Rows[RowNumber].Cells[0].Value; string name = (string)SearchAnswer.Rows[RowNumber].Cells[1].Value; 设置当前选中行为一整行： dgv.SelectionMode = DataGridViewSelectionMode.FullRowSelect; //选中状态为当前行 设置当前列不可排序: dgv.Columns[i].SortMode = DataGridViewColumnSortMode.NotSortable; //禁止对列进行排序DataGridView实现数据库的修改 删除选中行： int RowNumber = dataGridView1.CurrentCell.RowIndex; //获取当前选中行的行号 dataGridView1.Rows.RemoveAt(RowNumber); SqlCommandBuilder cb = new SqlCommandBuilder(sda); //更新数据库 sda.Update((DataTable)bs.DataSource); 动态添加行，显示DataTable中的内容： private void SetupColumns(DataGridView dgv, DataTable dt) { dgv.AutoGenerateColumns = false; //设置datagridview不可自动创建列 //手动为datagridview添加新列 dgv.Columns.Clear(); DataGridViewTextBoxColumn nameColumn = new DataGridViewTextBoxColumn(); nameColumn.HeaderText = "病历名称"; //列名称 nameColumn.ValueType = typeof(string); //设置列属性 dgv.Columns.Add(nameColumn); //添加列 DataGridViewTextBoxColumn CreateTimeColumn = new DataGridViewTextBoxColumn(); CreateTimeColumn.HeaderText = "创建时间"; CreateTimeColumn.ValueType = typeof(string); dgv.Columns.Add(CreateTimeColumn); DataGridViewTextBoxColumn PathColumn = new DataGridViewTextBoxColumn(); PathColumn.HeaderText = "病历保存路径"; PathColumn.ValueType = typeof(string); dgv.Columns.Add(PathColumn); //循环遍¦历Datatable中内容赋值给datagridview的每一行 foreach (DataRow dr in dt.Rows) { int j = 0; int dgvCount = dgv.Rows.Add(); //给datagridview动态创建一个行对象 foreach (DataColumn dc in dt.Columns) { string name = (string)dr[dc]; dgv.Rows[dgvCount].Cells[j].Value = name; j++; } } }将数据库信息写入byte数组： con.ConnectionString = s; con.Open(); SqlCommand sqlcommon = new SqlCommand("select * from 数据源", con); SqlDataReader reader1 = sqlcommon.ExecuteReader(); int Line = reader1.FieldCount - 1;//获取当前行中的列数 int Row = 0; //记录当前数据库的行数 int num = 0; //记录receiveData数据 while (reader1.Read()) { Row++; } reader1.Close(); byte[] receiveData = new byte[Row * Line]; SqlDataReader reader = sqlcommon.ExecuteReader(); while (reader.Read()) { receiveData[num] = (byte)(int.Parse((string)reader["列名"].ToString().Trim())); receiveData[num + 1] = (byte)(int.Parse((string)reader["列名"].ToString().Trim())); receiveData[num + 2] = (byte)(int.Parse((string)reader["列名"].ToString().Trim())); receiveData[num + 3] = (byte)(int.Parse((string)reader["列名"].ToString().Trim())); receiveData[num + 4] = (byte)(int.Parse((string)reader["列名"].ToString().Trim())); receiveData[num + 5] = (byte)(int.Parse((string)reader["列名"].ToString().Trim())); receiveData[num + 6] = (byte)(int.Parse((string)reader["列名"].ToString().Trim())); num += 7; } reader.Close(); con.Close();将byte数组写入数据库： con.Open(); string SqlStartTime = " insert into 表名values(@Line,@FH1,@FH2)"; SqlCommand cmd = new SqlCommand(s, con); cmd.Parameters.AddWithValue("@Line", MajorKey); cmd.Parameters.AddWithValue("@FH1", buffer[i].ToString()); cmd.Parameters.AddWithValue("@FH2", buffer[i + 1].ToString()); cmd.ExecuteNonQuery();//注意此句没有就无法插入数据 con.Close(); //关闭打开连接DropDownList动态显示数据库中的表： string s = "Data Source=A3135;Initial Catalog=TotalNumber;Integrated Security=True"; con = new SqlConnection(s); DataSet ds = new DataSet(); string strSQL = "select name from sysobjects where type='" + 'U' + "'"; sda = new SqlDataAdapter(strSQL, con); sda.Fill(ds); for (int i = 0; i < ds.Tables[0].Rows.Count; i++) { comboBox1.Items.Add(ds.Tables[0].Rowsi.ToString()); }
本文主要描述了在大规模的网络环境中部署服务功能存在的一些问题，还提出了几个关键领域，即SFC工作组将要探讨的关于SFC结构、工作协议、相关文档。1.问题描述 SFC工作组致力于解决的几个服务部署中存在的问题 1.1 拓扑相互依赖 网络服务的部署依赖于网络拓扑（物理网络、虚拟网络、混合网络），例如，当流量需要通过防火墙，则需要在网络路径上部署防火墙，或者引导网络流通过防火墙。服务传送上的限制可能抑制网络运营商的资源利用率、降低灵活性。拓扑的耦合限制了服务功能的部署和选择：服务功能在拓扑中的位置是固定的，因此部署和选择服务功能需要考虑拓扑信息。如下是一个简单例子：网络服务将服务负载均衡器作为一个默认网关，当网络服务响应流量负载不均衡时，所有通过服务的流量必须经过负载均衡器，强制网络管理员创建一个复杂的路由机制或者额外的接口来提供另一种拓扑结构。 1.2 配置复杂 1.3 限制高可用性 1.4服务功能顺序的一致性 服务功能本身是相互独立的，然而对于管理员来说，许多服务功能有一个严格的安放顺序。管理员没有一致的规则放置这些服务功能，因此选择链的顺序是复杂、繁琐的。 1.5 应用服务策略 服务功能依据拓扑信息来选择服务策略，由于扩展、延迟、复杂性的原因，收集拓扑信息越来越不可行，中心拓扑信息并不包含详细信息，这也迫使服务功能单独执行细粒度的分类，换句话说，拓普信息太粗粒度。 1.6 传输依赖 服务功能和拓扑的耦合性需要服务功能支持许多传输封装或者传输网关功能。 1.7 弹性服务交付 1.8 流量选择标准 特定段的所有流量强制通过该段内的所有服务不管该流量是否需要该服务，这是由于转发拓扑决定了转发规则。在一些部署中，通过路由策略或者访问控制过滤来细粒度流量选择，但这也会导致配置复杂，缺乏灵活性。 1.9 限制端到端服务的透明性 1.10 每一个服务功能的分类/重新分类 每一个服务功能前都需要有分类器，分类器间是相互独立的，不同服务功能的分类器功能是不同的，服务功能可能无法利用其他服务功能的分类结果。 1.11 对称的流量 服务功能链根据服务功能的要求决定是单向还是双向的，许多服务功能如DPI、防火墙都是双向的为了确保流状态的一致性。 1.12 多供应商的服务功能服务功能链 服务功能链希望通过服务部署解决上述问题，SFC工作组针对如下方面探讨解决方案。 2.1 服务覆盖网络 服务功能链利用特定的覆盖网络创建服务拓扑，覆盖服务提供服务功能的连接。创建一个上层的网络拓扑，该拓扑允许操作者利用覆盖层或者底层网络创建服务功能路径，依据需要在网络上部署服务功能。 从服务拓扑的角度，服务功能可以被看做资源消耗，按照实际需要的顺序连接服务功能（资源），不需要底层网络的改变，很容易的添加网络功能。 服务层能够提供特定服务的信息来进行服务相关的故障诊断。 2.2 业务划分 分类器选择哪些流进入服务网络，分类规则可根据设备能力、顾客需求、售后服务。分类器决定流通过哪个服务功能链，后续的分类根据服务功能链中的重新分类器改变服务功能的顺序。 2.3 SFC封装 SFC的封装可以在数据平面创建服务功能链，以及传送链的信息，如链标识、OAM状态。SFC的封装包含数据平面的元数据，元数据可用逻辑分类结点和服务功能以及服务功能间的信息交换，元数据不是用来传递数据包的。元数据可以包含先前分类器的分类结果、外部的资源信息，服务功能根据元数据进行本地策略决策。 元数据除了共享信息外，从网络拓扑上解耦策略，移除了每个服务功能前的分类器。3 安全考虑 服务覆盖网络：依据存在的传输协议创建，因此服务覆盖网络需要传输协议的安全机制。如操作者需要认证或者加密，可以使用传输协议的安全机制提供了这样的功能。
状态模式状态模式：当一个对象内在状态改变时允许改变行为，这个对象看起来像改变了其类（Allow an object to alter its behavior when its internal state changes. The object will appear to change its class）。状态模式的核心是封装，状态的变更引起了行为的变更，从外部看起来就好像这个对象对应的类发生了改变。状态模式的具体应用本篇采用设计模式之禅中的实例，关于电梯状态改变，从而引发电梯行为改变的例子。我们首先对假设电梯有四种状态：敞门状态、闭门状态、运行状态、停止状态，电梯有四个动作：开门、关门、运行、停止，我们通过下表表示电梯状态和动作之间的关系： 开门（open）关门（close）运行（run）停止（stop）敞门状态●★●●闭门状态★●★★运行状态●●●★停止状态★●★●电梯动作和状态对应表（●表示不允许，★表示允许） 当然我们在程序中可以通过设置电梯的状态，从而执行每个动作时判断当前电梯处于什么状态，从而决定电梯执行的动作，然而程序中会存在大量的switch......case语句。接下来我们直接介绍通过状态模式的方式实现电梯状态与动作的对应关系。通用类图如下：public abstract class LiftState {private Context context; public void setContext(Context text){ this.context = text; } public Context getContext(){ return this.context; } public abstract void open(); public abstract void close(); public abstract void run(); public abstract void stop();}public class OpenningState extends LiftState{@Override public void open() { // TODO Auto-generated method stub System.out.println("电梯门开启......"); } @Override public void close() { // TODO Auto-generated method stub super.getContext().setLiftState(Context.closingState); super.getContext().getLiftState().close(); } @Override public void run() { // TODO Auto-generated method stub } @Override public void stop() { // TODO Auto-generated method stub } }public class ClosingState extends LiftState{@Override public void open() { // TODO Auto-generated method stub super.getContext().setLiftState(Context.openningState); super.getContext().getLiftState().open(); } @Override public void close() { // TODO Auto-generated method stub System.out.println("电梯门关闭......"); } @Override public void run() { // TODO Auto-generated method stub super.getContext().setLiftState(Context.runningState); super.getContext().getLiftState().run(); } @Override public void stop() { // TODO Auto-generated method stub super.getContext().setLiftState(Context.stoppingState); super.getContext().getLiftState().stop(); } }public class RunningState extends LiftState{@Override public void open() { // TODO Auto-generated method stub } @Override public void close() { // TODO Auto-generated method stub } @Override public void run() { // TODO Auto-generated method stub System.out.println("电梯正在运行......"); } @Override public void stop() { // TODO Auto-generated method stub super.getContext().setLiftState(Context.stoppingState); super.getContext().getLiftState().stop(); } }public class StoppingState extends LiftState{@Override public void open() { // TODO Auto-generated method stub super.getContext().setLiftState(Context.openningState); super.getContext().getLiftState().open(); } @Override public void close() { // TODO Auto-generated method stub } @Override public void run() { // TODO Auto-generated method stub super.getContext().setLiftState(Context.runningState); super.getContext().getLiftState().run(); } @Override public void stop() { // TODO Auto-generated method stub System.out.println("电梯停止了......"); } }public class Context {public static OpenningState openningState = new OpenningState(); public static ClosingState closingState = new ClosingState(); public static RunningState runningState = new RunningState(); public static StoppingState stoppingState = new StoppingState(); private LiftState liftState; //定义一个电梯的状态 public void setLiftState(LiftState state){ this.liftState = state; //把当前的环境通知到各个实现类中 this.liftState.setContext(this); } public LiftState getLiftState(){ return this.liftState; } public void open(){ this.liftState.open(); } public void close(){ this.liftState.close(); } public void run(){ this.liftState.run(); } public void stop(){ this.liftState.stop(); }}public class Client {/** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Context context = new Context(); context.setLiftState(new ClosingState()); context.open(); context.close(); context.run(); context.stop(); } }View Code状态模式的优缺点状态模式的优点结构清晰，避免了过多的switch...case或者if...else语句，避免了程序的复杂性，提高了程序的可维护性；遵循设计原则，很好地体现了开闭原则和单一职责原则，每个状态都是一个子类，增加状态就增加子类，修改状态只需修改状态对应的子类封装性好，状态的变化放置到类的内部来实现，外部的调用不知道类内部如何实现状态和行为的变换。状态模式的缺点状态模式的缺点就是当对象状态过多时，会导致子类过多，也就是类膨胀，当然可以通过和其他设计模式组合（建造者模式）的方式来缓解类过多的情况。状态模式的使用场景行为虽状态改变而改变的场景，这也是状态模式的出发点，例如权限设计，人员的状态不同即使执行相同的行为结果也会不同，在这种情况下需要考虑使用状态模式。条件、分支判断语句的替代者，在程序中大量使用switch或者if判断语句导致程序结构不清晰，逻辑混乱，使用状态模式可以很好地避免这一问题，通过扩展子类实现了条件的判断逻辑。在行为受状态约束的场景下可以使用状态模式，而且使用时对象的状态最好不要超过5个。