关于框架学习:PGLBox-超大规模-GPU-端对端图学习训练框架正式发布

作者 | PGLBox项目组 导读 PGLBox是百度研发的基于GPU的大规模图模型训练框架,反对数百亿节点和边的图模型全GPU训练,已在百度宽泛部署。相比业界支流的分布式 CPU 解决方案,PGLBox 具备超高性能、超大规模、算法丰盛、灵便易用、落地宽泛等劣势。与传统的基于GPU的分布式解决方案相比,PGLBox能够在雷同的老本下晋升27倍的训练速度。 全文2232字,预计浏览工夫6分钟。 图神经网络(Graph Neural Network,GNN)是近年来呈现的一种利用深度学习间接对图构造数据进行学习的办法。通过在图的节点和边上制订聚合的策略,GNN 可能学习到图构造数据中节点以及边外在法则和更加深层次的语义特色。图神经网络不仅成为学术界钻研热点,而且曾经在工业界广泛应用落地。特地在搜寻、举荐、地图等畛域,采纳大规模分布式图引擎对异构图构造进行建模,这曾经成为技术倒退的新趋势。目前,分布式图学习框架通常在 CPU 集群上部署分布式图服务以及参数服务器,来反对大规模图构造的存储以及特色的更新。然而,基于 CPU 算力的图学习框架在建设老本、训练速度、稳定性以及简单算法反对等方面都存在有余。 因而,百度飞桨推出了可能同时反对简单图学习算法+超大图+超大离散模型的 GPU 大规模图学习训练框架 PGLBox。该框架联合了百度挪动生态模型团队在大规模业务技术的深耕,凝聚飞桨图学习 PGL 丰盛的算法能力与利用教训,并依靠飞桨深度学习平台通用的训练框架能力与灵便组网能力。不仅继承了飞桨后期开源的 Graph4Rec[1]的超大规模、灵便易用和适用性广的长处[2],而且训练性能取得了显著晋升,图算法能力反对更宽泛。 01 超高性能 GPU 分布式图学习训练框架随着图数据规模的一直增大,基于 CPU 分布式的解决方案须要大量的跨机器通信,导致训练速度慢且稳定性差。为了解决这个问题,PGLBox 将图存储、游走、采样、训练全流程 GPU 化,并实现流水线架构,极致晋升异构硬件效率,大幅晋升了图学习算法的训练速度。同时,针对 NVLink 拓扑、网卡拓扑非全互联问题,实现智能化直达通信,进一步晋升训练能力。相比基于 MPI CPU 分布式的传统计划,训练速度晋升 27 倍。PGLBox 实现了多级存储体系,对图、节点属性和图模型进行差异化存储,即图构造全显存、节点属性二级存储和图模型三级存储,将图规模晋升了一个数量级。为了均衡磁盘、内存、显存之间的占用,PGLBox 实现了平衡训练,对 Pass 大小平滑解决,削峰填谷,升高峰值显存,使得在单机状况下,可反对的图规模失去大幅晋升。 02 全面降级预置的图示意学习算法 图节点的属性是多种多样的,能够是文本、图像,也能够是用户画像、地理位置等,如何更好地建模节点特色是图示意学习的一个重要挑战。随着预训练模型席卷 NLP、CV等畛域,预训练 Transformer 是节点属性建模不可或缺的一部分。而简单构造的 Transformer 等预训练模型的引入所减少的大量计算量,是以往 CPU 分布式图示意学习框架不可承受的。得益于 PGLBox 同时兼备 GPU 的运算能力和大规模图的反对,让咱们可能同时实现大规模预训练模型 + 大规模图构造信息 + 大规模离散特色的端对端对立建模。在大规模图数据,通过三级存储加载之后,咱们能够通过加载不同的大规模预训练模型(例如 ERNIE 语言大模型、ERNIE-ViL 跨模态大模型等)来建模更丰盛的节点信息。对于大规模离散特色如用户ID、商品ID等,咱们能够同时利用到 PGLBox 提供的 GPU 参数服务器能力来建模。最初通过图信息汇聚的 Graph Transformer 图神经网络模块实现信息聚合,失去图的最终示意,并配合上游工作实现跨模态异构图端对端优化。 ...

February 28, 2023 · 1 min · jiezi

关于框架学习:接口测试框架接入性能测试实践分享

前言====== 现如今接口测试在软件品质行业中的位置,曾经越来越重要,绝对于下层的UI自动化测试和上层的单元测试,接口测试的“低”投入、“高”回报,也成了绝大多数品质保障实际的首选。 在发展接口测试时,往往很多时候都只在关注接口的功能性品质,而对于非功能性的品质保障验证,比方性能、平安,在理论工程利用或者设计用例时关注度显著有余(甚至很多压根没有这方面的测试实际)。 明天就以Python系下requests库(罕用于接口测试)和Robot FrameWork框架为例,和大家聊聊在接口测试过程中,和性能需要等同重要的性能测试查看项。 接口测试须要思考的性能查看项================== 在接口测试过程中,除了要思考产品需要的失常、异样、数据正确性等显性功能需要品质外,还有很多隐性需要品质须要关注,以性能测试为例,常见须要关注的查看项包含,但不限于: 1、单用户登录的响应工夫是否小于 3 秒;2、单用户登录时,后盾申请数量是否过多;3、高并发场景下用户登录的响应工夫是否小于 5 秒;4、高并发场景下服务端的监控指标是否合乎预期;5、高集合点并发场景下,是否存在资源死锁和不合理的资源期待;6、长时间大量用户间断登录和登出,服务器端是否存在内存透露。本文重点以接口响应工夫为例,介绍如何在requests库和Robot FrameWork框架纳入性能测试项,其它性能查看项思路根本都是相通的。 requests库接口测试校验响应工夫======================= 通常在联合requests库发展接口测试时,发送申请后,接口的响应工夫,也是咱们须要关注的一个重点,如果响应工夫太长,从产品业务或者用户角度也是不可承受的。那如何进行申请响应工夫校验,也成为了接口测试人员须要把握的一项小技能。 针对requests库校验申请响应工夫,给大家提供两种实现思路: 一、借助申请响应超时机制 具体实现: #-*- coding:utf-8 -*-import requestsfrom requests import exceptionstry: req = requests.post(url=url, data=data, headers=headers, verify=False, timeout=3) print r.json()except exceptions.Timeout as e: print("抛出异样") 在上述实现中,通过减少timeout参数,设置申请响应超时机制,当timeout=3(可自在定义),当申请响应工夫超过3秒,则会抛出超时异样。 其中,额定补充的知识点:超时(默认单位:s),有两种设置超时办法: timeout=5:设置5s的超时工夫timeout=(5,10):设置区间工夫的期待 当申请呈现超时时,则会抛出此异样:requests.exceptions.ConnectTimeout: HTTPConnectionPool 2、获取响应工夫办法:req.elapsed.total_seconds() 二、借助requests接口响应返回值elapsed 具体实现: #-*- coding:utf-8 -*-import requestsr = requests.post(url, data=data, headers=headers)print(r.status_code)print(r.elapsed)print(r.elapsed.total_seconds())print(r.elapsed.microseconds)print(r.elapsed.seconds)elapsed_time = r.elapsed.total_seconds() #获取理论的响应工夫 assert elapsed_time>3 上述代码实现中,通过获取申请返回的响应值来获取接口响应工夫,常见的几个获取响应工夫参数为: elapsed.total_seconds:获取响应工夫,单位s (举荐)elapsed. microseconds:获取响应工夫,大于1s的时候,只截取了前面的小数局部elapsed.seconds:单位s,响应工夫小于1s时,为0Robot Framework框架校验响应工夫=========================== 在上述咱们介绍了requests库在发展接口测试过程,校验申请响应工夫的实现思路。而通过Robot Framework框架发展接口测试,次要会依赖RequestsLibray库,而因而实现思路也是一样的。 ...

September 15, 2020 · 1 min · jiezi

第五篇-仿写Vue生态系列解析模板事件

( 第五篇 )仿写'Vue生态'系列___"解析模板事件" 本次任务 取消'eval', 改为'new Function'.支持用户使用'@'与'v-on'绑定各种事件.支持初始化'methods'数据.使用函数时可以传参与不传参, 可以使用'$event'.实现'c-center'与'c-show'指令.实现'cc_cb'函数, 模板里面也可以用if - else.一. eval 与 Function项目里面的取值操作, 我之前一直采用的都是eval函数, 但是前段时间突然发现一个特别棒的函数Function, 下面我来演示一下他的神奇之处. 1. 可以执行字符串 let fn1 = new Function('var a = 1;return a'); console.log(fn1()); // 12. 可以传递参数下面写的name与age就是传入函数的两个参数, let fn2 = new Function('name','age', ' return name+age');console.log(fn2('lulu',24)); // lulu24第二种传参方式 let fn3 = new Function('name, age', ' return name+age');console.log(fn3('lulu',24)); // lulu24综上我可以推断, 他的原理是把最后一个参数当做执行体, 然后前面如果有参数就被当做新生成函数的参数. 3. 全局作用域他执行的时候里面的作用域是全局的, 就算在函数内部, 执行时候也取不到函数内部的值, 所以想要使用的值, 都需要我们手动传进去. // 报错了, 找不到ufunction cc(){ let u = 777; let fn = new Function('var a = 5;console.log(u); return a'); console.log(fn()); } cc()// 执行成功function cc(){ u = 777; // 直接挂在window上 let fn = new Function('var a = 5;console.log(u); return a'); // 777 console.log(fn()); // 5 } cc()我也试了一下, 里面的var a 并不会污染全局, 放心使用吧; ...

October 6, 2019 · 4 min · jiezi

Swoole-整合成一个小框架

概述这是关于 Swoole 学习的第六篇文章:Swoole 整合成一个小框架。 第五篇:Swoole 多协议 多端口 的应用第四篇:Swoole HTTP 的应用第三篇:Swoole WebSocket 的应用第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用写了关于 Swoole 入门的 5 篇文章后,增加了不少的关注者,也得到了一些大佬的鼓励,也有很多关注者都加了微信好友,交流之后发现一些朋友比我优秀还比我努力,也得到了一些大佬的建议。 发现持续写文章真的不是件容易的事,担心别人认为没价值,担心想法太幼稚或有漏洞被别人笑话,担心脑子里墨水太少,写不出来... 知道自己思路还不够清晰,逻辑还不够严谨,告诉自己没关系,一些都会好起来的,逆境才能成长嘛,敢写就是好的开始,以此来激励自己持续的学习和思考。 跑题了,说回正题。 这篇文章其实是读者的小小要求,事情是这样的: 读者:“亮哥,看了你的文章很有收获,将文章 Demo 放在本地直接就能运行了,太感谢了” 本人:“哈哈。。。有收获就好,感谢支持 ~ ” 读者:“我有一个小小的要求,现在每个文件都是独立的,我想部署到生产环境,想操作上更便捷,有日志...” 本人:“你说的不是框架吗?现在有很多现成的,看 Swoole 官网推荐的 Swoft、EasySwoole、MixPHP 等。详细的参考这个地址:https://wiki.swoole.com/wiki/...” 读者:“看了,发现文件太多了,看不懂,你能帮忙讲解下吗?” 本人:“What?我也是入门呀,要不我搞个简单的轮子吧” ...... 于是就有了这篇文章,正好也是对前面 5 篇文章的复习吧。 效果 命令如下: php index.php 可以查看到上图php index.php start 开启服务(Debug模式)php index.php start -d 开启服务(Daemon模式)php index.php status 查看服务状态php index.php reload 服务热加载php index.php stop 关闭服务index.php 这是文件名称,大家叫什么都可以。 目录结构如下: ├─ controller│ ├── ...├─ client│ ├─ websocket│ ├── ...│ ├─ tcp│ ├── ...├─ server│ ├─ config│ ├── config.php│ ├─ core│ ├── Common.php│ ├── Core.php│ ├── HandlerException.php│ ├─ log -- 需要 读/写 权限│ ├── ...├─ index.php目前就这几个文件,后期研究新的知识点会直接集成到这里面。 ...

May 15, 2019 · 6 min · jiezi

Luthier-CI-中间件-Middleware

中间件 Middleware内容 Contents介绍 Introduction中间值执行点 Middleware execution points创建中间件 Create a middleware分配中间值 Assign a middleware 全局中间件 Global Middleware路由中间件 Route middleware运行中间件 Run a middleware 中间件参数 Middleware parameters外部中间件 External middleware 介绍 ( Introduction )将中间件视为一组层,请求必须在您的应用程序中通过才能到达资源。 例如,使用中间件,您可以验证用户是否已登录并具有足够的权限来访问应用程序的某些部分,否则将其重定向到其他位置。 实际上,中间件是控制器的扩展,因为框架的单例已经在此时构建,您可以使用该ci()函数来获取它。 中间件执行点 ( Middleware execution points )有两个执行点: pre_controller: 此时定义的中间件将在控制器构造函数之后执行,但在执行任何控制器操作之前执行。post_controller: 此时定义的中间件将完全在post_controllerCodeIgniter 的本机钩子上运行。控制器构造函数始终首先执行 这是CodeIgniter的行为,而Luthier CI不会对其进行修改。 在某些时候您可能需要在中间件之前执行代码,这样做的方法是在控制器中定义一个名为的公共方法preMiddleware: <?php# application/controllers/TestController.phpdefined('BASEPATH') OR exit('No direct script access allowed');class TestController extends CI_Controller{ public function preMiddleware() { // This will be executed after the constructor (if it exists), but before the middleware }}作为路由在回调中不可用 ...

May 6, 2019 · 2 min · jiezi

Luthier-CI-例子-Examples

