Dev
S
O
N
G
主页
分类
时间轴
涂鸦
关于
留言
工具箱
Dev
S
O
N
G
Dev
S
O
N
G
一个理想主义者的博客
主页
分类
时间轴
涂鸦
关于
留言
工具箱
TimeClip——一个实用的剪贴板小工具
附带源码,欢迎交流
......
It's
欢迎访问本站,欢迎留言、分享、点赞。愿您阅读愉快!
https://www.devsong.org
Cygwin下载安装
背景 有不少小伙伴看完C语言入门教程后反应Cygwin无法下载(官网:http://www.cygwin.com/),这里的话我就给大家做一回搬运工吧。 说明 截至发文日2019/07/09,Cygwin1.dll版本为3.0.7。 此处提供的只是Cygwin下载工具,用于安装和升级工具包。下载此处安装文件后,需要一步步下载自己需要的工具,如open ssh等。下载可以手动在代理处填入国内的Cygwin镜像。推荐清华大学开源镜像https://mirrors.tuna.tsinghua.edu.cn/cygwin/和网易开源镜像http://mirrors.163.com/。使用国内镜像安装会快很多很多很多。 安装文件下载地址 32位:点此下载 64位:点此下载 补充说明 安装和使用教程网上有很多,大家搜索参看即可。亦可查看头条或百家号视频教程: 头条: https://haokan.baidu.com/videoui/page/videoland?pd=bjh&vid=12363977571003140087&fr=bjhauthor&type=video 百家: https://www.ixigua.com/i6704630567779959300/ 喜欢的话欢迎持续关注。 免责声明 此处只是Cygwin安装文件的搬运,目的是方便大家下载学习和使用。软件和本站无关。
2019-07-09
实用工具
InputStream数据读取不完整-解决方法
IO流的操作作为基本知识相信大家并不陌生,平时开发中也需要经常和各种流打交道。但是对于流的操作,看似简单,有时一不小心也会掉坑里。 记录 下面记录下最近遇到的一个小问题。对于 inputStream 流的操作,有时候为了方便,相信有不少人会像如下一样操作。 获取 inputStream 流 -> 获取数据长度 -> 开辟byte存储空间 -> 一次性读取数据到 byte[] -> 使用byte数据(写出或其他) -> 依次关闭流 以上操作如果是针对本地文件读取,那么没啥问题。但是如果放到网络环境,问题就出现了。且看如下代码: @RequestMapping("/demo.do") public void demo(HttpServletResponse resp) { InputStream is = null; OutputStream os = null; try { String url = "http://xxxxxxxxxxxxxxx"; URL realUrl = new URL(url); HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection(); connection.connect(); is = connection.getInputStream(); int len = is.available(); byte[] bytes = new byte[len]; is.read(bytes); os = resp.getOutputStream(); os.write(bytes); os.flush(); } catch (Exception e) { logger.error("error", e); } finally { // close stream } } 可以看到,以上操作从一个网络链接读取数据然后写入 response,如果网络较好,而且文件较小的话,或许能侥幸读取完整数据。但是绝大部分情况下是获取不到完整数据的。因为网络和数据的原因,数据往往是‘分片’发送过来的,如果你做过测试的话你会发现 每次调用 is.available() 获取到的长度都不一样 所以会导致上述问题的发生。也就是 inputStream 数据获取不完整,对于网络文件,如图片等,直观的体现就是图片只加载了很小一部分。如果你使用上述代码下载文件,那么必然是残缺的。 解决方法 那么如何解决呢?方法就是好好使用read方法的返回值,下面提供一种公认的比较标准的写法,使用如下方法完美解决数据残缺问题,当然,如果你上网搜索的话,可以看到其他的一些写法,但是原理都一样,这里供大家参考。 @RequestMapping("/demo.do") public void demo(HttpServletResponse resp) { InputStream is = null; OutputStream os = null; try { String url = "http://xxxxxxxxxxxxxxx"; URL realUrl = new URL(url); HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection(); connection.connect(); is = connection.getInputStream(); byte[] bytes = new byte[102400]; int len; os = resp.getOutputStream(); while((len = is.read(bytes)) != -1){ os.write(bytes, 0, len); } os.flush(); } catch (Exception e) { logger.error("error", e); } finally { // close stream } }
2019-06-21
Java
那些好用的Chrome插件
背景 Chrome浏览器就不多说了,做开发必不可少的浏览器,日常使用也方便,谷歌家的软件质量也很有保障。另外,Chrome强大好用的一点在于其拥有丰富的插件库,打开 Chrome 网上应用商店,你会发现各种各样的插件供选择。下面就介绍几款比较流行好用的插件应用。 1、Chrono 第一款自然是Chrono下载管理器,Chrome自带的下载管理器也够用,但追求丰富功能的朋友可能会不太喜欢。而Chrono下载管理器提供众多个性化选项,并且界面美观,交互逻辑清晰。使用起来类似迅雷等下载软件界面,符合国人操作逻辑。下载历史等功能也一应俱全,非常值得一试。 2、The Great Suspender 从名字大概就能猜出其用途,有时候页面开得很多,浏览器占用内存过大,会导致浏览卡顿、电脑卡顿。这个插件就是作用于那些打开了很久未查看的页面,及时将其挂起,以减少内存占用。小内存用户的福音。值得推荐。 3、ColorZilla 这个是一款颜色辅助工具,前端开发者的福音。这个插件可以方便地拾取网页上的颜色,还可以方便快捷地生成前端颜色过渡样式代码、亦可做到分析网页颜色。使用方便,前端开发人员可以尝试一下。 4、Coogle Keep 拓展工具 Google Keep 是谷歌推出的一款记事本类型的软件,平时记录笔记记录想法很好用,而且可以在各个平台无缝同步,很方便。而这款浏览器端的拓展,可以方便地将想要记录保存的网页内容存储之Google Keep,非常好用。当然,美中不足的是使用 Google Keep 对网络有一定的要求。 5、Video Downloader professional 看名字和图标就可以知道,这是一款方便保存网页上自己喜欢的多媒体内容相关插件,使用这个插件,我们可以轻松保存喜欢的内容,下载到本地查看。 6、Vue.js 开发工具 相信对与大多数开发者来说,对于Vue.js并不感到陌生,这是一个很流行的前端开发框架。Vue.js devtools 正是辅助开发人员进行调试的。装上Vue.js 拓展开发插件后,在调试模式下,如果侦测到当前使用的是Vue.js 框架进行开发的化,就可以使用该插件查看各个组件的信息,方便调试与排错。前端开发者必备的插件。 结语 Chrome插件众多,以上只是众多好用的插件中的一小部分。可以根据自己的需求去在线商店查看和下载自己喜欢的插件。
2019-05-21
实用工具
迟来的大玩具
一直想买个板子用来学(追)习(剧)、办(游)公(戏),去年恰巧遇上ipad变化较大的一代,当时看了外观,到体验店体验了下,觉得很不错,想买但是价格相较于前几代还是高了许多,于是忍住了。有好几次都想剁手还是忍住了,于是它静静躺在我购物车里,每天是不是还会打开看看降价没(斜笑眼)。终于,前几天看到有满减券,于是剁手了。 体验 优点嘛,很明显,120Hz的屏幕刷新率,很爽,屏幕观感也很舒服。apple pencil 终于不是上一代那种尴尬的充电方式,好评;换上了typec,好评,窄边框,好评,轻薄手感好评。 缺点嘛,也很明显,正如众多评测机构说的一样,IOS 拖了后腿。如此强悍的硬件,大部分时候发挥不出真正用处。虽说完整版 PS 之类的专业软件会为 iPad pro 适配完整版,但毕竟是少数。此外,iPad 上面软件生态现在看来真的不是太好,特别是 iPad pro 2018 发布之后,屏幕比例的变化使得很多软件使用起来都会出现黑边,并且在iPad 上,很多软件都是强行拉伸,界面和字体模糊,体验极差。不过适配过得软件,用起来还是很不错得。 玩具 从买它的那一刻起,我就不奢求其能作为生产力工具。的确,在我看来iPad也谈不上什么生产力,配个键盘像点样子,但是不支持鼠标(据说IOS13 会支持)。所以如大多数人一样,iPad买回来之后都是作为一个可以追剧的玩具。 除了玩游戏追剧,用来写写画画也是不错得,学生党用来记笔记也是不错得选择。附上5.20乱涂乱画得半成品。 结语 毫无头绪得说了一堆。5.20 没人陪只好把玩iPad顺便水水博客啦(<_<)。
2019-05-20
数码
Canvas 缩放图像如何保持像素风格
背景 众所周知,图像是由一个个像素点组成的,图像缩放,在屏幕上直观的展现就是一个像素点记录的信息用多个像素点展示。这么说可能不太严谨,但是比较好理解,比如,一个像素点记录的信息,在放大过程中若使用了四个像素点来展示,可以简单地理解为当前图像等比放大了两倍。在缩放的过程中,为了更好的观感,都会有一些辅助算法来处理放大后的图像,平滑是其中最基础的一种。在使用某些图像查看软件时,肉眼能比较明显地感受到平滑处理的效果,如图: 我们在使用Canvas来缩放图像的时候,也会出现类似的效果。那有时候我们就希望保持像素 风格,该怎么做呢? 处理方法 处理起来很简单,在获取到上下文对象后,我们可以对其属性进行一些设置,以阻止平滑效果的作用。只需将平滑设置为 false 即可,以下是实例代码: this.ctx2d = this.canvas.getContext('2d'); // 禁用平滑 this.ctx2d.mozImageSmoothingEnabled = false; this.ctx2d.webkitImageSmoothingEnabled = false; this.ctx2d.msImageSmoothingEnabled = false; this.ctx2d.imageSmoothingEnabled = false; this.ctx2d.oImageSmoothingEnabled = false;针对不同浏览器都设置了相应的属性以保证兼容性。设置后使用Canvas缩放图像就不会出现平滑处理了。也就达到了保持像素化的效果。
2019-05-20
HTML/CSS
使用notePad++优化XShell日志查看体验
背景 XShell是后台开发调试过程中经常使用到的工具,但有时候,但有时候,庞大的系统日志与错误疯狂输出的时候,对比info与error定位问题所在的时候,就稍显不友好。疯狂刷出来的日志使得定位查看变得困难,甚至如果缓冲区设置的小的话,刚出来的日志马上就被清除显示,这时候不得不去服务器下载日志文件到本地,进行查看。 优化 首先为了方便查看,自然是将缓冲区加大。设置方法如下(文件》属性》终端): 当然,加大缓冲区会使得上下滑动容易跟丢目标,若直接选择拖动滚动条,则会因为内容过多,滚动过于灵敏,从而增加定位难度。所以加大缓冲区有利有弊。看个人需求与操作习惯。 更优雅的方式 既然使用xshell自身查看日志不方便,那结合其他文本编辑器效果如何呢?于是就有了下面 XShell + notepad++ 的尝试。 为了结合其他文本编辑器使用,首先需要开启xhell日志记录。开启入口如下(文件》属性》高级》日志记录》连接时开始日志记录): 设置好之后,每次连接时就开始记录日志并输出到文件中。下面就该notepad++出场了。使用notepad++打开日志文件,然后进行设置(设置》首选项》其他): 勾选上自动更新文件,然后就能使用notepad++优雅的查看日志了,刷的再快也无所谓,而且结合文本编辑器自身的搜索功能,定位问题爽歪歪。 结语 以上介绍的是一点小技巧,但解决问题的方式本来就是多种多样的,相信还有更好跟优雅的方式。若您恰巧有好的方式,欢迎留言分享~
2019-05-15
随笔
vue + webpack 减小打包体积
接触Vue开发没多久,开发过程中遇到了一些坑,也学到了不少东西。开发完成后发现打包出来的文件都很大,放到线上严重影响网站加载速度。于是上网搜索了一下解决方法。下面是我遇到的坑及解决方法。 webpack 配置 查看打包后的文件发现,我们引用的所有框架文件都被打包了,如果使用到了ElementUI 等,也会一并打包。所以打包后的文件巨大。解决方法就是配置依赖从外部加载,然后通过CDN加载项目需要的文件。首先需要在 webpack基础配置中 ( webpack.base.conf.js ) 进行如下配置(将需要的组件配置为外部加载): externals: { 'vue': 'Vue', 'vue-router': 'VueRouter', 'element-ui': 'ELEMENT', },以上配置加在 module.exports 中,并且注意写法,后面的名称大小写不对也会导致引用失败。 修改index.html 做好如上配置之后,就可以在 index.html 中以加入js cdn链接了,如下: <script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script> <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script> <script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script> <script src="https://cdn.bootcss.com/element-ui/2.7.2/index.js"></script>如果你也和我一样,使用到了 ElementUI, 那么需要特别注意的是 ElementUI 的引用必须在 Vue 之后,因为其中引用到了 Vue,如果放前面的话会提示 Vue 未定义。此外比较重要的一点是: script引入位置不能放在 标签后面,即 body 和 html 之间 这是因为,打包之后,项目依赖的js是append到 body 中 的,这样外部引入的框架依赖就引用不到了,也会报 elementUi 和 Vue 未定义。除非手动更改打包后的js加载逻辑。 其他优化 取消 map 文件生成 map 文件是方便调试用的,取消生成可以提高打包效率,减小产物体积。在index.js中配置即可,将true改为false,如图: 将框架 css 也使用 CDN 引入 使用到的组件较多的话 css 文件也会有很大体积,这时可以将css也以cdn的形式引入,如 ElementUI 的基础样式文件: <link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.7.2/theme-chalk/index.css" type="text/css">具体使用哪个CDN根据项目情况来决定。
2019-05-12
HTML/CSS
9102年第一篇博文.to Yunhua
2019年快过半了,好久没打理的博客,偶尔上来看看都有些陌生了。没办法,总是被生活按在地上摩擦~所以一拖再拖。 但有的时候,离开,只为更好的展现,利用零碎时间断断续续鼓捣近两个月后,新版开始投入使用。 这是新版本投入使用后第一篇博文,也是2019年第一篇博文,很有意义,之前说过留给某小盆友,该写点什么呢。 考虑了好久,恰巧马上又是一年毕业季,先写点祝福吧(^_^)。听说小盆友可以提前结束实习,在这里先预祝: 毕业快乐! 考研顺利!
2019-05-08
时光记
SSH登录异常:WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
[type=mk]记一个SSH登录异常。头一次遇见,上网一看,碰到这个情况的人还真挺多,看异常提示这么翻译也很有趣:远程主机标识已经改变,或许有什么人在做坏事!解决方法也很简单。 错误提示:WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! 错误详情: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerprint for the RSA key sent by the remote host is SHA256:ZG3L294uusCPuid9gQ7uXu3Im+OGL+SPl3yUt8iUcU. Please contact your system administrator. Add correct host key in /home/admin/.ssh/known_hosts to get rid of this message. Offending RSA key in /home/admin/.ssh/known_hosts:9 RSA host key for 192.168.104.28 has changed and you have requested strict checking. Host key verification failed.首次用SSH协议登录远程主机,都会先生成一个对应的主机标识(其实是一个 RSA 秘钥)保存在本地文件内,下次再登录该远程系统服务就直接从文件中加载。当首次生成的存于服务器上的公钥发生改变,或者本地存储的秘钥发生改变,导致两端无法对应,就会出现上面的警告,自然,这种情况下是无法登陆远程主机的。 解决方法 仔细看警告内容会发现,解决方法很简单,既然发生了改变,那我们将其删除,重新生成一个即可。而存储位置在警告内容中已经指出,你可以将那个文件整个删除。 当然,更好的做法是编辑一下那个文件,找到出问题的主机ip, 删除对应的存储记录即可,这样,如果文件中存储有其他主机标识的话,就不至于影响其他正常登录的主机
2018-12-07
Linux
Oracle数据库中树形数据结构的遍历
树形结构,在数据结构反映到生活中是比较常见的,比如超市商品的分类,从大类到中间各级分类到最后的商品,又或者一个公司内部的部门划分等都可以映射为数据结构的数据。下面看一下在Oracle数据库中如何遍历一个树形结构的数据。 假设有这个一个需求,要求统计一个公司每个部门下的员工人数(包括其子部门),假设其中一个部门的结构如下,那么如果要统计这个部门下面有多少员工该怎么做呢?既然是树形结构,那么很容易想起数据结构中我们很树形的二叉树,二叉树是比较基本的一个树形数据结构。处理二叉树的时候,我们只需根据需求遍历即可,同样,如图所示的结构一般称为自由树,而处理这个树形结构依旧是一一遍历即可。 数据准备 为方便演示,这里先进行一下数据的准备,按照上图所示的结构在数据库中建立好数据,导出的脚本如下: -- 创建用户表 CREATE TABLE TB_USER ( ID VARCHAR2(50) not null, ORG_ID VARCHAR2(50), USER_NAME VARCHAR2(50) ); -- 创建组织表 CREATE TABLE TB_ORG ( ID VARCHAR2(50) not null, UP_ORG_ID VARCHAR2(50), ORG_NAME VARCHAR2(50) ); 数据初始化脚本:点此下载 开始遍历 下面,我们先不考虑统计部门人数,只考虑部门,因为树形结构是通过部门来维系的,所以只要遍历出了部门,其他的都好办了。通过浏览Oracle官方文档发现,遍历部门树有两种方式。一种是 connect ... by ... start with ... 语句,官方网站文章内称之为递归的“旧”方法,还有一种是 with ... select ... ,官方网站文章内称之为递归的"新"方法。具体看如下两个查询(部门id 为B69A838684F34197B866C417EE3820AA 的根节点的树): 查询一: SELECT ID, UP_ORG_ID FROM TB_ORG CONNECT BY PRIOR ID = UP_ORG_ID -- 关联条件及顺序(PRIOR位置不同,结果也不一样,因为遍历的方向改变了) START WITH ID = 'B69A838684F34197B866C417EE3820AA' -- 起始点 查询二: WITH t (ID, UP_ORG_ID) AS( SELECT ID, UP_ORG_ID FROM TB_ORG WHERE ID = 'B69A838684F34197B866C417EE3820AA' -- 起始点 UNION ALL SELECT org.ID, org.UP_ORG_ID FROM t, TB_ORG org WHERE t.ID = org.UP_ORG_ID -- 关联条件 ) SELECT * FROM t 以上两个查询都能得到部门ID为 B69A838684F34197B866C417EE3820AA 的树的所有部门。查询结果如下: 观察上面两个查询,可以发现其中的共同点,其中比较关键的是起点和关联条件的定义。还是比较容易理解的。到这一步,部门树算是遍历完了,那么下一步就比较容易了,用一个内连接关联用户(职员)即可得到所有关联的职员列表,统计职员人数或者更细致的信息也就很简单了。获取关联用户(职员)列表的查询如下: 方式一: SELECT b.* FROM ( SELECT ID, UP_ORG_ID FROM TB_ORG CONNECT BY PRIOR ID = UP_ORG_ID START WITH ID = 'B69A838684F34197B866C417EE3820AA' ) a INNER JOIN ( SELECT ID AS USER_Id, ORG_ID, USER_NAME FROM TB_USER ) b ON b.ORG_ID = a.ID 方式二: SELECT userId, ORG_ID, USER_NAME from ( WITH t (ID, UP_ORG_ID) AS( SELECT ID, UP_ORG_ID FROM TB_ORG WHERE ID = 'B69A838684F34197B866C417EE3820AA' -- 起始点 UNION ALL SELECT org.ID, org.UP_ORG_ID FROM t, TB_ORG org WHERE t.ID = org.UP_ORG_ID -- 关联条件 ) SELECT * FROM t a INNER JOIN ( SELECT ID as userId, ORG_ID, USER_NAME FROM TB_USER ) b ON a.ID = b.ORG_ID ) 以上两个查询都将得到如下结果: 得到user列结果其余的信息也就得到了。 总结 树形结构的处理还是很常用的,当然,以上只是我根据自己的理解写的,由于水平有限,描述得可能不是那么清楚,又或者以上语句执行效率不高,请见谅。如果你有好的见解或者更优的处理方法,欢迎留言交流。 最后,留下Oracle网站上看到的文章地址:https://www.oracle.com/technetwork/cn/articles/hartley-recursive-086819-zhs.html#6 里面讲得很详细,也讲了深度和广度遍历的方法,可以查看该文章。
2018-10-28
数据库
如何在不root的情况下卸载或禁用安卓手机内的顽固APP
某些安卓手机用起来总是不那么省心,特别是那些非国行的安卓手机,若是厂商开放一点,不封锁解锁通道,手机可以解锁可以 root 的话,折腾起来也还好,root 之后刷个好用的 rom 配合一些工具做一下优化,用起来也还不错。但是那只是理想情况,现实中有些安卓手机厂商是严禁用户解锁bootLoader, root 啥的自然行不通,虽然有些手机不解锁 bootloader 也能进行 root,但那也是几年前安卓版本较低的时候的事了。 闲话不多说,下面进入正题,如何在不root的情况下卸载或者禁用系统内的一些顽固无用的APP呢?要知道,对于很多系统内置的APP在设置界面是无法进行禁用的,比如 google service,这时候就需要借助 ADB 工具。利用 ADB 工具,几个简单的 ADB 指令即可对系统 APP 进行卸载操作或者禁用操作。 准备工作,如果你没有 ADB 相关工具,请先下载。这里我提供两个地址,有梯子的童鞋直接上谷歌下载: https://developer.android.com/studio/releases/platform-tools 没有梯子的童鞋看下面,找到SDK Platform-Tools下载即可: https://www.androiddevtools.cn/index.html 下载好之后解压即可,解压完成之后你可以把解压路径添加到系统变量,当然,如果你不常用的话可打开命令行添加为临时变量,或者不添加也行,在解压后的目录打开命令行操作即可。在执行操作之前,确保你的手机与电脑是连接正常的。输入 adb devices, 若提示如图所示的类似内容,则表示连接成功。 执行卸载指令: 卸载某个APP,只需知道其包名即可,如卸载“谷歌play服务”的指令如下: adb shell pm uninstall --user 0 com.google.android.gms 执行禁用命令,也类似: adb shell pm disable-user com.google.android.gms 注意: 【1】某些手机的系统在你执行完上述指令后,虽然卸载成功了,但是你重启手机之后又会为你装上。这种情况下一般禁用相关APP即可。 【2】恢复出厂设置的话,卸载了的那些APP就又回来了。原因也很简单,虽然执行上述指令卸载了APP,但是只是对当前系统当前用户卸载了,APK文件还是在系统内,重置手机之后自然又装上了。不过这不是重点,因为我们的目的达到了。 【3】卸载时出现错误:[DELETE_FAILED_DEVICE_POLICY_MANAGER] 这多半是因为你要卸载的那个软件请求了 设备管理器 服务权限,只需在设置中找到 设备管理器,然后关闭那个软件的设备管理器权限即可。 总结: 利用 ADB,可以解决不少无法 root 以致系统内无用软件无法管理的问题。虽然 利用 ADB 利用 shell 命令能执行一些高权限动作,但也仅限于某几个指令,很多还是需要 root 权限,否则遇到的只会是 permission denied。所以根本办法还是 root 比较方便,无奈某些手机实在无法 root。
2018-10-27
Android
节日快乐
请关爱身边每一位努力奋斗的程序员 请记住他们的样子 因为他们一不小心就秃了 ... 扎心了 --- 祝各位 猿 各位 狮 节日快乐!--- --- 是时候添置几件新衣服了 --- --- 节日愿望 --- --- 头发浓密茂盛 --- --- 修得BUG抗体 --- (图片源于互联网)
2018-10-24
日常娱乐
设计模式之代理模式
什么是代理模式 为其他对象提供一种代理以控制对这个对象的访问的一种设计模式。一张图解释代理模式: 代理模式的优点 (1)隔离作用;(2)无侵入地进行某些业务逻辑的执行 代理模式的分类 两个大类:(1)静态代理; (2)动态代理 此外,无论是静态代理还是动态代理都有基于接口实现和基于继承实现两类,动态代理典型的如JDK代理(基于接口)和Cglib代理(基于继承) 静态代理 缺点:代理的方法越多,重复的逻辑越多。假设代理目标对象有100个方法需要进行代理执行,那么代理类里面也必须进行这100个方法的委托,而如果这些方法前后需要插入的逻辑是一样的话,这样做就会有100段重复的代码,耗时耗力。 示例: Subject接口:Subject.java public interface Subject { void say(); } RealSubject.java (Subject实现类) public class RealSubject implements Subject { @Override public void say() { System.out.println("hello world"); } } Proxy.java (基于接口,实现Subject接口) public class Proxy implements Subject{ private RealSubject target; public Proxy(RealSubject target) { this.target = target; } public void say() { System.out.println("静态代理开始"); try { target.say(); } catch (Exception e) { System.out.println("error : " + e.getMessage()); throw e; } finally { System.out.println("静态代理结束"); } } } SubjectPlus.java (基于继承实现代理, 继承RealSubject) public class SubjectPlus extends RealSubject { @Override public void say() { System.out.println("基于继承的静态代理---开始"); super.say(); System.out.println("基于继承的静态代理---结束"); } } Main方法:Client.java public class Client { public static void main(String[] args) { //基于接口 Proxy proxy = new Proxy(new RealSubject()); proxy.say(); //基于继承, 通过继承RealSubject实现代理 Subject subject = new SubjectPlus(); subject.say(); } } 执行结果: 静态代理开始 hello world 静态代理结束 基于继承的静态代理---开始 hello world 基于继承的静态代理---结束 动态代理之JDK代理 JDK代理只能针对有接口的类的接口方法进行动态代理。由于接口里面是不能有private方法的,所以jdk也无法对private方法进行代理。Subject、RealSubject沿用上述类。 自定义类:JdkProxy.java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class JdkProxy implements InvocationHandler { private RealSubject target; public JdkProxy(RealSubject target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始 JDK 动态代理"); Object result; try { result = method.invoke(target, args); } catch (Exception e) { System.out.println("error : " + e.getMessage()); throw e; } finally { System.out.println("完成 JDK 动态代理"); } return result; } } Main方法:Client.java import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Subject.class}, new JdkProxy(new RealSubject())); subject.say(); } } 运行结果 开始 JDK 动态代理 hello world 完成 JDK 动态代理 动态代理之Cglib代理 注:需要导入Cglib的jar包,此外,Cglib依赖于Asm.jar,需要一并导入。如果报错说java.lang.ClassNotFoundException: org.objectweb.asm.Type就是应为没引入Asm包。 由于Cglib是基于继承来实现代理,所以无法对static、final类进行代理,也无法对private、static方法进行代理 DemoInterceptor.java 实现了MethodInterceptor接口 import java.lang.reflect.Method; public class DemoInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("cglib 动态代理开始"); Object result; try { result = methodProxy.invokeSuper(o, objects); } catch (Exception e) { System.out.println("error : " + e.getMessage()); throw e; } finally { System.out.println("cglib 动态代理完成"); } return result; } } Client.java 通过Enhancer获取代理实例 import net.sf.cglib.proxy.Enhancer; public class Client { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(RealSubject.class); enhancer.setCallback(new DemoInterceptor()); Subject subject = (Subject) enhancer.create(); subject.say(); } } 执行结果: cglib 动态代理开始 hello world cglib 动态代理完成
2018-08-15
Java
intelliJ IDEA Java注释模板配置
主要内容:intelliJ IDEA live template 配置 话不多说,进入正题:个性化自己的注释模板,主要针对Java,其余的语言也是类似的配置方式 第一部分 方法注释配置 步骤一: 首先,进入到设置界面:位置:File -> Settings -> Editor -> Live Templates 进入到Live Templates界面之后,点击右上角的加号,新建一个模板组,名称按自己需要,随便取。 步骤二: 新建完模板组后,选择我们新建的组,然后点击右上角的加号,新建动态模板: 步骤三: 模板配置 解释:Abbreviation: 快捷标识:比如我们需要调用这个模板,由于这个值我设置为“*”,所以在使用时只需输入 /**, 然后敲tab键(可配置)即可。 Template text中需要我们输入模板内容,需要注意的是动态内容必须使用$包裹,否则无效。此处我设置的是方法的注释模板,内容如下: * * description TODO * date $date$ $time$ * @author song@devsong.org $params$ * @return $returns$ */ 由于我们针对的是Java的配置,所以输入模板内容后,点击左下角的Define设置可用的范围,直接勾选Java即可。如图: 然后点击Edit variables进行参数配置,配置如下: params里面由于可能涉及多个参数,所以需要使用groovy脚本来处理,脚本内容如下: groovyScript("def result=''; def params=\"${_1}\".replaceAll('[\\\\[|\\\\]|\\\\s]', '').split(',').toList(); for(i = 0; i < params.size(); i++) {result+=' * @param ' + params[i] + ((i < params.size() - 1) ? '\\n' : '')}; return result", methodParameters()) 其实也就是获取参数列表,遍历输出,复制到上面即可。 其他配置 触发按键:在如下位置设置: 完成以上配置后就可以进行使用了,效果如下: 第二部分 类注释配置 完成了上面的方法注释配置,类注释其实是类似的,在上面的组中新建一个模板,配置如下:这里我设置标识为c,所以需要插入该模板的时候,只需输入/*c然后按下tab键即可。(你也可以把完整的注释(包括/*)写入模板,然后使用的时候直接敲出标识符,然后按tab即可) 可以根据自己的需求进行调整。 内容如下: * * description TODO * created $date$ $time$ * class_name $NAME$ * @author song@devsong.org * @version 1.0 */ 使用效果: 至此,配置完成。可以灵活按照需求进行调整。 20190312 配置更新 (1)方法注释模板 * * * $params$ * @return * @date $date$ $time$ * @author song@devsong.org */ ###params脚本 groovyScript("def result=''; def params=\"${_1}\".replaceAll('[\\\\[|\\\\]|\\\\s]', '').split(',').toList(); for(i = 0; i (2)类注释模板 * * * * @author song@devsong.org * @version 1.0 * @date $date$ $time$ * @className $NAME$ */
2018-08-11
实用工具
MySQL 8.0 java.sql.SQLException Unknown system variable 'query_cache_size'
由于xxxxx,最近更新了MySQL版本到MySQL8.0,原本以为这只是一次普通的软件升级行为,可是当我跳到坑里的时候,我才意识到没那么简单。下面贴出错误描述。 【友情提示:不想看我废话的请直接到文章末尾查看解决方法】 详细信息: 严重 [http-nio-8080-exec-10] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [mvc-dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: Cannot create PoolableConnectionFactory (Unknown system variable 'query_cache_size') ### The error may exist in config/mybatis/sqlXml/article.xml ### The error may involve org.devsong.dao.IArticle.getSiteMapData ### The error occurred while executing a query ### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: Cannot create PoolableConnectionFactory (Unknown system variable 'query_cache_size')] with root cause java.sql.SQLException: Unknown system variable 'query_cache_size' ... ... 上面的错误看起来是不是很熟悉,没错,上面的错误在以前升级MySQL的时候,是由于数据库连接驱动版本过低,与数据库不匹配。这处理起来很简单,我也遇到过多次了,直接升级mysql-connector版本就行了。你如果在网上搜索上面的错误的话,绝大部分的解决方法也都是升级驱动。 想着已经非常眼熟这个异常了,于是我以一个老司机的姿态像往常一样更新了Maven依赖配置,像什么都没发生过一样重新部署了项目。嘀嘀嘀...错误代码500...我赶紧打开后台看异常信息,我的乖乖,怎么还是上面那个异常,明明更新了驱动的呀。。。至此老司机翻车。 我连滚带爬地打开代码,寻找着错误,看了一下Spring相关配置,没问题,看了一下数据库配置文件,没问题。于是我开始猜想是不是其他和数据库相关的东西也该升级了,就这样,我把数据库连接池、Spring-jdbc等能升级的都升级。想着异常一定能解决,于是我再一次部署了项目,内心忐忑的我再一次翻车:嘀嘀嘀...错误代码500。。。 连翻两次车之后,我决定放弃自己折腾了,老老实实去看文档。我搬出了常年不用的梯子,爬到了墙外,在一个角落里,我看到了某些东西:从MySQL8.0开始,已经支持将驱动写为com.mysql.cj.jdbc.Driver。在配置数据库连接的时候,需要加上时区配置。我抱着试一试的心态,修改了一下JDBC配置,在我以为又会失败时,嘀嘀嘀,成功。。。 下面是旧版配置和新版配置的对比: #### 旧 版 #### jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8 jdbc.username=root jdbc.password=root #### 新 版 #### jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC jdbc.username=root jdbc.password=root 网上说驱动写为com.mysql.jdbc.Driver 或者 com.mysql.cj.jdbc.Driver 都行,但我实际测试必须写为后者才行,并且serverTimezone的设置也不能少,二者缺一不可,少一个就翻车。 总结: (1)更新驱动 (2)更改jdbc配置 --> driver名、serverTimezone 附: 你在折腾的时候或许重装过MySQL,如果你在卸载重装的过程中发现Windows服务中充斥着多个MySQL,想删除的话: Win + R >> CMD >> sc delete 服务名
2018-08-07
数据库
1
2
3
...
7
8
9
涂鸦
热门
CSS hover更改其他元素属性
12140
JPEGView--一个好用的图片查看和编辑软件
11157
MySQL 8.0 java.sql.SQLException Unknown system variable 'query_cache_size'
9795
Chrome浏览器暗色模式
8694
intelliJ IDEA Java注释模板配置
8683
Maven 和 Gradle 国内代理配置
8088
android.database.sqlite.SQLiteException:AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY (code 1)
7993
安卓刷机双清recovery报错 Failed to mount '/data' (invalid argument)
6890
Tomcat响应流程及Servlet的执行流程
6392
一个eclipse配置tomcat容易困扰新手的问题
6168
分类
Java
(43)
Android
(9)
C/C++
(11)
嵌入式
(2)
数据库
(8)
PHP
(1)
JavaScript
(7)
HTML/CSS
(7)
随笔
(8)
实用工具
(10)
时光记
(8)
Linux
(11)
Windows
(2)
日常娱乐
(2)
数码
(1)
每日资讯
(0)
最新留言
LuckyX
Aug 18, 2021.
有用!红米note5A 成功,谢谢楼主
嗯
June 23, 2021.
12
极客
Apr 13, 2021.
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As range, Cancel As Boolean) Dim min As Double Dim max As Double Dim range As Double max = ActiveSheet.range(Target.Address).Offset(11, -1).Value min = ActiveSheet.range(Target.Address).Offset(12, -1).Value range = ActiveSheet.range(Target.Address).Offset(13, -1).Value For i = 0 To 10 ActiveSheet.range(Target.Address).Offset(i, 0).Value = (max - ActiveSheet.range(Target.Address).Offset(i, -1).Value) / range Next i End Sub
招投标
Jan 06, 2021.
这个确实很刚需了,用的时候比较多
招投标
Jan 06, 2021.
下次拿家里的安卓机试一试,还可以偶尔用下
招投标
Jan 06, 2021.
感谢大佬的分享,支持一下
招投标
Jan 06, 2021.
文章写的不粗哦,赞一个
月雅
Sept 17, 2020.
大佬教我学编程.。。。。。
小曾曾的博客(www.xiaozeng.cc)
June 14, 2020.
抱歉,因为个人原因,本站暂停运营。
小曾曾的博客(www.xiaozeng.cc)
Apr 29, 2020.
小曾曾的博客(www.xiaozeng.cc)已添加贵站链接,希望贵站能添加。
友链
帅大叔的博客
DevSONG
张宇童-前沿技术博客
逆风的小窝