例子 Examples例子 # 1: 多语言网站 Multi-language website这是一个示例,显示了由URL管理的多语言网站。中间件用于加载当前语言文件。 <?php# application/routes/web.phpRoute::get('/', function(){ // Route "by default". This is a good place to request a cookie, session variable    // or something that allows us to restore the last language of the user, or show a    // language selection screen if no information is provided. redirect(route('homepage', ['_locale' => 'en']));});Route::group('{((es|en|it|br|ge)):_locale}', ['middleware' => ['Lang_middleware']], function(){ Route::get('home', function(){ var_dump( ci()->lang->line('test') ); })->name('homepage'); Route::get('about', function(){ var_dump( ci()->lang->line('test') ); })->name('about');});<?php# application/middleware/Lang_middleware.phpclass Lang_middleware{ public function run() { // Obtaining the value of the "_locale" sticky parameter $locale = ci()->route->param('_locale'); $langs = [ 'es' => 'spanish', 'en' => 'english', 'it' => 'italian', 'br' => 'portuguese-brazilian', 'ge' => 'german', ]; ci()->lang->load('test', $langs[$locale]); }}

May 6, 2019 · 1 min · jiezi

Luthier-CI-命令行-Command-line

命令行 Command line内容 Contents介绍 Introduction句法 Syntax使用CLI路由 Using CLI routes内置CLI工具 Built-in CLI tools 激活 Activation'luthier make'命令 'luthier make' command'luthier migrate'命令 'luthier migrate' command 介绍 ( Introduction )感谢Luthier CI,您可以通过命令行界面(CLI)利用框架提供的各种可能性。 句法 SyntaxCLI路由的语法类似于HTTP和AJAX路由。必须在application/routes/cli.php文件中定义CLI路由 例: <?php# application/routes/cli.php// Using anonymous functionsRoute::cli('test', function(){ // <- (note that here the method is 'cli' and not 'get', 'post', etc.) echo 'Hello world!';});// Pointing to an existing controllerRoute::cli('test2', 'foo@bar');CLI路由共享与HTTP / AJAX对应的相同属性,您可以在此处了解有关它们的更多信息。 使用CLI路由 Using CLI routesCLI路由共享与HTTP / AJAX对应的相同属性,您可以在此处了解有关它们的更多信息。 ...

May 6, 2019 · 2 min · jiezi

Luthier-CI-简单的认证-SimpleAuth

简单的认证 SimpleAuth内容 Contents介绍 Introduction安装 Installation 第1步:复制所需的文件 Step 1: Copy the required files第2步:安装数据库 Step 2: Install the database第3步:定义路线 Step 3: Define the routesSimpleAuth控制器 SimpleAuth Controller 自定义用户注册表单 Customize the user registration formSimpleAuth中间件 SimpleAuth MiddlewareSimpleAuth库 SimpleAuth Library 基本功能 Basic functions 获取当前用户 Obtaining the current user验证用户是否是来宾(匿名) Verify if a user is a guest (anonymous)验证用户的角色 Verify the role of a user验证用户的权限 Verify the user's permissions访问控制列表(ACL)功能 Access Control List (ACL) functions其他功能 Other functions意见和翻译 Views and translations ...

May 6, 2019 · 5 min · jiezi

Luthier-CI-认证-Authentication

认证 Authentication介绍 IntroductionCodeIgniter包含构建用户身份验证系统所需的所有工具。不幸的是,它缺乏易于实现,维护和扩展的集成接口或库。 Luthier CI使用受Symfony启发的身份验证模型解决了这个问题,该模型寻求尽可能多的灵活性,以便开发人员可以快速开始工作,而无需重新发明轮子。 激活 Activation作为可选模块,必须首先激活Luthier CI认证功能。为此,请转到该 application/config/hooks.php 文件并替换它: <?php# application/config/hooks.phpdefined('BASEPATH') OR exit('No direct script access allowed');// (...)$hook = Luthier\Hook::getHooks();附: <?php# application/config/hooks.phpdefined('BASEPATH') OR exit('No direct script access allowed');// (...)$hook = Luthier\Hook::getHooks( [ 'modules' => ['auth'] ]);验证工具可用 Authentication tools availableLuthier CI的身份验证有两种:SimpleAuth 和 Luthier CI Authentication Framework. SimpleAuth: 最快最有趣的方式 ( the fastest and funniest way )如果您需要的是预先配置,可自定义且易于使用的身份验证系统,SimpleAuth非常适合您。它专为最常见的身份验证设计:通过表单和数据库进行传统登录。 它的一些功能: 登录屏幕和用户注册注册时验证电子邮件重设密码用户角色“提醒我”基于cookie的功能(可选)访问控制列表(ACL)(可选)它适用于所有CodeIgniter数据库驱动程序在登录期间防止暴力攻击(可选)路线的自动定义(使用方法Route::auth())多个模板可供选择,翻译成多种语言Luthier CI Authentication Framework: 适用于高级用户 ( for advanced users )Luthier CI Authentication Framework 是一组抽象地定义用户认证处理的类和接口。有了它,您可以执行以下任务: ...

May 6, 2019 · 1 min · jiezi

Luthier-CI-调试-Debug

调试 Debug实验功能 我们已经努力使事情正常工作,但是可能会出现错误,导致和/或收取此功能所需的资产。如果您在使用过程中发生过事故,请通知我们。 内容 Contents介绍 Introduction激活 Activation调试消息 Debug messages添加您自己的数据收集器 Add your own data collectors 介绍 Introduction由于将Luthier CI与这个出色的工具集成在一起,您可以将PHP Debug Bar 添加到您的应用程序中。 激活 Activation要激活此功能(默认情况下已禁用),请转到您的 application/config/hooks.php 文件并替换: <?php# application/config/hooks.phpdefined('BASEPATH') OR exit('No direct script access allowed');// (...)$hook = Luthier\Hook::getHooks();附: <?php# application/config/hooks.phpdefined('BASEPATH') OR exit('No direct script access allowed');// (...)$hook = Luthier\Hook::getHooks( [ 'modules' => ['debug'] ]);您应该在窗口底部看到调试栏: 调试消息 Debug messages要添加调试消息,请使用该类的 log() 静态方法 Luthier\Debug: # use Luthier\Debug;Debug::log($variable, $type, $dataCollector);$variable 要调试的变量在哪里,并且 $type 是消息的类型,可以是 'info', 'warning' 或 'error'. ...

May 6, 2019 · 1 min · jiezi

Luthier-CI安装-Installation

安装 ( Installation )内容 ( Contents )要求 Requirements安装 Installation 获得Luthier CI Get Luthier CI启用Composer自动加载和挂钩 Enable Composer autoload and hooks将Luthier CI与您的应用程序连接 Connect Luthier CI with your application初始化 Initialization 要求 ( Requirements )PHP >= 5.6 (Compatible con PHP 7)CodeIgniter 3 安装 ( Installation ) 获得Luthier CI ( Get Luthier CI )需要Composer Luthier CI通过Composer安装。你可以在这里得到它。 here. 转到该application文件夹并执行以下命令: composer require luthier/luthier 启用Composer autoload 和 hooks要使Luthier CI工作,必须在框架中启用Composer 自动加载和挂钩。在文件中config.php修改以下内容: <?php# application/config/config.php// (...)$config['enable_hooks'] = TRUE;$config['composer_autoload'] = TRUE;// (...) 将Luthier CI与您的应用程序连接在hooks.php文件中,将Luthier CI挂钩分配给$hook变量: ...

May 6, 2019 · 1 min · jiezi

Luthier-CI-认证框架-Authentication-Framework

Luthier CI 认证框架 ( Authentication Framework )内容 Contents介绍 Introduction创建用户提供商 Creation of User Providers 用户实例 User instance用户加载 Users load密码哈希及其验证 Password hash and its verification验证用户是否处于活动状态并已验证 Validate that a user is active and verified与用户提供商合作 Working with User Providers 用户登录 User login高级用户登录 Advanced user login会话 Sessions 在会话中存储用户 Storing a user in the session从会话中检索用户 Retrieving a user from the session自定义会话数据 Custom session data删除当前会话 Deleting the current session用户操作 User Operations 角色验证 Roles verification权限验证 Permissions verification基于控制器的认证 Controller-based authentication ...

May 6, 2019 · 8 min · jiezi

Luthier-CI-路由-Routes

路由 ( Routes )内容 ( Contents )介绍 Introduction路由类型 Route types句法 Syntax 命名空间 Namespaces前缀 Prefixes命名路线 Named routes回调为路线 Callbacks as routes组 Groups资源路线 Resource routes默认控制器 Default controller参数 Parameters 可选参数 Optional parameters参数正则表达式 Parameter regex“粘性”参数 "Sticky" parameters 介绍 ( Introduction )Luthier CI更改CodeIgniter路由的行为: 在CodeIgniter中,默认情况下,可以在任何HTTP谓词下访问路由。使用Luthier CI时,必须为每个路由定义接受的HTTP谓词,并且任何与这些参数不匹配的请求都将生成404错误。在CodeIgniter中,可以直接从URL访问控制器,而无需定义路由。另一方面,使用Luthier CI,尝试访问未定义的路径(即使URL与控制器的名称和方法匹配)也会生成404错误。在CodeIgniter中,路由参数是指向控制器的简单正则表达式,在Luthier CI中,路由是一个独立且唯一的实体,它包含定义明确的参数以及从中构建URL的能力。在CodeIgniter中,您只能创建指向控制器的路由。使用Luthier CI,可以使用匿名函数作为控制器,甚至可以在不使用单个控制器的情况下构建完整的Web应用程序。 路由类型 ( Route types )您可以使用三种类型的路由: HTTP routes: 它们在HTTP请求下访问,并在application/routes/web.php文件中定义AJAX routes: 它们仅在AJAX请求下访问,并在application/routes/api.php文件中定义CLI routes: 它们仅在CLI(命令行界面)环境下访问,并在application/routes/cli.php文件中定义AJAX路由进入api.php 虽然你可以在 web.php 文件中定义AJAX路由,但最好这样做 api.php 如果您使用相同的URL和相同的HTTP动词定义两条或更多路线,则第一条路线将被返回ALWAYSLuthier CI允许您使用动词GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS和TRACE定义HTTP路由: 句法 ( Syntax )如果您使用过Laravel,那么您将知道如何使用Luthier CI,因为它的语法是相同的。这是路线最简单的例子: ...

May 6, 2019 · 3 min · jiezi

实例详解Spring-MVC入门使用

MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model),视图(View)和控制器(Controller).通过分层使开发的软件结构更清晰,从而达到开发效率的提高,可维护性和扩展性得到提高.Spring提供的MVC框架是在J2EE Web开发中对MVC模式的一个实现,本文通过实例讲解一下Spring MVC 的使用. 先来看一个HTTP request在Spring的MVC框架是怎么被处理的:(图片来源于Spring in Action) 1,DispatcherServlet是Spring MVC的核心,它的本质是一个实现了J2EE标准中定义的HttpServlet,通过在web.xml配置<servlet-mapping>,来实现对request的监听. <servlet> <servlet-name>springTestServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springTestServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>** 以.do结尾的request都会由springTestServlet来处理.2,3,当接受到request的时候,DispatcherServlet根据HandlerMapping的配置(HandlerMapping的配置文件默认根据<servlet-name>的值来决定,这里会读取springTestServlet-servlet.xml来获得HandlerMapping的配置信息),调用相应的Controller来对request进行业务处理. <bean id="simpleUrlMapping"class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/login.do">loginController</prop> </props> </property> </bean> <bean id="loginController"class="com.test.spring.mvc.contoller.LoginController"> <propertyname="sessionForm"> <value>true</value> </property> <propertyname="commandName"> <value>loginCommand</value> </property> <property name="commandClass"> <value>com.test.spring.mvc.commands.LoginCommand</value> </property> <property name="authenticationService"> <refbean="authenticationService"/> </property> <propertyname="formView"> <value>login</value> </property> <propertyname="successView"> <value>loginDetail</value> </property> </bean>** 以login.do结尾的request由loginController来处理.<property name="formView">配置的是Controller接收到HTTP GET请求的时候需要显示的逻辑视图名,本例是显示login.jsp,<property name="successView">配置的是在接收到HTTP POST请求的时候需要显示的逻辑视图名,在本例中即login.jsp提交的时候需要显示名为loginDetail的逻辑视图.4,Controller进行业务处理之后,返回一个ModelAndView对象.return new ModelAndView(getSuccessView(),"loginDetail",loginDetail);5,6,DispatcherServlet根据ViewResolver的配置(本例是在springTestServlet-servlet.xml文件中配置)将逻辑view转换到真正要显示的View,如JSP等. <bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass"> <value>org.springframework.web.servlet.view.JstlView</value> </property> <property name="prefix"> <value>/jsp/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean>**其作用是将Controller中返回的ModleAndView解析到具体的资源(JSP文件),如上例中的return new ModelAndView(getSuccessView();按照上面ViewResolver配置,会解析成/jsp/loginDetail.jsp.规则为prefix+ModelAndView的第二个参数+suffix.示例的完整代码如下: ...

April 29, 2019 · 2 min · jiezi

模拟spring框架,深入讲解spring的对象的创建

导读项目源码地址因为公司使用的是spring框架,spring是什么?它就像包罗万象的容器,我们什么都可以往里面填,比如集合持久层的hibernate或mybatis框架,类似于拦截器的的shiro框架等等。它的好处是可以自动创建对象。以前,在没有使用spring框架时,我们必须自己创建对象。但自从有了spring框架后,Java开发就像迎来了春天,一切都变的那么简单。它有几种自动创建对象的方式,比如构造器创建对象,set创建对象。。。如果想要对其有更多的了解,那么,下载有很多博客,都对其做了详细的介绍。我在这里不必再做详解了。项目使用了logback和slf4j记录日志信息,因为它们两个是经常合作的。同时,也使用了lombok框架,这个框架可以自动生成set、get、toString、equals、hashcode方法等。下面,便详细介绍我的这个项目。设计模式本项目采用工厂和建造者设计模式。工厂设计模式用来加载配置文件。在没有使用注解的前提下,我们把所有的将要创建对象的信息写进配置文件中,这就是我们常说的依赖注入。而当代码加载时,需要加载这些配置文。这里需要两个雷来支撑。一个是XmlConfigBean,记录每个xml文件中的bean信息。XmlBeanProperty这里记录每个bean中的属性信息。加载文件方法中调用了这两个类,当然,我是用了org下的jdom来读取xml文件,正如以下代码所示。 /** * Created By zby on 22:57 2019/3/4 * 加载配置文件 * * @param dirPath 目录的路径 /public static LoadConfig loadXmlConFig(String dirPath) { if (StringUtils.isEmpty(dirPath)){ throw new RuntimeException(“路径不存在”); } if (null == config) { File dir = new File(dirPath); List<File> files = FactoryBuilder.createFileFactory().listFile(dir); if (CollectionUtil.isEmpty(files)) { throw new RuntimeException(“没有配置文件files=” + files); } allXmls = new HashMap<>(); SAXBuilder saxBuilder = new SAXBuilder(); Document document = null; for (File file : files) { try { Map<String, XmlConfigBean> beanMaps = new HashMap<>(); //创建配置文件 String configFileName = file.getName(); document = saxBuilder.build(file); Element rootEle = document.getRootElement(); List beans = rootEle.getChildren(“bean”); if (CollectionUtil.isNotEmpty(beans)) { int i = 0; for (Iterator beanIterator = beans.iterator(); beanIterator.hasNext(); i++) { Element bean = (Element) beanIterator.next(); XmlConfigBean configBean = new XmlConfigBean(); configBean.setId(attributeToConfigBeanProps(file, i, bean, “id”)); configBean.setClazz(attributeToConfigBeanProps(file, i, bean, “class”)); configBean.setAutowire(attributeToConfigBeanProps(file, i, bean, “autowire”)); configBean.setConfigFileName(configFileName); List properties = bean.getChildren(); Set<XmlBeanProperty> beanProperties = new LinkedHashSet<>(); if (CollectionUtil.isNotEmpty(properties)) { int j = 0; for (Iterator propertyIterator = properties.iterator(); propertyIterator.hasNext(); j++) { Element property = (Element) propertyIterator.next(); XmlBeanProperty beanProperty = new XmlBeanProperty(); beanProperty.setName(attributeToBeanProperty(file, i, j, property, “name”)); beanProperty.setRef(attributeToBeanProperty(file, i, j, property, “ref”)); beanProperty.setValue(attributeToBeanProperty(file, i, j, property, “value”)); beanProperties.add(beanProperty); } configBean.setProperties(beanProperties); } beanMaps.put(configBean.getId(), configBean); } } allXmls.put(configFileName, beanMaps); } catch (JDOMException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return new LoadConfig(); } return config;}上面使用到了文件工厂设计模式,内部使用深度递归算法。如果初始目录下,仍旧有子目录,调用自身的方法,直到遇见文件,如代码所示:/Created By zby on 14:04 2019/2/14获取文件的集合/private void local(File dir) {if (dir == null) { logger.error(“文件夹为空dir=” + dir); throw new RuntimeException(“文件夹为空dir=” + dir);}File[] fies = dir.listFiles();if (ArrayUtil.isNotEmpty(fies)) { for (File fy : fies) { if (fy.isDirectory()) { local(fy); } String fileName = fy.getName(); boolean isMatch = Pattern.compile(reg).matcher(fileName).matches(); boolean isContains = ArrayUtil.containsAny(fileName, FilterConstants.FILE_NAMES); if (isMatch && !isContains) { fileList.add(fy); } }}}建造者设计模式这里用来修饰类信息的。比如,将类名的首字母转化为小写;通过类路径转化为类字面常量,如代码所示: / * Created By zby on 20:19 2019/2/16 * 通过类路径转为类字面常量 * * @param classPath 类路径 /public static <T> Class<T> classPathToClazz(String classPath) { if (StringUtils.isBlank(classPath)) { throw new RuntimeException(“类路径不存在”); } try { return (Class<T>) Class.forName(classPath); } catch (ClassNotFoundException e) { logger.error(“路径” + classPath + “不存在,创建失败e=” + e); e.printStackTrace(); } return null;}类型转换器如果不是用户自定义的类型,我们需要使用类型转化器,将配置文件的数据转化为我们Javabean属性的值。因为,从配置文件读取过来的值,都是字符串类型的,加入Javabean的id为long型,因而,我们需要这个类型转换。/* * Created By zby on 22:31 2019/2/25 * 将bean文件中的value值转化为属性值 /public final class Transfomer { public final static Integer MAX_BYTE = 127; public final static Integer MIN_BYTE = -128; public final static Integer MAX_SHORT = 32767; public final static Integer MIN_SHORT = -32768; public final static String STR_TRUE = “true”; public final static String STR_FALSE = “false”; /* * Created By zby on 22:32 2019/2/25 * 数据转化 * * @param typeName 属性类型的名字 * @param value 值 / public static Object transformerPropertyValue(String typeName, Object value) throws IllegalAccessException { if (StringUtils.isBlank(typeName)) { throw new RuntimeException(“属性的类型不能为空typeName+” + typeName); } if (typeName.equals(StandardBasicTypes.STRING)) { return objToString(value); } else if (typeName.equalsIgnoreCase(StandardBasicTypes.LONG)) { return stringToLong(objToString(value)); } else if (typeName.equals(StandardBasicTypes.INTEGER) || typeName.equals(StandardBasicTypes.INT)) { return stringToInt(objToString(value)); } else if (typeName.equalsIgnoreCase(StandardBasicTypes.BYTE)) { return stringToByte(objToString(value)); } else if (typeName.equalsIgnoreCase(StandardBasicTypes.SHORT)) { return stringToShort(objToString(value)); } else if (typeName.equalsIgnoreCase(StandardBasicTypes.BOOLEAN)) { return stringToBoolean(objToString(value)); } else if (typeName.equalsIgnoreCase(StandardBasicTypes.DOUBLE)) { return stringToDouble(objToString(value)); } else if (typeName.equalsIgnoreCase(StandardBasicTypes.FLOAT)) { return stringToFloat(objToString(value)); } else if (typeName.equals(StandardBasicTypes.DATE)) { return stringToDate(objToString(value)); } else if (typeName.equals(StandardBasicTypes.BIG_DECIMAL)) { return stringToBigDecimal(objToString(value)); } else { return value; } } /* * Created By zby on 22:32 2019/2/25 * 数据转化 / public static void transformerPropertyValue(Object currentObj, Field field, Object value) throws IllegalAccessException { if (null == currentObj && field == null) { throw new RuntimeException(“当前对象或属性为空值”); } String typeName = field.getType().getSimpleName(); field.setAccessible(true); field.set(currentObj, transformerPropertyValue(typeName, value)); } /* * Created By zby on 23:29 2019/2/25 * obj to String / public static String objToString(Object obj) { return null == obj ? null : obj.toString(); } /* * Created By zby on 23:54 2019/2/25 * String to integer / public static Integer stringToInt(String val) { if (StringUtils.isBlank(val)) { return 0; } if (val.charAt(0) == 0) { throw new RuntimeException(“字符串转为整形失败val=” + val); } return Integer.valueOf(val); } /* * Created By zby on 23:31 2019/2/25 * String to Long / public static Long stringToLong(String val) { return Long.valueOf(stringToInt(val)); } /* * Created By zby on 23:52 2019/2/26 * String to byte / public static Short stringToShort(String val) { Integer result = stringToInt(val); if (result >= MIN_SHORT && result <= MAX_SHORT) { return Short.valueOf(result.toString()); } throw new RuntimeException(“数据转化失败result=” + result); } /* * Created By zby on 0:03 2019/2/27 * String to short / public static Byte stringToByte(String val) { Integer result = stringToInt(val); if (result >= MIN_BYTE && result <= MAX_BYTE) { return Byte.valueOf(result.toString()); } throw new RuntimeException(“数据转化失败result=” + result); } /* * Created By zby on 0:20 2019/2/27 * string to double / public static Double stringToDouble(String val) { if (StringUtils.isBlank(val)) { throw new RuntimeException(“数据为空,转换失败”); } return Double.valueOf(val); } /* * Created By zby on 0:23 2019/2/27 * string to float / public static Float stringToFloat(String val) { if (StringUtils.isBlank(val)) { throw new RuntimeException(“数据为空,转换失败”); } return Float.valueOf(val); } /* * Created By zby on 0:19 2019/2/27 * string to boolean / public static boolean stringToBoolean(String val) { if (StringUtils.isBlank(val)) { throw new RuntimeException(“数据为空,转换失败val=” + val); } if (val.equals(STR_TRUE)) { return true; } if (val.equals(STR_FALSE)) { return false; } byte result = stringToByte(val); if (0 == result) { return false; } if (1 == result) { return true; } throw new RuntimeException(“数据转换失败val=” + val); } /* * Created By zby on 0:24 2019/2/27 * string to Date / public static Date stringToDate(String val) { if (StringUtils.isBlank(val)) { throw new RuntimeException(“数据为空,转换失败val=” + val); } SimpleDateFormat format = new SimpleDateFormat(); try { return format.parse(val); } catch (ParseException e) { throw new RuntimeException(“字符串转为时间失败val=” + val); } } /* * Created By zby on 0:31 2019/2/27 * string to big decimal / public static BigDecimal stringToBigDecimal(String val) { if (StringUtils.isBlank(val)) { throw new RuntimeException(“数据为空,转换失败val=” + val); } return new BigDecimal(stringToDouble(val)); }}常量类型自动装配类型/* * Created By zby on 13:50 2019/2/23 * 装配类型 /public class AutowireType { /* * 缺省情况向,一般通过ref来自动(手动)装配对象 / public static final String NONE = null; /* * 根据属性名事项自动装配, * 如果一个bean的名称和其他bean属性的名称是一样的,将会自装配它。 / public static final String BY_NAME = “byName”; /* * 根据类型来装配 * 如果一个bean的数据类型是用其它bean属性的数据类型,兼容并自动装配它。 / public static final String BY_TYPE = “byType”; /* * 根据构造器constructor创建对象 / public static final String CONSTRUCTOR = “constructor”; /* * autodetect – 如果找到默认的构造函数,使用“自动装配用构造”; 否则,使用“按类型自动装配”。 / public static final String AUTODETECT = “autodetect”; }属性类型常量池/* * Created By zby on 22:44 2019/2/25 * 类型常量池 /public class StandardBasicTypes { public static final String STRING = “String”; public static final String LONG = “Long”; public static final String INTEGER = “Integer”; public static final String INT = “int”; public static final String BYTE = “Byte”; public static final String SHORT = “Short”; public static final String BOOLEAN = “Boolean”; public static final String DOUBLE = “double”; public static final String FLOAT = “float”; public static final String DATE = “Date”; public static final String TIMESTAMP = “Timestamp”; public static final String BIG_DECIMAL = “BigDecimal”; public static final String BIG_INTEGER = “BigInteger”;}getBean加载上下文文件首先需要一个构造器,形参时文件的名字;getBean方法,形参是某个bean的id名字,这样,根据当前bean的自动装配类型,来调用响应的方法。 /* * Created By zby on 11:17 2019/2/14 * 类的上下文加载顺序 /public class ClassPathXmlApplicationContext {private static Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class.getName());private String configXml;public ClassPathXmlApplicationContext(String configXml) { this.configXml = configXml;}/* * Created By zby on 18:38 2019/2/24 * bean对应的id的名称 /public Object getBean(String name) { String dirPath="../simulaspring/src/main/resources/"; Map<String, Map<String, XmlConfigBean>> allXmls = LoadConfig.loadXmlConFig(dirPath).getAllXmls(); boolean contaninsKey = MapUtil.findKey(allXmls, configXml); if (!contaninsKey) { throw new RuntimeException(“配置文件不存在” + configXml); } Map<String, XmlConfigBean> beans = allXmls.get(configXml); contaninsKey = MapUtil.findKey(beans, name); if (!contaninsKey) { throw new RuntimeException(“id为” + name + “bean不存在”); } XmlConfigBean configFile = beans.get(name); if (null == configFile) { throw new RuntimeException(“id为” + name + “bean不存在”); } String classPath = configFile.getClazz(); if (StringUtils.isBlank(classPath)) { throw new RuntimeException(“id为” + name + “类型不存在”); } String autowire = configFile.getAutowire(); if (StringUtils.isBlank(autowire)) { return getBeanWithoutArgs(beans, classPath, configFile); } else { switch (autowire) { case AutowireType.BY_NAME: return getBeanByName(beans, classPath, configFile); case AutowireType.CONSTRUCTOR: return getBeanByConstruct(classPath, configFile); case AutowireType.AUTODETECT: return getByAutodetect(beans, classPath, configFile); case AutowireType.BY_TYPE: return getByType(beans, classPath, configFile); } } return null; }}下面主要讲解默认自动装配、属性自动装配、构造器自动装配默认自动装配如果我们没有填写自动装配的类型,其就采用ref来自动(手动)装配对象。 /* * Created By zby on 18:33 2019/2/24 * 在没有设置自动装配时,通过ref对象 /private Object getBeanWithoutArgs(Map<String, XmlConfigBean> beans, String classPath, XmlConfigBean configFile) {//属性名称String proName = null;try { Class currentClass = Class.forName(classPath); //通过引用 ref 创建对象 Set<XmlBeanProperty> properties = configFile.getProperties(); //如果没有属性,就返回,便于下面的递归操作 if (CollectionUtil.isEmpty(properties)) { return currentClass.newInstance(); } Class<?> superClass = currentClass.getSuperclass(); //TODO 父类的集合// List<Class> superClasses = null; //在创建子类构造器之前,创建父类构造器, // 父类构造器的参数子类构造器的参数 Object currentObj = null; //当前构造器 Object consArgsObj = null; String consArgsName = null; boolean hasSuperClass = (null != superClass && !superClass.getSimpleName().equals(“Object”)); if (hasSuperClass) { Constructor[] constructors = currentClass.getDeclaredConstructors(); ArrayUtil.validateArray(superClass, constructors); Parameter[] parameters = constructors[0].getParameters(); if (parameters == null || parameters.length == 0) { consArgsObj = constructors[0].newInstance(); } else { ArrayUtil.validateArray(superClass, parameters); consArgsName = parameters[0].getType().getSimpleName(); //配置文件大类型,与参数构造器的类型是否相同 for (XmlBeanProperty property : properties) { String ref = property.getRef(); if (StringUtils.isNotBlank(ref) && ref.equalsIgnoreCase(consArgsName)) { classPath = beans.get(ref).getClazz(); Class<?> clazz = Class.forName(classPath); consArgsObj = clazz.newInstance(); } } currentObj = constructors[0].newInstance(consArgsObj); } } else { currentObj = currentClass.newInstance(); } for (XmlBeanProperty property : properties) { //这里适合用递归,无限调用自身 //通过name找到属性,配置文件中是否有该属性,通过ref找到其对应的bean文件 proName = property.getName(); Field field = currentClass.getDeclaredField(proName); if (null != field) { String ref = property.getRef(); Object value = property.getValue(); //如果没有赋初值,就通过类型创建 if (null == value && StringUtils.isNotBlank(ref)) { boolean flag = StringUtils.isNotBlank(consArgsName) && null != consArgsObj && consArgsName.equalsIgnoreCase(ref); //递归调用获取属性对象 value = flag ? consArgsObj : getBean(ref); } field.setAccessible(true); Transfomer.transformerPropertyValue(currentObj, field, value); } } return currentObj;} catch (ClassNotFoundException e) { logger.error(“名为” + classPath + “类不存在”); e.printStackTrace();} catch (InstantiationException e) { e.printStackTrace();} catch (IllegalAccessException e) { e.printStackTrace();} catch (InvocationTargetException e) { e.printStackTrace();} catch (NoSuchFieldException e) { logger.error(classPath + “类的属性” + proName + “不存在”); throw new RuntimeException(classPath + “类的属性” + proName + “不存在”);}return null;}构造器创建对象根据构造器constructor创建对象/* * Created By zby on 23:06 2019/3/2 * * @param classPath 类路径 * @param configFile 配置文件 /private Object getBeanByConstruct(String classPath, XmlConfigBean configFile) { try { Class currentClass = Class.forName(classPath); Set<XmlBeanProperty> properties = configFile.getProperties(); if (CollectionUtil.isEmpty(properties)) { return currentClass.newInstance(); } ///构造器参数类型和构造器对象集合 Object[] objects = new Object[properties.size()]; Class<?>[] paramType = new Class[properties.size()]; Field[] fields = currentClass.getDeclaredFields(); int i = 0; for (Iterator iterator = properties.iterator(); iterator.hasNext(); i++) { XmlBeanProperty property = (XmlBeanProperty) iterator.next(); String proName = property.getName(); String ref = property.getRef(); Object value = property.getValue(); for (Field field : fields) { Class<?> type = field.getType(); String typeName = type.getSimpleName(); String paramName = field.getName(); if (paramName.equals(proName) && ObjectUtil.isNotNull(value) && StringUtils.isBlank(ref)) { objects[i] = Transfomer.transformerPropertyValue(typeName, value); paramType[i] = type; break; } else if (paramName.equals(proName) && StringUtils.isNotBlank(ref) && ObjectUtil.isNull(value)) { objects[i] = getBean(ref); paramType[i] = type; break; } } } return currentClass.getConstructor(paramType).newInstance(objects); } catch (ClassNotFoundException e) { logger.error(“名为” + classPath + “类不存在”); e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null;}属性自动装配根据属性名事项自动装配,如果一个bean的名称和其他bean属性的名称是一样的,将会自装配它。 /* * Created By zby on 21:16 2019/3/1 * 根据属性名事项自动装配, * @param classPath 类路径 * @param configFile 配置文件 */private Object getBeanByName( String classPath, XmlConfigBean configFile) { String proName = null; try { Class currentClass = Class.forName(classPath); Class superclass = currentClass.getSuperclass(); Method[] methods = currentClass.getDeclaredMethods(); List<Method> methodList = MethodHelper.filterSetMethods(methods); Object currentObj = currentClass.newInstance(); Set<XmlBeanProperty> properties = configFile.getProperties(); //配置文件中,但是有父类, if (CollectionUtil.isEmpty(properties)) { boolean isExit = null != superclass && !superclass.getSimpleName().equals(“Object”); if (isExit) { Field[] parentFields = superclass.getDeclaredFields(); if (ArrayUtil.isNotEmpty(parentFields)) { if (CollectionUtil.isNotEmpty(methodList)) { for (Field parentField : parentFields) { for (Method method : methodList) { if (MethodHelper.methodNameToProName(method.getName()).equals(parentField.getName())) { //如果有泛型的话 Type genericType = currentClass.getGenericSuperclass(); if (null != genericType) { String genericName = genericType.getTypeName(); genericName = StringUtils.substring(genericName, genericName.indexOf("<") + 1, genericName.indexOf(">")); Class genericClass = Class.forName(genericName); method.setAccessible(true); method.invoke(currentObj, genericClass); } break; } } break; } } } } return currentObj; } //传递给父级对象 service – 》value List<Method> tmpList = new ArrayList<>(); Map<String, Object> map = new HashMap<>(); Object value = null; for (XmlBeanProperty property : properties) { proName = property.getName(); if (ArrayUtil.isNotEmpty(methods)) { String ref = property.getRef(); value = property.getValue(); for (Method method : methodList) { String methodName = MethodHelper.methodNameToProName(method.getName()); Field field = currentClass.getDeclaredField(methodName); if (methodName.equals(proName) && null != field) { if (null == value && StringUtils.isNotBlank(ref)) { value = getBean(ref); } else if (value != null && StringUtils.isBlank(ref)) { value = Transfomer.transformerPropertyValue(field.getType().getSimpleName(), value); } method.setAccessible(true); method.invoke(currentObj, value); map.put(proName, value); tmpList.add(method); break; } } } } tmpList = MethodHelper.removeMethod(methodList, tmpList); for (Method method : tmpList) { Class<?>[] type = method.getParameterTypes(); if (ArrayUtil.isEmpty(type)) { throw new RuntimeException(“传递给父级对象的参数为空type=” + type); } for (Class<?> aClass : type) { String superName = ClassHelper.classNameToProName(aClass.getSimpleName()); value = map.get(superName); method.setAccessible(true); method.invoke(currentObj, value); } } return currentObj; } catch (ClassNotFoundException e) { logger.error(“名为” + classPath + “类不存在”); e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { logger.error(“类” + classPath + “属性” + proName + “不存在”); e.printStackTrace(); } return null;}总结这里没有使用注解,我们可以使用注解的方式实现自动装配,但这不spring的核心,应该时spring的美化,核心值如何实现自动装配。 ...

March 17, 2019 · 10 min · jiezi

模仿hibernate框架,详解hibernate部分方法设计

导读源码地址公司的持久层采用的hibernate框架,这也是很多公司使用的一种持久层框架。它将瞬时态的数据转化为持久态、或将持久态的数据转化为瞬时态数据。我比较喜欢看源码,看别人的架构思想,因为,笔者想向架构师的方向进发。看了别人的源码,突然想模拟hibernate框架,自己写个框架出来。 这里去除了hibernate框架晦涩的地方,当做自己学习材料还是不错的。里面涉及到反射、连接池等等。 这个项目中,你可以知道数据库连接池是怎么建的,又是怎么回收的。 使用警惕代码块加载配置文件以下详细介绍我个人的项目,但肯定没有人家源码写得好,这里仅作为学习使用。如果不懂的,可以私信我。配置文件本项目以idea为开发环境和以maven搭建的,分为java包和test包。java包的配置文件放在resources下,代码放在com.zby.simulationHibernate包下,如下是配置文件:连接池我们在使用hibernate时,一般会配置连接池,比如,初始化连接数是多少,最大连接数是多少?这个连接的是什么?我们在启动项目时,hibernate根据初始的连接数,来创建多少个数据库连接对象,也就是jdbc中的Connection对象。为什么要有这个连接池?因为,每次开启一个连接和关闭一个连接都是消耗资源的,我们开启了这些连接对象之后,把它们放在一个容器中,我们何时需要何时从容器中取出来。当不需要的时候,再将踏进放回到容器中。因而,可以减少占用的资源。如下,是初始化的连接对象:package com.zby.simulationHibernate.util.factory;import com.zby.simulationHibernate.util.exception.GenericException;import org.apache.commons.lang3.StringUtils;import java.io.IOException;import java.io.InputStream;import java.sql.DriverManager;import java.sql.SQLException;import java.util.Properties;/** * Created By zby on 21:23 2019/1/23 * 数据库的连接 /public class Connect { /* * 连接池的初始值 / private static int initPoolSize = 20; /* * 创建property的配置文件 / protected static Properties properties; /* * 连接池的最小值 / protected static int minPoolSize; /* * 连接池的最大值 / protected static int maxPoolSize; //【2】静态代码块 static { //加载配置文件 properties = new Properties(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(“db.properties”); try { properties.load(is); minPoolSize = Integer.valueOf(properties.getProperty(“jdbc.minConnPool”)); if (minPoolSize <= initPoolSize) minPoolSize = initPoolSize; maxPoolSize = Integer.valueOf(properties.getProperty(“jdbc.maxConnPool”)); if (minPoolSize > maxPoolSize) throw new GenericException(“连接池的最小连接数不能大于最大连接数”); } catch (IOException e) { System.out.println(“未找到配置文件”); e.printStackTrace(); } } /* * Created By zby on 16:50 2019/1/23 * 获取数据连接 / protected java.sql.Connection createConnect() { String driverName = properties.getProperty(“jdbc.driver”); if (StringUtils.isEmpty(driverName)) { driverName = “com.mysql.jdbc.Driver”; } String userName = properties.getProperty(“jdbc.username”); String password = properties.getProperty(“jdbc.password”); String dbUrl = properties.getProperty(“jdbc.url”); try { Class.forName(driverName); return DriverManager.getConnection(dbUrl, userName, password); } catch (ClassNotFoundException e) { System.out.println(“找不到驱动类”); e.printStackTrace(); } catch (SQLException e) { System.out.println(“加载异常”); e.printStackTrace(); } return null; }}创建Session会话我们在使用hibernate时,不是直接使用连接对象,而是,以会话的方式创建一个连接。创建会话的方式有两种。一种是openSession,这种是手动提交事务。getCurrentSession是自动提交事务。如代码所示:package com.zby.simulationHibernate.util.factory;import java.sql.Connection;import java.util.ArrayList;import java.util.Iterator;import java.util.List;/* * Created By zby on 15:43 2019/1/23 /public class SqlSessionFactory implements SessionFactory { /* * 连接池 / private static List<Connection> connections; /* * 连接对象 * * @return / private static Connect connect = new Connect(); protected static List<Connection> getConnections() { return connections; } //静态代码块,初始化常量池 static { connections = new ArrayList<>(); Connection connection; for (int i = 0; i < Connect.minPoolSize; i++) { connection = connect.createConnect(); connections.add(connection); } } @Override public Session openSession() { return getSession(false); } @Override public Session getCurrentSession() { return getSession(true); } /* * 获取session * * @param autoCommit 是否自动提交事务 * @return / private Session getSession(boolean autoCommit) { //【1】判断连接池有可用的连接对象 boolean hasNoValidConn = hasValidConnction(); //【2】没有可用的连接池,使用最大的连接池 if (!hasNoValidConn) { for (int i = 0; i < (Connect.maxPoolSize - Connect.minPoolSize); i++) { connections.add(connect.createConnect()); } } //【3】有可用的连接 for (Iterator iterator = connections.iterator(); iterator.hasNext(); ) { Connection connection = null; try { connection = (Connection) iterator.next(); connection.setAutoCommit(autoCommit); Session session = new Session(connection); iterator.remove(); return session; } catch (Exception e) { e.printStackTrace(); } } return null; } /* * Created By zby on 21:50 2019/1/23 * 当我们没开启一个连接,连接池的数目减少1,直到连接池的数量为0 / private boolean hasValidConnction() { return null != connections && connections.size() != 0; }}数据查找我们既然使用这个框架,必然要有数据查找的功能。返回结果分为两种,一种是以实体类直接返回,调用AddEntity方法。但是,有时时多张表查询的结果,这种情况下,直接以实体类肯定不可以的,因而,我们需要使用自定义接收对象,并将查找结果进行过滤,再封装成我们想要的对象。第一种,以实体类返回/* * Created By zby on 23:19 2019/1/23 * 体检反射的实体类 /public SqlQuery addEntity(Class<T> persistenceClass) { this.persistenceClass = persistenceClass; return this;}第二种,过滤后返回数据 /* * Created By zby on 19:18 2019/1/27 * 创建类型 /public SqlQuery addScalar(String tuple, String alias) { if (CommonUtil.isNull(aliasMap)) { aliasMap = new HashMap<>(); } for (Map.Entry<String, String> entry : aliasMap.entrySet()) { String key = entry.getKey(); if (key.equals(tuple)) throw new GenericException(“alias已经存在,即alias=” + key); String value = aliasMap.get(key); if (value.equals(alias) && key.equals(tuple)) throw new GenericException(“当前alias的type已经存在,alias=” + key + “,type=” + value); } aliasMap.put(tuple, alias); return this;}/* * Created By zby on 9:20 2019/1/28 * 数据转换问题 /public SqlQuery setTransformer(ResultTransformer transformer) { if (CommonUtil.isNull(aliasMap)) { throw new IllegalArgumentException(“请添加转换的属性数量”); } transformer.transformTuple(aliasMap); this.transformer = transformer; return this;}以集合的方式返回数据:/* * Created By zby on 17:02 2019/1/29 * 设置查找参数 /public SqlQuery setParamter(int start, Object param) { if (CommonUtil.isNull(columnParamer)) columnParamer = new HashMap<>(); columnParamer.put(start, param); return this;}/* * Created By zby on 16:41 2019/1/24 * 查找值 /public List<T> list() { PreparedStatement statement = null; ResultSet resultSet = null; try { statement = connection.prepareStatement(sql); if (CommonUtil.isNotNull(columnParamer)) { for (Map.Entry<Integer, Object> entry : columnParamer.entrySet()) { int key = entry.getKey(); Object value = entry.getValue(); statement.setObject(key + 1, value); } } resultSet = statement.executeQuery(); PersistentObject persistentObject = new PersistentObject(persistenceClass, resultSet); if (CommonUtil.isNotNull(aliasMap)) return persistentObject.getPersist(transformer); return persistentObject.getPersist(); } catch (Exception e) { e.printStackTrace(); } finally { SessionClose.closeConnStateResSet(connection, statement, resultSet); } return null;}返回唯一值/* * Created By zby on 16:41 2019/1/24 * 查找值 /public T uniqueResult() { List<T> list = list(); if (CommonUtil.isNull(list)) return null; if (list.size() > 1) throw new GenericException(“本来需要返回一个对象,却返回 " + list.size() + “个对象”); return list.get(0);}测试 @Test public void testList() { Session session = new SqlSessionFactory().openSession(); String sql = “SELECT " + " customer_name AS customerName, " + " name AS projectName " + “FROM " + " project where id >= ? and id <= ?”; SqlQuery query = session.createSqlQuery(sql); query.setParamter(0, 1); query.setParamter(1, 2); query.addScalar(“customerName”, StandardBasicTypes.STRING) .addScalar(“projectName”, StandardBasicTypes.STRING); query.setTransformer(Transforms.aliasToBean(ProjectData.class)); List<ProjectData> projects = query.list(); for (ProjectData project : projects) { System.out.println(project.getCustomerName() + " " + project.getProjectName()); } } @Ignore public void testListNoData() { Session session = new SqlSessionFactory().openSession(); String sql = “SELECT " + " customer_name AS customerName, " + " name AS projectName " + “FROM " + " project where id >= ? and id <= ?”; SqlQuery query = session.createSqlQuery(sql). setParamter(0, 1). setParamter(1, 2). addEntity(Project.class); List<Project> projects = query.list(); for (Project project : projects) { System.out.println(project.getCustomerName() + " " + project.getGuestCost()); } }保存数据我们这里以 merger来保存数据,因为这个方法非常的特殊。如果该瞬时态的独享有主键,而且,其在数据库中依旧存在该主键的数据,我们此时就更新数据表。如果数据表中没有当前主键的数据,我们向数据库中添加该对象的值。如果该瞬时态的对象没有主键,我们直接在数据表中添加该对象。如代码所示: /* * Created By zby on 15:41 2019/1/29 * 合并,首先判断id是否存在,若id存在则更新,若id不存在,则保存数据 /public T merge(T t) { if (CommonUtil.isNull(t)) throw new IllegalArgumentException(“参数为空”); Class<T> clazz = (Class<T>) t.getClass(); Field[] fields = clazz.getDeclaredFields(); boolean isContainsId = CommonUtil.isNotNull(PropertyUtil.containId(fields)) ? true : false; long id = PropertyUtil.getIdValue(fields, t, propertyAccessor); if (isContainsId) { return id > 0L ? update(t) : save(t); } return save(t);} /* * Created By zby on 17:37 2019/1/29 * 保存数据 /public T save(T t) { if (CommonUtil.isNull(t)) throw new RuntimeException(“不能保存空对象”); PreparedStatement statement = null; ResultSet resultSet = null; StringBuilder columnJoint = new StringBuilder(); StringBuilder columnValue = new StringBuilder(); try { Field[] fields = t.getClass().getDeclaredFields(); String sql = " insert into " + ClassUtil.getClassNameByGenericity(t) + “(”; for (int i = 0; i < fields.length; i++) { String propertyName = fields[i].getName(); Object propertyValue = propertyAccessor.getPropertyValue(t, propertyName); if (CommonUtil.isNotNull(propertyValue)) { String columnName = PropertyUtil.propertyNameTransformColumnName(propertyName, true); if (StandardBasicTypes.BOOLEAN.equalsIgnoreCase(fields[i].getGenericType().toString())) { columnJoint.append(“is_” + columnName + “,”); columnValue.append(propertyValue + “,”); } else if (StandardBasicTypes.LONG.equalsIgnoreCase(fields[i].getGenericType().toString()) || StandardBasicTypes.FLOAT.equalsIgnoreCase(fields[i].getGenericType().toString()) || StandardBasicTypes.DOUBLE.equalsIgnoreCase(fields[i].getGenericType().toString()) || StandardBasicTypes.INTEGER.equalsIgnoreCase(fields[i].getGenericType().toString())) { columnJoint.append(columnName + “,”); columnValue.append(propertyValue + “,”); } else if (StandardBasicTypes.DATE.equalsIgnoreCase(fields[i].getGenericType().toString())) { columnJoint.append(columnName + “,”); columnValue.append(”’” + DateUtil.SIMPLE_DATE_FORMAT.format((Date) propertyValue) + “’,”); } else { columnJoint.append(columnName + “,”); columnValue.append(”’" + propertyValue + “’,”); } } } columnJoint = StringUtil.replace(columnJoint, “,”); columnValue = StringUtil.replace(columnValue, “,”); sql += columnJoint + “) VALUES(” + columnValue + “)”; System.out.println(sql); statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); statement.executeUpdate(); resultSet = statement.getGeneratedKeys(); while (resultSet.next()) { return load((Class<T>) t.getClass(), resultSet.getLong(1)); } return t; } catch (SQLException e) { System.out.println(“保存数据出错,实体对象为=” + t); e.printStackTrace(); } finally { SessionClose.closeConnStateResSet(connection, statement, resultSet); } return null;}测试代码: @Testpublic void testSave() { Session session = new SqlSessionFactory().getCurrentSession(); Project project = new Project(); project.setCustomerName(“hhhh”); project.setCreateDatetime(new Date()); project.setDeleted(true); project = (Project) session.save(project); System.out.println(project.getId());}通过id加载对象有时,我们只要根据当前对象的id,获取当前对象的全部信息,因而,我们可以这样写: /* * Created By zby on 16:36 2019/1/29 * 通过id获取对象 /public T load(Class<T> clazz, Long id) { if (CommonUtil.isNull(clazz)) throw new IllegalArgumentException(“参数为空”); String className = ClassUtil.getClassNameByClass(clazz); String sql = " select * from " + className + " where id= ? “; SqlQuery query = createSqlQuery(sql) .setParamter(0, id) .addEntity(clazz); return (T) query.uniqueResult();}测试代码:@Testpublic void testload() { Session session = new SqlSessionFactory().openSession(); Project project = (Project) session.load(Project.class, 4L); System.out.println(project);}回收连接对象当我们使用完该连接对象后,需要将对象放回到容器中,而不是直接调用connection.close()方法,而是调用这个方法: /* * Created By zby on 16:10 2019/3/17 * 获取容器的对象,如果是关闭session,则将连接对象放回到容器中 * 如果是开启session,则从容器中删除该连接对象 /protected static List<Connection> getConnections() { return connections;} /* * Created By zby on 22:45 2019/1/23 * <p> * 当关闭当前会话时,这并非真正的关闭会话 * 只是将连接对象放回到连接池中 */public static void closeConn(Connection connection) { SqlSessionFactory.getConnections().add(connection);}总结写框架其实是不难的,难就难在如何设计框架。或者说,难就难在基础不牢。如果基础打不牢的话,很难网上攀升。 ...

March 17, 2019 · 6 min · jiezi

框架与RTTI的关系,RTTI与反射之间的关系

导读在之后的几篇文章,我会讲解我自己的hibernate、spring、beanutils框架,但讲解这些框架之前,我需要讲解RTTI和反射。工作将近一年了,我们公司项目所使用的框架是SSH,或者,其他公司使用的是SSM框架。不管是什么样的框架,其都涉及到反射。那么,什么是反射?我们在生成对象时,事先并不知道生成哪种类型的对象,只有等到项目运行起来,框架根据我们的传参,才生成我们想要的对象。比如,我们从前端调用后端的接口,查询出这个人的所有项目,我们只要传递这个人的id即可。当然,数据来源于数据库,那么,问题来了,数据是怎么从持久态转化成我们想要的顺时态的?这里面,就涉及到了反射。但是,一提到反射,我们势必就提到RTTI,即运行时类型信息(runtime Type Infomation)。RTTIpo类/** * Created By zby on 16:53 2019/3/16 /@AllArgsConstructor@NoArgsConstructorpublic class Pet { private String name; private String food; public void setName(String name) { this.name = name; } public void setFood(String food) { this.food = food; } public String getName() { return name; } public String getFood() { return food; }}/* * Created By zby on 17:03 2019/3/16 /public class Cat extends Pet{ @Override public void setFood(String food) { super.setFood(food); }}/* * Created By zby on 17:04 2019/3/16 /public class Garfield extends Cat{ @Override public void setFood(String food) { super.setFood(food); }}/* * Created By zby on 17:01 2019/3/16 /public class Dog extends Pet{ @Override public void setFood(String food) { super.setFood(food); }}以上是用来说明的persistent object类,也就是,我们在进行pojo常用的javabean类。其有继承关系,如下图:展示信息如一下代码所示,方法eatWhatToday有两个参数,这两个参数一个是接口类,一个是父类,也就是说,我们并不知道打印出的是什么信息。只有根据接口的实现类来和父类的子类,来确认打印出的信息。这就是我们输的运行时类型信息,正因为有了RTTI,java才有了动态绑定的概念。/* * Created By zby on 17:05 2019/3/16 /public class FeedingPet { /* * Created By zby on 17:05 2019/3/16 * 某种动物今天吃的是什么 * * @param baseEnum 枚举类型 这里表示的时间 * @param pet 宠物 / public static void eatWhatToday(BaseEnum baseEnum, Pet pet) { System.out.println( pet.getName() + “今天” + baseEnum.getTitle() + “吃的” + pet.getFood()); } }测试类 @Testpublic void testPet(){ Dog dog=new Dog(); dog.setName(“宠物狗京巴”); dog.setFood(FoodTypeEnum.FOOD_TYPE_BONE.getTitle()); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MORNING,dog); Garfield garfield=new Garfield(); garfield.setName(“宠物猫加菲猫”); garfield.setFood(FoodTypeEnum.FOOD_TYPE_CURRY.getTitle()); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,garfield);}打印出的信息为:那么,这和反射有什么关系呢?反射获取当前类信息正如上文提到的运行时类型信息,那么,类型信息在运行时是如何表示的?此时,我们就想到了Class这个特殊对象。见名知其意,即类对象,其包含了类的所有信息,包括属性、方法、构造器。我们都知道,类是程序的一部分,每个类都有一个Class对象。每当编写并且执行了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行当前程序的jvm将使用到类加载器。jvm首先调用bootstrap类加载器,加载核心文件,jdk的核心文件,比如Object,System等类文件。然后调用plateform加载器,加载一些与文件相关的类,比如压缩文件的类,图片的类等等。最后,才用applicationClassLoader,加载用户自定义的类。加载当前类信息反射正式利用了Class来创建、修改对象,获取和修改属性的值等等。那么,反射是怎么创建当前类的呢?第一种,可以使用当前上下文的类路径来创建对象,如我们记载jdbc类驱动的时候,如以下代码:/* * Created By zby on 18:07 2019/3/16 * 通过上下文的类路径来加载信息 /public static Class byClassPath(String classPath) { if (StringUtils.isBlank(classPath)) { throw new RuntimeException(“类路径不能为空”); } classPath = classPath.replace(" “, “”); try { return Class.forName(classPath); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null;}第二种,通过类字面常量,这种做法非常简单,而且更安全。因为,他在编译时就会受到检查,我们不需要将其置于try catch的代码快中,而且,它根除了对forName的方法调用,所以,更高效。这种是spring、hibernate等主流框架使用的。框架hibernate的内部使用类字面常量去创建对象后,底层通过jdbc获取数据表的字段值,根据数据表的字段与当前类的属性进行一一匹配,将字段值填充到当前对象中。匹配不成功,就会报出相应的错误。类字面常量获取对象信息,如代码所示。下文,也是通过类字面常量创建对象。 /* * Created By zby on 18:16 2019/3/16 * 通过类字面常量加载当前类的信息 /public static void byClassConstant() { System.out.println(Dog.class);}第三种,是通过对象来创建当前类,这种会在框架内部使用。/** Created By zby on 18:17 2019/3/16* 通过类对象加载当前类的信息*/public static Class byCurrentObject(Object object) { return object.getClass();}反射创建当前类对象我们创建当前对象,一般有两种方式,一种是通过clazz.newInstance();这种一般是无参构造器,并且创建对对象后,可以获取其属性,通过属性赋值和方法赋值,如如代码所示:第一种,通过clazz.newInstance()创建对象/** * Created By zby on 18:26 2019/3/16 * 普通的方式创建对象 /public static <T> T byCommonGeneric(Class clazz, String name, BaseEnum baseEnum) { if (null == clazz) { return null; } try { T t = (T) clazz.newInstance(); //通过属性赋值,getField获取公有属性,获取私有属性 Field field = clazz.getDeclaredField(“name”); //跳过检查,否则,我们没办法操作私有属性 field.setAccessible(true); field.set(t, name); //通过方法赋值 Method method1 = clazz.getDeclaredMethod(“setFood”, String.class); method1.setAccessible(true); method1.invoke(t, baseEnum.getTitle()); return t; } catch (Exception e) { e.printStackTrace(); } return null;}测试: @Testpublic void testCommonGeneric() { Dog dog= GenericCurrentObject.byCommonGeneric(Dog.class, “宠物狗哈士奇”, FoodTypeEnum.FOOD_TYPE_BONE); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_NOON,dog);}叔叔出结果为:你会发现一个神奇的地方,就是名字没有输出来,但我们写了名字呀,为什么没有输出来?因为,dog是继承了父类Pet,当我们在创建子类对象时,首先,会加载父类未加载的构造器、静态代码块、静态属性、静态方法等等。但是,Dog在这里是以无参构造器加载的,当然,同时也通过无参构造器的实例化了父类。我们在给dog对象的name赋值时,、并没有给父类对象的name赋值,所以,dog的name是没有值的。父类引用指向子类对象,就是这个意思。如果我们把Dog类中的 @Override public void setFood(String food) {super.setFood(food); }的super.setFood(food); 方法去掉,属性food也是没有值的。如图所示:通过构造器创建对象 /* * Created By zby on 18:26 2019/3/16 * 普通的方式创建对象 */ public static <T> T byConstruct(Class clazz, String name, BaseEnum baseEnum) { if (null == clazz) { return null; }// 参数类型, Class paramType[] = {String.class, String.class}; try {// 一般情况下,构造器不止一个,我们根据构器的参数类型,来使用构造器创建对象 Constructor constructor = clazz.getConstructor(paramType);// 给构造器赋值,赋值个数和构造器的形参个数一样,否则,会报错 return (T) constructor.newInstance(name, baseEnum.getTitle()); } catch (Exception e) { e.printStackTrace(); } return null; } 测试: @Test public void testConstruct() { Dog dog= GenericCurrentObject.byConstruct(Dog.class, “宠物狗哈士奇”, FoodTypeEnum.FOOD_TYPE_BONE); System.out.println(“输出宠物的名字:"+dog.getName()+"\n”); System.out.println(“宠物吃的什么:"+dog.getFood()+"\n”); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,dog); }测试结果:这是通过构造器创建的对象。但是注意的是,形参类型和和参数值的位数一定要相等,否则,就会报出错误的。总结为什么写这篇文章,前面也说了,很多框架都用到了反射和RTTI。但是,我们的平常的工作,一般以业务为主。往往都是使用别人封装好的框架,比如spring、hibernate、mybatis、beanutils等框架。所以,我们不大会关注反射,但是,你如果想要往更高的方向去攀登,还是要把基础给打捞。否则,基础不稳,爬得越高,摔得越重。我会以后的篇章中,通过介绍我写的spring、hibernate框架,来讲解更好地讲解反射。 ...

March 16, 2019 · 2 min · jiezi

关于Luthier CI

欢迎关于Luthier CILuthier CI是CodeIgniter的一个插件,增加了有趣的功能,旨在简化大型网站和API的构建。它是为了尽可能地与框架集成,因此在安装Luthier CI后,应用程序中已存在的所有内容应该继续正常工作。本文档假定您具有有关CodeIgniter的基本知识。如果您从未使用过CodeIgniter,那么他们的官方文档就是一个很好的起点Luthier CI是免费软件,可在MIT许可下使用。特征改进了路由 ( Improved routing )Luthier CI通过受Laravel启发的语法取代了在应用程序中定义路由的方式。例如,而不是定义类似于此的大量路由:$route[‘catalog/cars/(:any)’][‘GET’] = ‘CarsController/catalog/$1’;$route[‘catalog/cars/(:any)/(:any)’][‘GET’] = ‘CarsController/catalog/$1/$2’;$route[‘catalog/bikes/(:any)’][‘GET’] = ‘BikesController/catalog/$1’;$route[‘catalog/bikes/(:any)’][‘POST’] = ‘BikesController/catalog/$1’;$route[‘catalog/bikes/(:any)/(:any)’][‘GET’] = ‘BikesController/catalog/$1/$2’;$route[‘catalog/bikes/(:any)/(:any)’][‘POST’] = ‘BikesController/catalog/$1/$2’;$route[‘catalog/airplanes/(:any)’][‘GET’] = ‘AirplanesController/catalog/$1/$2’;$route[‘catalog/airplanes/(:any)/(:any)’][‘GET’] = ‘AirplanesController/catalog/$1/$2’;…你可以用更紧凑的方式编写它:Route::group(‘catalog’, function(){ Route::get(‘cars/{category_id}/{filter_by?}’, ‘CarsController@catalog’); Route::match([‘get’,‘post’], ‘bikes/{category_id}/{filter_by?}’, ‘BikesController@catalog’); Route::get(‘airplanes/{category_id}/{filter_by?}’, ‘AirplanesController@catalog’);});此外,Luthier CI可以帮助您保持路由的有序性,因为每种类型的路由都有自己的文件,必须定义它:HTTP路由有一个文件,AJAX路由有另一个文件,CLI路由有另一个文件。中间件 ( Middleware )Luthier CI 在框架中引入了中间件的概念。正确使用,中间件可以帮助您在控制器上创建过滤器和操作,否则,使用库和帮助程序实现将非常繁琐。您可以在特定路由和路由组中使用中间件,甚至可以在应用程序中全局使用。简易安装Luthier CI通过Composer安装,并使用CodeIgniter 挂钩集成到您的应用程序中。忘记复制或移动文件或遵循大量的步骤以使Luthier CI工作。在大多数情况下,安装不会超过5分钟!社区和支持要报告错误并提出更改,请访问Github上的Luthier CI repository on Github存储库

March 4, 2019 · 1 min · jiezi

vue.js框架原理浅析

vue.js是一个非常优秀的前端开发框架,不是我说的,大家都知道。首先我现在的能力,独立阅读源码还是有很大压力的,所幸vue写的很规范,通过方法名基本可以略知一二,里面的原理不懂的地方多方面查找资料,本文中不规范不正确的地方欢迎指正,学生非常愿意接受各位前辈提出宝贵的建议和指导。使用vue的版本是v2.5.13,采用了flow作为类型管理工具,关于flow相关内容选择性忽略了,不考虑类型系统,只考虑实现原理,写下这篇文章。本文大概涉及到vue几个核心的地方:vue实例化,虚拟DOM,模板编译过程,数据绑定。一、vue的生命周期二、vue实例化研究vue的实例化就要研究_init方法,此方法定义在src/core/instance/init.js下的initMixin中,里面是对vue实例即vm的处理。其中包括开发环境下的代理配置等一些列处理,并处理了传递给构造函数的参数等,重点在一系列方法 initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, ‘beforeCreate’) initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, ‘created’)初始化生命周期,初始化事件,初始化渲染,触发执行beforeCreate生命周期方法,初始化data/props数据监听,触发执行created生命周期方法。对应到生命周期示例图,created方法执行结束,接下来判断是否传入挂载的el节点,如果传入的话此时就会通过$mount函数把组件挂载到DOM上面,整个vue构造函数就执行完成了。以上是vue对象创建的基本流程。三、模板编译挂载的$mount函数,此函数的实现与运行环境有关,在此只看web中的实现。实现只有简单的两行,1、判断运行环境为浏览器,2、调用工具方法查找到el对应的DOM节点,3、mountComponent方法来实现挂载,这里就涉及到了挂载之前的处理问题。1、对于拥有render(JSX)函数的情况,组件可以直接挂载,2、如果使用的是template,需要从中提取AST渲染方法(注意如果使用构建工具,最终会为我们编译成render(JSX)形式,所以无需担心性能问题),AST即抽象语法树,它是对真实DOM结构的映射,可执行,可编译,能够把每个节点部分都编译成vnode,组成一个有对应层次结构的vnode对象。有了渲染方法,下一步就是更新DOM,注意并不是直接更新,而是通过vnode,于是涉及到了一个非常重要的概念。四、虚拟dom虚拟DOM技术是一个很流行的东西,现代前端开发框架vue和react都是基于虚拟DOM来实现的。虚拟DOM技术是为了解决一个很重要的问题:浏览器进行DOM操作会带来较大的开销。1、要知道js本身运行速度是很快的,2、而js对象又可以很准确地描述出类似DOM的树形结构,基于这两点前提,人们研究出一种方式,通过使用js描述出一个假的DOM结构,每次数据变化时候,在假的DOM上分析数据变化前后结构差别,找出这个最小差别并且在真实DOM上只更新这个最小的变化内容,这样就极大程度上降低了对DOM的操作带来的性能开销。上面的假的DOM结构就是虚拟DOM,比对的算法成为diff算法,这是实现虚拟DOM技术的关键。1、在vue初始化时,首先用JS对象描述出DOM树的结构,2、用这个描述树去构建真实DOM,并实际展现到页面中,3、一旦有数据状态变更,需要重新构建一个新的JS的DOM树,4、对比两棵树差别,找出最小更新内容,5、并将最小差异内容更新到真实DOM上。有了虚拟DOM,下面一个问题就是,什么时候会触发更新,接下来要介绍的,就是vue中最具特色的功能–数据响应系统及实现。五、数据绑定vue.js的作者尤雨溪老师在知乎上一个回答中提到过自己创作vue的过程,最初就是尝试实现一个类似angular1的东西,发现里面对于数据处理非常不优雅,于是创造性的尝试利用ES5中的Object.defineProperty来实现数据绑定,于是就有了最初的vue。vue中响应式的数据处理方式是一项很有价值的东西。vue官网上面其实有具体介绍,下面是一张官方图片:vue响应响应实现的基本原理:1、vue会遍历此data中对象所有的属性,2、并使用Object.defineProperty把这些属性全部转为getter/setter,3、而每个组件实例都有watcher对象,4、它会在组件渲染的过程中把属性记录为依赖,5、之后当依赖项的 setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。为什么vue不能在IE8以下运行?因为IE8不支持ES5,所以用不了Object.defineProperty方法,又因为Object.defineProperty无法shim,所以vue不支持IE8及以下不支持ES5的浏览器。

February 20, 2019 · 1 min · jiezi

我设计一个phpms框架前的准备

phpms框架源码https://github.com/wuxiumu/ms一、PHP常用的四种数据结构简介:spl是php的一个标准库。官方文档:http://php.net/manual/zh/book…<?php //spl(php标准库)数据结构 /** * 栈(先进后出) /function zan(){ $stack = new SplStack(); $stack->push(‘data1’);//入栈(先进后出) $stack->push(‘data2’);//入栈 $stack->push(‘data3’);//入栈 echo $stack->pop().PHP_EOL;//出栈 echo $stack->pop().PHP_EOL;//出栈 echo $stack->pop().PHP_EOL;//出栈} /* 队列(先进先出) /function duilie(){ $queue = new SplQueue(); $queue->enqueue(‘data4’);//入队列 $queue->enqueue(‘data5’);//入队列 $queue->enqueue(‘data6’);//入队列 echo $queue->dequeue().PHP_EOL;//出队列 echo $queue->dequeue().PHP_EOL;//出队列 echo $queue->dequeue().PHP_EOL;//出队列} / * 堆 /function dui(){ $heap = new SplMinHeap(); $heap->insert(‘data8’);//入堆 $heap->insert(‘data9’);//入堆 $heap->insert(‘data10’);//入堆 echo $heap->extract().PHP_EOL;//从堆中提取数据 echo $heap->extract().PHP_EOL;//从堆中提取数据 echo $heap->extract().PHP_EOL;//从堆中提取数据} /* * 固定数组(不论使不使用,都会分配相应的内存空间) /$array = new SplFixedArray(15);$array[‘0’] = 54;$array[‘6’] = 69;$array[‘10’] = 32;var_dump($array);二、PHP链式操作的实现(原理)1、入口文件index.phpheader(“content-type:text/html;charset=utf-8”); define(‘PHPMSFRAME’,DIR); define(‘CORE’,PHPMSFRAME.’/core’);define(‘APP’,PHPMSFRAME.’/app’);define(‘MODULE’,‘app’);define(‘DEBUG’,true); include “vendor/autoload.php”;if(DEBUG){ $whoops = new \Whoops\Run; $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler); $whoops->register(); ini_set(‘display_error’, ‘On’);}else{ ini_set(‘display_error’, ‘Off’);}include CORE.’/common/function.php’;include CORE.’/phpmsframe.php’;spl_autoload_register(’\core\phpmsframe::load’);\core\phpmsframe::run();2、自动加载类corephpmsframe.php<?phpnamespace core;class phpmsframe { public static $classMap = array(); public $assign; static public function run() { $route = new \core\lib\route(); $ctrlClass = $route->ctrl; $action = $route->action; $ctrlfile = APP.’/ctrl/’.$ctrlClass.‘Ctrl.php’; $ctrlClass = ‘\’.MODULE.’\ctrl\’.$ctrlClass.‘Ctrl’; if(is_file($ctrlfile)){ include $ctrlfile; $ctrl = new $ctrlClass(); $ctrl->$action(); }else{ $msg = “控制器 $ctrlClass 不存在\n”; self::reportingDog($msg); } } static public function load($class) { if(isset($classMap[$class])){ return true; }else{ $class = str_replace(’\’, ‘/’, $class); $file = PHPMSFRAME.’/’.$class.’.php’; if(is_file($file)){ include $file; self::$classMap[$class] = $class; }else{ return false; } } } public function assign($name,$value){ $this->assign[$name]=$value; } public function display($file){ $file_path = APP.’/views/’.$file; if(is_file($file_path)){ /twig模板/ $loader = new \Twig_Loader_Filesystem(APP.’/views’); $twig = new \Twig_Environment($loader, array( ‘cache’ => PHPMSFRAME.’/cache’, ‘debug’=>DEBUG, )); $template = $twig->load($file); $template->display($this->assign?$this->assign:’’); /twig模板end/ /原生模板/ //extract($this->assign); //include $file_path; /原生模板end/ } } static private function reportingDog($msg){ echo $msg."\n"; include ‘smile/havefun.php’; $num = str_pad(rand(00,32),2,“0”,STR_PAD_LEFT); $num = “str_”.$num; $Parsedown = new \Parsedown(); echo $Parsedown->text($$num); $num = “str_".rand(50,84); echo $Parsedown->text($$num); exit; }}3、数据库类注:只是原理,并没有对方法进行具体的封装,具体的封装还是看个人喜好去定链式查询的风格。corelibmodel.php<?phpnamespace core\lib;use core\lib\drive\database\medooModel;class model extends medooModel{ }corelibdrivedatabasemedooModel.php<?php/* * 继承medoo第3方类库,模型基类 /namespace core\lib\drive\database;use core\lib\conf;use Medoo\Medoo;class medooModel extends Medoo{ //初始化,继承pdo应该是就可以直接用手册中的pdo中的方法了 public function __construct() { $option = conf::all(‘database’); parent::__construct($option[‘mysql_medoo_conf’]); }}三、PHP魔术方法的使用在php设计模式中,会涉及到很多魔术方法的使用,这里也对经常会用到的魔术方法进行简单总结。1、框架入口文件corephpmsframe.php /* * 魔术方法的使用 / / 这是一个魔术方法,当一个对象或者类获取其不存在的属性的值时, * 如:$obj = new BaseController ; * $a = $obj -> a ; * 该方法会被自动调用,这样做很友好,可以避免系统报错 / public function __get($property_name){ $msg = “属性 $property_name 不存在\n”; self::reportingDog($msg); } / 这是一个魔术方法,当一个对象或者类给其不存在的属性赋值时, * 如:$obj = new BaseController ; * $obj -> a = 12 ; * 该方法(_set(属性名,属性值))会被自动调用,这样做很友好,可以避免系统报错 / public function __set($property_name,$value){ $msg = “属性 $property_name 不存在\n”; self::reportingDog($msg); } / 这是一个魔术方法,当一个对象或者类的不存在属性进行isset()时, * 注意:isset 用于检查一个量是否被赋值 如果为NULL会返回false * 如:$obj = new BaseController ; * isset($obj -> a) ; * 该方法会被自动调用,这样做很友好,可以避免系统报错 / public function __isset($property_name){ $msg = “属性 $property_name 不存在\n”; self::reportingDog($msg); } / 这是一个魔术方法,当一个对象或者类的不存在属性进行unset()时, * 注意:unset 用于释放一个变量所分配的内存空间 * 如:$obj = new BaseController ; * unset($obj -> a) ; * 该方法会被自动调用,这样做很友好,可以避免系统报错 / public function __unset($property_name){ $msg = “属性 $property_name 不存在\n”; self::reportingDog($msg); } / 当对这个类的对象的不存在的实例方法进行“调用”时,会自动调用该方法, * 这个方法有2个参数(必须带有的): * $methodName 表示要调用的不存在的方法名; * $argument 是一个数组,表示要调用该不存在的方法时,所使用的实参数据, / public function __call($methodName,$argument){ $msg = “实例方法 $methodName 不存在\n”; self::reportingDog($msg); }四、三种基础设计模式1、工厂模式通过传入参数的不同,来实例化不同的类。$cache =\Core\extend\CacheFactory::getCacheObj(‘redis’,array( ‘host’ => ‘127.0.0.1’, ‘pass’ => ‘myRedis&&&’ ));var_dump($cache);coreextendCacheFactory.php<?phpnamespace core\extend;class CacheFactory{ const FILE = 1; const MEMCACHE = 2; const REDIS = 3; static $instance;//定义静态属性,用于存储对象 /* * 工厂类创建缓存对象 * @param $type 指定缓存类型 * @param array $options 传入缓存参数 * @return FileCache|Memcache|RedisCache / static function getCacheObj($type, array $options) { switch ($type) { case ‘file’: case self::FILE: self::$instance = new FileCache($options); break; case ‘memcache’: case self::MEMCACHE: self::$instance = new Memcache($options); break; case ‘redis’: case self::REDIS: self::$instance = new RedisCache($options); break; default: self::$instance = new FileCache($options); break; } return self::$instance; }}2、单例模式保证一个类只实例化一个类对象,进而减少系统开销和资源的浪费//单例模式创建对象$obj = Extend\SingleObject::getInstance();$obj2 = Extend\SingleObject::getInstance();var_dump($obj,$obj2);//从结果可以看出,两个实例化的对象其实是一个对象coreextendSingleObject.php<?phpnamespace core\extend;class SingleObject{ //私有的静态属性,用于存储类对象 private static $instance = null; //私有的构造方法,保证不允许在类外 new private function __construct(){ } //私有的克隆方法, 确保不允许通过在类外 clone 来创建新对象 private function __clone(){ } //公有的静态方法,用来实例化唯一当前类对象 public static function getInstance() { if(is_null(self::$instance)){ self::$instance = new self; } return self::$instance; }}3、注册树模式将我们用到的对象注册到注册树上,然后在之后要用到这个对象的时候,直接从注册树上取下来就好。(就和我们用全局变量一样方便)coreextendRegisterTree,php<?phpnamespace core\extend;class RegisterTree{ static protected $objects;//静态类属性,用于储存注册到注册树上的对象 /* * 将对象注册到注册树上 * @param $alias 对象的别名 * @param $object 对象 / static function setObject($alias,$object) { self::$objects[$alias] = $object; } /* * 从注册树上取出给定别名相应的对象 * @param $alias 将对象插入到注册树上时写的别名 * @return mixed 对象 / static protected function getObject($alias) { return self::$objects[$alias]; } /* * 将对象从注册树上删除 * @param $alias 将对象插入到注册树上时写的别名 / public function unsetObject($alias) { unset(self::$objects[$alias]); }}关于注册树模式,这里推荐一篇文章 ,可以方便理解。 https://segmentfault.com/a/11…五、其他常见的8种PHP设计模式1、适配器模式将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本的由于接口不兼容而不能一起工作的那些类可以一起工作。应用场景:老代码接口不适应新的接口需求,或者代码很多很乱不便于继续修改,或者使用第三方类库。常见的有两种适配器,分别是类适配器和对象适配器,这里拿更看好的对象适配器举例:<?phpnamespace Extend; /* * 对象适配器模式具体流程 * 1、根据需求定义接口,进而满足新需求功能 * 2、定义新类,继承并实现定义的接口 * 3、在实现接口时,原有的功能,只通过原有类对象调用原有类功能(委托) * 4、再根据需求,在新类中实现新需求功能 * 【适用性】 * (1)你想使用一个已经存在的类,而它的接口不符合你的需求 * (2)你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作 * (3)你想使用一个已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口(仅限于对 / /* * 目标角色(根据需求定义含有旧功能加上新功能的接口) * Interface Target 我们期望得到的功能类 * @package Extend /interface Target{ public function simpleMethod1(); public function simpleMethod2();} /* * 源角色(在新功能提出之前的旧功能类和方法) * Class Adaptee * @package Extend /class Adaptee{ public function simpleMethod1() { echo ‘Adapter simpleMethod1’."<br>”; } } /* * 类适配器角色(新定义接口的具体实现) * Class Adapter * @package Extend /class Adapter implements Target{ private $adaptee; function __construct() { //适配器初始化直接new 原功能类,以方便之后委派 $adaptee = new Adaptee(); $this->adaptee = $adaptee; } //委派调用Adaptee的sampleMethod1方法 public function simpleMethod1() { echo $this->adaptee->simpleMethod1(); } public function simpleMethod2() { echo ‘Adapter simpleMethod2’."<br>"; } } /* * 客户端调用 /$adapter = new Adapter();$adapter->simpleMethod1();$adapter->simpleMethod2();这篇文章介绍了类适配器的使用,感兴趣的可以了解一下 https://segmentfault.com/a/11...2、策略模式将一组特定的行为和算法封装成类,以适应某些特定的上下文环境,这种模式就是策略模式,策略模式可以实现依赖倒置以及控制反转。实例举例:假如一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有的广告位展示展示不同的广告。<?php /* * 首页数据控制器 * Class Index /class Home{ /* * 最好写上这个注释,告诉phpstorm是对应的哪个接口类,否则虽然程序执行正确,但phpstorm识别不了 * @var \Extend\UserType / protected $userType; /* * 首页展示数据 * 使用策略模式 * Index constructor. / function index() { echo “AD:”; $this->userType->showAd(); echo “Category:”; $this->userType->showCategory(); } /* * 策略模式 * 根据传递的用户性别展示不同类别数据 * @param \Extend\UserType $userType / function setUserType(\Extend\UserType $userType) { $this->userType = $userType; } } $obj = new Home();if ($_GET[‘userType’] == ‘female’){ $userType = new \Extend\FemaleUserType();} else { $userType = new \Extend\MaleUserType();}$obj->setUserType($userType);$obj->index();Extend/userType.php(定义的接口)<?php namespace Extend; /* * 策略模式 * 定义根据性别不同展示不同商品类目和广告接口 * Interface UserType * @package Extend /interface UserType{ //显示广告 function showAd(); //展示类目 function showCategory(); }MaleUserType.php、FemaleUserType.php(具体实现的类 )<?php namespace Extend; /* * 定义男性商品类目和广告位数据接口 * Class MaleUserType * @package Extend /class MaleUserType implements UserType{ /* * 广告栏数据展示 / function showAd() { echo “this is 男性’s 广告条目数据”; } /* * 商品类目数据展示 / function showCategory() { echo “this is 男性’s 商品类目数据”; } }<?php namespace Extend; /* * 定义女性商品类目和广告位数据接口 * Class FemaleUserType * @package Extend /class FemaleUserType implements UserType{ /* * 广告栏数据展示 / function showAd() { echo “this is 女性’s 广告条目数据”; } /* * 商品类目数据展示 / function showCategory() { echo “this is 女性’s 商品类目数据”; } }3、数据对象映射模式将对象和数据存储映射起来,对一个对象的操作会映射为对数据存储的操作。下面在代码中实现数据对象映射模式,我们将实现一个ORM类,将复杂的sql语句映射成对象属性的操作。并结合使用数据对象映射模式、工厂模式、注册模式。 (1)数据库映射模式简单实例实现<?php //使用数据对象映射模式代替写sql$user = new Extend\User(25);$user->name = ‘小卜丢饭团子’;$user->salary = ‘20000’;$user->city = ‘浙江省’; Extend/User.php<?php namespace Extend; class User{ //对应数据库中的4个字段 public $id; public $name; public $salary; public $city; //存储数据库连接对象属性 protected $pdo; public $data; function __construct($id) { $this->id = $id; $this->pdo = new \PDO(‘mysql:host=127.0.0.1;dbname=test’,‘root’,‘123456’); } function __destruct() { $this->pdo->query(“update user set name = ‘{$this->name}’,salary = ‘{$this->salary}’,city = ‘{$this->city}’ where id=’{$this->id}’”); }}这样,执行index.php文件,数据库就会发生相应的操作,也就实现了基本的数据对象映射。(2)数据库映射模式复杂案例实现<?php class EX{ function index() { //使用数据对象映射模式代替写sql $user = Extend\Factory::getUserObj(25); $user->name = ‘小卜丢饭团子’; $user->salary = ‘20000’; $user->city = ‘浙江省’; } function test() { $user = Extend\Factory::getUserObj(25); $user->city = ‘广东省’; } } $ex = new EX();$ex->index();Extend/Factory.php<?php namespace Extend; class Factory{ /* * 工厂模式创建数据库对象,单例模式保证创建唯一db对象 * @return mixed / static function CreateDatabaseObj() { $db = Database::getInstance(); return $db; } /* * 工厂模式创建user对象,注册树模式保证创建唯一对象,避免资源浪费 * @param $id * @return User|mixed / static function getUserObj($id) { $key = ‘user’.$id; $user = RegisterTree::getObject($key); if (!$user) { $user = new User($id); RegisterTree::setObject($key,$user); } return $user; }}Extend/Register.php<?php namespace Extend; /* * 注册树模式 * Class RegisterTree * @package Extend /class RegisterTree{ static protected $objects;//静态类属性,用于储存注册到注册树上的对象 /* * 将对象注册到注册树上 * @param $alias 对象的别名 * @param $object 对象 / static function setObject($alias,$object) { self::$objects[$alias] = $object; } /* * 从注册树上取出给定别名相应的对象 * @param $alias 将对象插入到注册树上时写的别名 * @return mixed 对象 / static function getObject($alias) { return self::$objects[$alias]; } /* * 将对象从注册树上删除 * @param $alias 将对象插入到注册树上时写的别名 / public function unsetObject($alias) { unset(self::$objects[$alias]); } }Extend/User.php<?php namespace Extend; class User{ //对应数据库中的4个字段 public $id; public $name; public $salary; public $city; //存储数据库连接对象属性 protected $pdo; public $data; function __construct($id) { $this->id = $id; $this->pdo = new \PDO(‘mysql:host=127.0.0.1;dbname=test’,‘root’,‘123456’); } function __destruct() { $this->pdo->query(“update user set name = ‘{$this->name}’,salary = ‘{$this->salary}’,city = ‘{$this->city}’ where id=’{$this->id}’”); }}这样,就实现了稍复杂的数据对象映射模式和工厂模式、注册树模式相结合的案例。 4、观察者模式当一个对象状态发生改变时,依赖它的对象会全部收到通知,并自动更新。场景:一个事件发生后,要执行一连串更新操作。传统的编程方式就是在事件的代码之后直接加入处理逻辑,当更新的逻辑增多之后,代码会变的难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件主体的代码。观察者模式实现了低耦合,非侵入式的通知与更新机制。 4.1、传统模式举例:<?php/* * 一个事件的逻辑控制器 * Class Event /class Event{ /* * 用户确认订单 / function firmOrder() { //这里假设一个事件发生了,比如用户已经完成下单 echo “用户已下单<br>”; //传统方式是在发生一个事件之后直接进行一系列的相关处理,耦合度比较高,比如写入日志,给用户发邮件等等 echo “在用户下单之后进行的一系列操作<br>”; } } $event = new Event();$event->firmOrder();4.2、观察者模式典型实现方式:(1)定义2个接口:观察者(通知)接口、被观察者(主题)接口(2)定义2个类,观察者类实现观察者接口、被观察者类实现被观察者接口(3)被观察者注册自己需要通知的观察者(4)被观察者类某个业务逻辑发生时,通知观察者对象,进而每个观察者执行自己的业务逻辑。代码示例:<?php/* * 观察者模式场景描述: * 1、购票后记录文本日志 * 2、购票后记录数据库日志 * 3、购票后发送短信 * 4、购票送抵扣卷、兑换卷、积分 * 5、其他各类活动等 / /* * 观察者接口 /interface TicketObserver{ function buyTicketOver($sender, $args); //得到通知后调用的方法} /* * 被观察者接口(购票主题接口) /interface TicketObserved{ function addObserver($observer); //提供注册观察者方法} /* * 主体逻辑,继承被观察者接口 * Class BuyTicket /class BuyTicket implements TicketObserved{ /* * 定义观察者数组属性,用于储存观察者 * @var array / private $observers = array(); /* * 实现被观察者接口定义的方法(添加观察者) * @param $observer 实例化后的观察者对象 / public function addObserver($observer) { $this->observers[] = $observer; } /* * 购票主体方法 * BuyTicket constructor. * @param $ticket 购票排号 / public function buyTicket($ticket) { //1、根据需求写购票逻辑 //………….. //2、购票成功之后,循环通知观察者,并调用其buyTicketOver实现不同业务逻辑 foreach ($this->observers as $observe) { $observe->buyTicketOver($this, $ticket); //$this 可用来获取主题类句柄,在通知中使用 } } } /* * 购票成功后,发送短信通知 * Class buyTicketMSN /class buyTicketMSN implements TicketObserver{ public function buyTicketOver($sender, $ticket) { echo (date ( ‘Y-m-d H:i:s’ ) . " 短信日志记录:购票成功:$ticket<br>"); }} /* * 购票成功后,记录日志 * Class buyTicketLog /class buyTicketLog implements TicketObserver{ public function buyTicketOver($sender, $ticket) { echo (date ( ‘Y-m-d H:i:s’ ) . " 文本日志记录:购票成功:$ticket<br>"); }} /* * 购票成功后,赠送优惠券 * Class buyTicketCoupon /class buyTicketCoupon implements TicketObserver{ public function buyTicketOver($sender, $ticket) { echo (date ( ‘Y-m-d H:i:s’ ) . " 赠送优惠券:购票成功:$ticket 赠送10元优惠券1张。<br>"); }} //实例化购票类$buy = new BuyTicket();//添加多个观察者$buy->addObserver(new buyTicketMSN());$buy->addObserver(new buyTicketLog());$buy->addObserver(new buyTicketCoupon());//开始购票$buy->buyTicket (“7排8号”);5、原型模式原型模式与工厂模式的作用类似,都是用来创建对象的。但是实现方式是不同的。原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象。这样,就免去了类创建时重复的初始化操作。原型模式适用于大对象的创建,创建一个大对象需要很大的开销,如果每次new就会消耗很大,原型模式仅需内存拷贝即可。代码实例:<?php/* * 抽象原型角色 /interface Prototype{ public function copy();} /* * 具体原型角色 /class ConcretePrototype implements Prototype{ private $_name; public function __construct($name) { $this->_name = $name; } public function setName($name) { $this->_name = $name; } public function getName() { return $this->_name; } public function copy() { //深拷贝实现 //$serialize_obj = serialize($this); // 序列化 //$clone_obj = unserialize($serialize_obj); // 反序列化 //return $clone_obj; // 浅拷贝实现 return clone $this; } } /* * 测试深拷贝用的引用类 /class Demo{ public $array;} //测试$demo = new Demo();$demo->array = array(1, 2);$object1 = new ConcretePrototype($demo);$object2 = $object1->copy(); var_dump($object1->getName());echo ‘<br />’;var_dump($object2->getName());echo ‘<br />’; $demo->array = array(3, 4);var_dump($object1->getName());echo ‘<br />’;var_dump($object2->getName());echo ‘<br />’;关于原型模式文章:https://www.imooc.com/article…6、装饰器模式可以动态的添加或修改类的功能一个类实现一个功能,如果要再修改或添加额外的功能,传统的编程模式需要写一个子类继承它,并重新实现类的方法。使用装饰器模式,仅需在运行时添加一个装饰器对象即可实现,可以实现最大的灵活性。<?php/* * 装饰器流程 * 1、声明装饰器接口(装饰器接口) * 2、具体类继承并实现装饰器接口(颜色装饰器实现,字体大小装饰器实现) * 3、在被装饰者类中定义"添加装饰器"方法(EchoText类中的addDecorator方法) * 4、在被装饰者类中定义调用装饰器的方法(EchoText类中的beforeEcho和afterEcho方法) * 5、使用时,实例化被装饰者类,并传入装饰器对象(比如new ColorDecorator(‘yellow’)) / /* * 装饰器接口 * Class Decorator /interface Decorator{ public function beforeEcho(); public function afterEcho();} /* * 颜色装饰器实现 * Class ColorDecorator /class ColorDecorator implements Decorator{ protected $color; public function __construct($color) { $this->color = $color; } public function beforeEcho() { echo “<dis style=‘color: {$this->color}’>”; } public function afterEcho() { echo “</div>”; }} /* * 字体大小装饰器实现 * Class SizeDecorator /class SizeDecorator implements Decorator{ protected $size; public function __construct($size) { $this->size = $size; } public function beforeEcho() { echo “<dis style=‘font-size: {$this->size}px’>”; } public function afterEcho() { echo “</div>”; }} /* * 被装饰者 * 输出一个字符串 * 装饰器动态添加功能 * Class EchoText /class EchoText{ protected $decorators = array();//存放装饰器 //装饰方法 public function Index() { //调用装饰器前置操作 $this->beforeEcho(); echo “你好,我是装饰器。”; //调用装饰器后置操作 $this->afterEcho(); } //添加装饰器 public function addDecorator(Decorator $decorator) { $this->decorators[] = $decorator; } //执行装饰器前置操作 先进先出原则 protected function beforeEcho() { foreach ($this->decorators as $decorator) $decorator->beforeEcho(); } //执行装饰器后置操作 先进后出原则 protected function afterEcho() { $tmp = array_reverse($this->decorators); foreach ($tmp as $decorator) $decorator->afterEcho(); }} //实例化输出类$echo = new EchoText();//增加装饰器$echo->addDecorator(new ColorDecorator(‘yellow’));//增加装饰器$echo->addDecorator(new SizeDecorator(‘22’));//输出$echo->Index();7、迭代器模式在不需要了解内部实现的前提下,遍历一个聚合对象的内部元素而又不暴露该对象的内部表示,这就是PHP迭代器模式的定义。相对于传统编程模式,迭代器模式可以隐藏遍历元素的所需的操作。<?php$users = new Extend\AllUser();//循环遍历出所有用户数据foreach ($users as $user) { var_dump($user);}Extend/AllUser.php<?phpnamespace Extend; /* * 迭代器模式,继承php内部自带的迭代器接口(\Iterator) * Class AllUser * @package Extend /class AllUser implements \Iterator{ protected $index = 0;//表示索引 protected $ids = array();//用于储存所有user的id(实际应用中,可以采用注册树模式进行存储) protected $pdo;//用于存储数据库对象 function __construct() { //获取pdo数据库对象 $this->pdo = new \PDO(‘mysql:host=127.0.0.1;dbname=test’,‘root’,‘123456’); //获取所有用户的id $this->ids = $this->pdo->query(“select id from user”)->fetchAll(2); } /* * 实现接口方法,重置迭代器,回到集合开头 / public function rewind() { $this->index = 0; } /* * 实现接口方法,获取当前元素 * @return mixed|void / public function current() { $id = $this->ids[$this->index][‘id’]; //获取当前用户的数据 $user_data = $this->pdo->query(“select * from user where id=’{$id}’”)->fetch(2); return $user_data; } /* * 实现接口方法,获取当前元素键值 * @return mixed|void / public function key() { return $this->index; } /* * 实现接口方法,获取下一个元素 / public function next() { $this->index++; } /* * 实现接口方法,验证是否还有下一个元素 * @return bool|void */ public function valid() { return $this->index < count($this->ids); } }关于php迭代器文章 http://www.php.cn/php-weiziji… 8、代理模式在客户端与实体之间建立一个代理对象(proxy),客户端对实体进行操作全部委派给代理对象,隐藏实体的具体实现细节。典型的应用就是mysql的主从结构,读写分离。在mysql中,对所有读的操作请求从库,所有写的操作请求主库。声明一个代理类,前台使用时只需创建一个代理类,调用对应方法即可。代码实例:<?php // 1、传统编程模式是手动选择#查询操作使用从库//$db_slave = Extend\Factory::getDatabase(‘slave’);//$info = $db_slave->query(“select * from user where id = 1 limit 1”);#增删改操作使用主库//$db_master = Extend\Factory::getDatabase(‘master’);//$db_master->query(“update user name = ‘xiaobudiu’ where id = 29 limit 1”); // 2、使用代理模式$db_proxy = new Extend\Proxy();$db_proxy->getUserName(1);$db_proxy->setUserName(29,‘xiaobudiu’);Extend/Proxy.php<?phpnamespace Extend; class Proxy implements IUserProxy{ function getUserName($id) { $db = Factory::getDatabase(‘slave’); $db->query(“select name from user where id =$id limit 1”); } function setUserName($id, $name) { $db = Factory::getDatabase(‘master’); $db->query(“update user set name = $name where id =$id limit 1”); }}Extend/Factory.php<?phpnamespace Extend; class Factory{ static function getDatabase($id) { $key = ‘database’.$id; if ($id == ‘slave’) { $slaves = Application::getInstance()->config[‘database’][‘slave’]; $db_conf = $slaves[array_rand($slaves)]; } else { $db_conf = Application::getInstance()->config[‘database’][$id]; } //注册树模式存储及获取对象 $db = Register::get($key); if (!$db) { $db = new Database\MySQLi(); $db->connect($db_conf[‘host’], $db_conf[‘user’], $db_conf[‘password’], $db_conf[‘dbname’]); Register::set($key, $db); } return $db; } }Extend/Application.php<?phpnamespace Extend; class Application{ public $base_dir; protected static $instance; public $config; protected function __construct($base_dir) { $this->base_dir = $base_dir; $this->config = new Config($base_dir.’/configs’); } static function getInstance($base_dir = ‘’) { if (empty(self::$instance)) { self::$instance = new self($base_dir); } return self::$instance; } }Extend/Config.php<?phpnamespace Extend; /** * 配置类,继承于php自带的ArrayAccess接口 * 允许一个对象以数组的方式访问 * Class Config * @package Extend /class Config implements \ArrayAccess{ protected $path; protected $configs = array(); function __construct($path) { $this->path = $path; } function offsetGet($key) { if (empty($this->configs[$key])) { $file_path = $this->path.’/’.$key.’.php’; $config = require $file_path; $this->configs[$key] = $config; } return $this->configs[$key]; } function offsetSet($key, $value) { throw new \Exception(“cannot write config file.”); } function offsetExists($key) { return isset($this->configs[$key]); } function offsetUnset($key) { unset($this->configs[$key]); }}configs/database.php<?php$config = array( ‘master’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ‘slave’ => array( ‘slave1’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ‘slave2’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ),);return $config;关于php代理模式文章 https://segmentfault.com/a/11…五、其余设计模式以及总结文章接:https://segmentfault.com/a/11…https://segmentfault.com/a/11… 六、面向对象编程的基本原则1、单一职责原则:一个类只需要做好一件事情。不要使用一个类完成很多功能,而应该拆分成更多更小的类。2、开放封闭原则:一个类写好之后,应该是可扩展而不可修改的。3、依赖倒置原则:一个类不应该强依赖另外一个类,每个类对于另外一个类都是可替换的。4、配置化原则:尽量使用配置,而不是硬编码。5、面向接口编程原则:只需要关心某个类提供了哪些接口,而不需要关心他的实现。 七、自动加载配置类文件1、php中使用ArrayAccess实现配置文件的加载(使得程序可以以数组的方式进行读取配置)(1)定义Config.php,继承php自带的ArrayAccess接口,并实现相应的方法,用于读取和设置配置Extend/Config.php<?phpnamespace Extend; /* * 配置类,继承于php自带的ArrayAccess接口 * 允许一个对象以数组的方式访问 * Class Config * @package Extend /class Config implements \ArrayAccess{ protected $path; protected $configs = array(); function _construct($path) { $this->path = $path; } function offsetGet($key) { if (empty($this->configs[$key])) { $file_path = $this->path.’/’.$key.’.php’; $config = require $file_path; $this->configs[$key] = $config; } return $this->configs[$key]; } function offsetSet($key, $value) { throw new \Exception(“cannot write config file.”); } function offsetExists($key) { return isset($this->configs[$key]); } function offsetUnset($key) { unset($this->configs[$key]); }}(2)configs/database.php<?php$config = array( ‘master’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ‘slave’ => array( ‘slave1’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ‘slave2’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ),);return $config;(3)读取配置<?php $config = new Extend\Config(DIR.’/configs’);var_dump($config[‘database’]);到此,就可以在程序中随心所欲的加载配置文件了。2、在工厂方法中读取配置,生成可配置化的对象Extend/Factory.php<?phpnamespace Extend; class Factory{ static function getDatabase($id) { $key = ‘database’.$id; if ($id == ‘slave’) { $slaves = Application::getInstance()->config[‘database’][‘slave’]; $db_conf = $slaves[array_rand($slaves)]; } else { $db_conf = Application::getInstance()->config[‘database’][$id]; } //注册树模式存储及获取对象 $db = Register::get($key); if (!$db) { $db = new Database\MySQLi(); $db->connect($db_conf[‘host’], $db_conf[‘user’], $db_conf[‘password’], $db_conf[‘dbname’]); Register::set($key, $db); } return $db; } }Extend/Application.php<?phpnamespace Extend; class Application{ public $base_dir; protected static $instance; public $config; protected function __construct($base_dir) { $this->base_dir = $base_dir; $this->config = new Config($base_dir.’/configs’); } static function getInstance($base_dir = ‘’) { if (empty(self::$instance)) { self::$instance = new self($base_dir); } return self::$instance; } }Extend/Config.php<?phpnamespace Extend; /* * 配置类,继承于php自带的ArrayAccess接口 * 允许一个对象以数组的方式访问 * Class Config * @package Extend */class Config implements \ArrayAccess{ protected $path; protected $configs = array(); function __construct($path) { $this->path = $path; } function offsetGet($key) { if (empty($this->configs[$key])) { $file_path = $this->path.’/’.$key.’.php’; $config = require $file_path; $this->configs[$key] = $config; } return $this->configs[$key]; } function offsetSet($key, $value) { throw new \Exception(“cannot write config file.”); } function offsetExists($key) { return isset($this->configs[$key]); } function offsetUnset($key) { unset($this->configs[$key]); }} ...

January 19, 2019 · 11 min · jiezi