-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 28 KB
/
content.json
1
{"pages":[{"title":"","text":"32lzrlJrEj","link":"/baidu_verify_32lzrlJrEj.html"},{"title":"about","text":"傲霜惯北枝,荷醒擎雨时。","link":"/about/index.html"},{"title":"tags","text":"","link":"/tags/index.html"},{"title":"categories","text":"","link":"/categories/index.html"}],"posts":[{"title":"Java调用静态方法需要类装载还是初始化?","text":"结论及原因 结论:Java调用静态方法时会对类进行装载、连接和初始化 原因:Java类的加载方式是按需加载,遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。 测试 参照之前写的一篇文章,得出的测试思路: 对一个有静态方法和静态代码块的类只进行装载,不进行初始化 通过反射获取其静态方法,并invoke这个静态方法 因为静态代码块在初始化的时候执行,所以如果invoke这个静态方法的时候,静态代码块执行了,说明了Java调用静态方法时已经完成了类的初始化 有一个含有static方法和static块的类 1234567891011class MyStatic { static { System.out.println(\"静态代码块执行了\"); } public static void say() { System.out.println(\"静态方法执行了\"); } MyStatic() {}} 测试 12345678910111213public class Test { Test() {} public static void main(String[] args) { Test test = new Test(); try{ Class clazz = Class.forName(\"MyStatic\", false, test.getClass().getClassLoader()); Method method = clazz.getMethod(\"say\"); method.invoke(null); } catch (ClassNotFoundException e) { e.printStackTrace(); } }} 控制台打印的结果: 静态代码块执行了 静态方法执行了 以上","link":"/Java%E8%B0%83%E7%94%A8%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%E9%9C%80%E8%A6%81%E7%B1%BB%E8%A3%85%E8%BD%BD%E8%BF%98%E6%98%AF%E5%88%9D%E5%A7%8B%E5%8C%96/"},{"title":"Java双缓冲技术的简单使用","text":" 动画的刷新频率越快,那么动画看起来就越连贯。 但是使用java的GUI技术的时候,提高重画频率往往会出现闪烁,移动的物体看起来有点一顿一顿的,原因就是重画频率太快,上个paint方法还没有完成就开始执行下个paint方法了。 要解决这个问题,可以使用java的双缓冲技术。 双缓冲技术原理 每一次调用paint方法之前,先把paint方法需要画出来的东西画在一张虚拟的图片上,画完后再显示在屏幕上 创建个Image对象,用于存放虚拟的图片 1Image offScreenImage = null; 重写update方法 我们知道repaint方法会调用update方法,由update方法来调用paint方法,那么只要重写update方法,就可以对paint方法的调用进行控制 有关repaint、update和paint之间的关系,可以看这篇文章:关于Java中的paint,repaint,update三个方法的关系 重写的步骤: 首先创建一张和整个窗体一样大的图片 把内容画到图片上 把图片画到屏幕上 代码示例如下: 12345678910111213141516public void update(Graphics g) { // TODO Auto-generated method stub if(offScreenImage == null) { offScreenImage = this.createImage(WIDTH, HEIGHT); //创建一张大小和窗口大小一样的虚拟图片 } Graphics gOffScreen = offScreenImage.getGraphics(); //获得画笔 //刷新背景,否则物体运动痕迹会保留 Color c = gOffScreen.getColor(); //复制原前景色 gOffScreen.setColor(backgroundColor); //backgroundColor为原来设置的背景色 gOffScreen.fillRect(0, 0, WIDTH, HEIGHT); //画一个矩形覆盖掉原来的图像 gOffScreen.setColor(c); //还原前景色 paint(gOffScreen); //画到虚拟图片上 g.drawImage(offScreenImage, 0, 0, null); //把图片画到屏幕上} 双缓冲示例123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108package com.ryokai96.test;import java.awt.Color;import java.awt.Frame;import java.awt.Graphics;import java.awt.Image;import java.awt.event.WindowAdapter;import java.awt.event.WindowEvent;public class DoubleBufferTest extends Frame{ private static final int WIDTH = 800; //窗口宽 private static final int HEIGHT = 600; //窗口高 private int x = 50; //圆的横坐标 private int y = 50; //圆的纵坐标 private static Image offScreenImage = null; //用于实现双缓冲 /* * 此方法用于创建窗口 */ public void launchFrame() { setSize(WIDTH, HEIGHT); setTitle(\"DoubleBufferTest\"); //jdk1.4之后提供的方法,setLocationRelativeTo(owner); //这种方法是设定一个窗口的相对于另外一个窗口的位置(一般是居中于父窗口的中间) //如果owner==null则窗口就居于屏幕的中央。 setLocationRelativeTo(null); //使窗口可关闭 this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { // TODO Auto-generated method stub System.exit(0); } }); setResizable(false); //设置不可改变窗口大小 this.setBackground(Color.white); setVisible(true); new Thread(new PaintThread()).start(); //开启repaint线程 } /* * 重写update方法,实现双缓冲,以消除闪烁 */ @Override public void update(Graphics g) { // TODO Auto-generated method stub if(offScreenImage == null) { offScreenImage = this.createImage(WIDTH, HEIGHT); //创建一张大小和窗口大小一样的虚拟图片 } Graphics gOffScreen = offScreenImage.getGraphics(); //获得画笔 //刷新背景,否则物体运动痕迹会保留 Color c = gOffScreen.getColor(); gOffScreen.setColor(Color.white); gOffScreen.fillRect(0, 0, WIDTH, HEIGHT); gOffScreen.setColor(c); paint(gOffScreen); //画到虚拟图片上 g.drawImage(offScreenImage, 0, 0, null); //把图片画到屏幕上 } @Override public void paint(Graphics g) { // TODO Auto-generated method stub Color c = g.getColor(); //取出原前景色 g.setColor(Color.RED); //设置前景色 g.fillOval(this.x, this.y, 30, 30); //画圆 g.setColor(c); //恢复原前景色 y += 5; } /* * 用于重画的线程 */ private class PaintThread implements Runnable { @Override public void run() { // TODO Auto-generated method stub while(true) { repaint(); //重画 try { Thread.sleep(50); //每隔50ms重画一次 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public static void main(String[] args) { DoubleBufferTest tc = new DoubleBufferTest(); tc.launchFrame(); }}","link":"/Java%E5%8F%8C%E7%BC%93%E5%86%B2%E6%8A%80%E6%9C%AF%E7%9A%84%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/"},{"title":"Java静态代码块执行时机","text":" 百度一下java的static块执行时机,搜出来的文章有两种说法,第一种说static块是在类的加载时执行(包括我看过的Java视频都是这么说的),另一种则是反对第一种说法,即static块并不是在类的加载时执行的,而是在类的初始化时执行的;谁对谁错,事实用代码证明。 类的运行步骤 类的运行可分为三个步骤:装载、连接、初始化 装载:查找和导入Class文件 连接:把类的二进制数据合并到JRE中,又分为三个步骤 校验:检查载入Class文件数据的正确性 准备:给类的静态变量分配存储空间 解析:将符号引用转成直接引用 初始化:对类的静态变量,静态代码块执行初始化操作(看到这里,结论就已经出来了,但我们还是要用代码来说话) 用反射机制来证明static块的执行时机 有一个含static块的类: 1234567class MyStatic { static { System.out.println(\"静态代码块执行了\"); } MyStatic() {}} 先来看一个错误的例子(我看的视频里就是用这个例子来证明static块是在类的加载时执行的) 12345678910public class Test { public static void main(String[] args) { Test test = new Test(); try{ Class.forName(\"MyStatic\"); //误认为forName方法运行类只完成加载,实际上forName方法运行时已执行了类的初始化 } catch (ClassNotFoundException e) { e.printStackTrace(); } }} 运行结果 静态代码块执行了 静态代码块执行了,然而这并不能代表什么,因为 1Class.forName(\"MyStatic\"); 等同于 1Class.forName(\"MyStatic\", true, test.getClass().getClassLoader()); 其中第二个参数指明了是否进行类初始化,将其改成false再试一次 1234567891011public class Test { Test() {} public static void main(String[] args) { Test test = new Test(); try{ Class.forName(\"MyStatic\", false, test.getClass().getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } }} 这时MyStatic类并没有进行初始化过程,而程序运行没有任何输出,得出结论: Java中静态代码块是在类的初始化过程中执行的,并不是在类的装载时执行的","link":"/Java%E9%9D%99%E6%80%81%E4%BB%A3%E7%A0%81%E5%9D%97%E6%89%A7%E8%A1%8C%E6%97%B6%E6%9C%BA/"},{"title":"关于数组名和对数组名取地址","text":"以下是基于32位系统的测试: 程序一: 1234567void main(){ int a[3] = {1, 2, 3}; int *p1 = (int *)(&a+1); int *p2 = (int *)(a+1); printf(\"%x, %x\", p1[-1], *p2); //输出为3, 2} 程序二 1234567void main(){ int a[3] = {1, 2, 3}; printf(\"%p\\n%p\\n\", a, &a); //这里a和&a输出一样为0012FF58 printf(\"%d\\n%d\\n\", sizeof(a), sizeof(&a)); //sizeof(a)输出为12,sizeof(&a)输出为4 printf(\"%p\\n%p\", &a+1, a+1); //注意&优先级高于+,&a+1输出0012FF64,a+1输出为0012FF5C} 程序二中:a和&a打印出来的都是数组a的首地址。 数组名a虽然能够单独拿出来代表一个地址,但是和指针还是有区别的,数组名是符号地址常量,在编译时求值并存在编译器的符号表里面,其值就是个内存地址,程序并没有给其分配空间,这与指针不同,sizeof(a)得到的是整个数组的大小12。所以a与a+0有区别,a+0与&a[0]等价。 关于&a,注意这里的a是一个指针(a加了&后就自动变成了指针,这点很有趣),指向整个数组,其值是数组的首地址,&a就是指针a取地址,所以sizeof(&a)就是一个指针所占内存空间的大小(可以把int a[3]改为char a[3]试试),而&a+1的偏移量是整个数组的大小,所以,在程序一中p1[-1]输出的不是1而是3。 关于&a是否非法,本来对常量取地址是非法的,但是标准组织没有规定对数组名取地址是非法还是合法,所以因编译器而异,在VS中是合法的。","link":"/%E5%85%B3%E4%BA%8E%E6%95%B0%E7%BB%84%E5%90%8D%E5%92%8C%E5%AF%B9%E6%95%B0%E7%BB%84%E5%90%8D%E5%8F%96%E5%9C%B0%E5%9D%80/"},{"title":"基于Hexo和Github搭建博客","text":"前言这是一篇使用GitHub Pages和Hexo搭建独立博客的总结 我是去年十月份左右接触的git,直到前不久,git对我来说都只是同步个人代码的工具(对git的版本控制、分支都不是很理解),这次借着搭建博客的机会稍稍理解了一点git的分支是怎么回事 写博客是一个好习惯,我从去年开始会写一些博客,之后有时间会陆续搬过这里来(其实也就那么几篇- -!) 在看之前,应该先了解一下: GitHub GitHub Pages Hexo Markdown 准备工作创建对应仓库 安装Git,略 注册GitHub,略 配置Git,略 与GitHub建立联系,略 以上4步相关资料:Git和Github简单配置 在GitHub上创建新的仓库,命名为 username.github.io (其中username为github的用户名),例如 Ryokai96.github.io 安装Hexo 安装Hexo需要首先安装Node.js,到官网下载安装(最好安装LTS版本):Node.js官网 新建本地文件夹blog 12$ mkdir blog$ cd blog 全局安装hexo-cli,blog文件夹内安装hexo 12$ npm install -g hexo-cli$ npm install hexo 搭建Hexo博客 创建并初始化Hexo目录 1$ hexo init Hexo 完成后,当前目录下会多出一个Hexo目录 在Hexo目录中安装需要的插件 123$ cd Hexo# hexo deploy git插件$ npm install hexo-deployer-git --save 测试 123456# 新建hellohexo.md文章$ hexo new hellohexo.md# 渲染成静态页面$ hexo generate# 启动本地hexo服务器$ hexo server 上面两条也可简写为 12$ hexo g$ hexo s 然后在浏览器中输入localhost:4000即可进行预览 使用GitHub Pages搭建一个Hexo博客 编辑Hexo目录下的_config.yml文件 1$ vim _config.yml 将下面这段进行修改 1234# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy: type: 修改为 123456# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy: type: git repository: git@github.com:Ryokai96/Ryokai96.github.io branch: master 注意 : 后面有一个空格,如 type: git repository后的url使用ssh形式,注意最后的.git要去掉 Hexo首页默认显示最新的一篇文章,可以在文章中使用<!--more-->来分割预览的文章和更多详情,或者使用hexo-excerpt插件来自动分隔(不推荐)。若要使用hexo-exerpt,首先在Hexo目录下安装插件: 12# hexo 自动截断插件$ npm install hexo-excerpt --save 然后编辑Hexo目录下的_config.yml文件,添加以下配置: 12345excerpt: depth: 2 excerpt_excludes: [] more_excludes: [] hideWholePostExcerpts: true 完成部署 12$ hexo generate$ hexo deploy 这两条也可简写为 12$ hexo g$ hexo d 之后,在浏览器中键入 Ryokai96.github.io 即可访问博客 Hexo的同步使用Hexo搭建博客的同学可能会遇到这个问题:如果更换了设备,如何对Hexo博客进行管理? github或者gitee都有免费的私有仓库,使用私有仓库同步hexo代码,即可多设备同步hexo配置。 在github或gitee新建一个仓库,名字随便,比如blogcode 在blog目录下初始化git仓库 1$ git init 修改.git文件夹下的config文件 12$ cd .git$ vim config 修改config文件,添加以下配置,指定远程仓库 123456[remote \"origin\"] url = git@github.com:Ryokai96/blogcode.git fetch = +refs/heads/*:refs/remotes/origin/*[branch \"master\"] remote = origin merge = refs/heads/master 如果在新建远程仓库的时候生成了README文件,可以先git pull一下 推送 123$ git add .$ git commit -m \"hexo init\"$ git push origin master 注意:如果有包含的目录是其他的git仓库,比如git clone方式安装的next主题,在themes/next目录下有.git目录,是无法同步next目录的,这里也不能将其作为子仓库,解决方式有两种,一种是直接删除next目录下的.git目录,另外一种是将next目录下的.git目录以及.git目录中的HEAD文件改名,比如改成.git_backup和HEAD_backup。使用第二种改名的方式可以在当该git仓库有更新时将名字改回来,用git pull更新,这种方式是否有弊端,我还不清楚。 之后要更新博客,进入Hexo文件夹,hexo g & hexo d,要同步源文件,就在blog根目录提交修改 更换设备后或重装系统后,想管理博客,通过如下步骤: git clone git@github.com:Ryokai96/blogcode.git 在克隆过来的本地仓库ryoukai中执行以下指令 123$ npm install$ cd Hexo$ npm install 然后就可以像原来一样管理博客了 Hexo其他配置 使用Next主题:Hexo的Next主题配置、Next使用文档 提交百度谷歌收录:Hexo博客收录百度和谷歌-基于Next主题 让搜索引擎更容易找到你:Hexo SEO优化 另一个优秀主题Icarus:ppoffice/hexo-theme-icarus","link":"/%E5%9F%BA%E4%BA%8EHexo%E5%92%8CGithub%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/"},{"title":"在CentOS7上搭建ZooKeeper+Dubbo分布式服务","text":"环境 系统: CentOS 7.3 x64 JDK: JDK1.8 8u161 Ant: apache-ant-1.10.1 Maven: apache-maven-3.5.2 Tomcat: apache-tomcat-8.5.24 ZooKeeper: zookeeper 3.4.11 开始搭建安装ZooKeeper 解压zookeeper-3.4.11.tar.gz,移动到/opt目录 12tar zxvf zookeeper-3.4.11.tar.gzmv zookeeper-3.4.11 /opt/ 编辑配置文件 123456cd /opt/zookeeper-3.4.11/confcp zoo_sample.cfg zoo.cfgvim zoo.cfg# 替换下面内容dataDir=/tmp/zookeeper/datadataLogDir=/tmp/zookeeper/logs 创建zookeeper数据和日志目录 1234cd /tmpmkdir zookeepercd zookeepermkdir data logs 启动zookeeper 12cd /opt/zookeeper-3.4.11/bin./zkServer.sh start 下面是打包成rpm包安装方式,血泪史,可以不看跳过 下载必要的软件包 1sudo yum install -y rpm-build rpmdevtools mock 下载zookeeper-rpms: zookeeper-rpms包含了用于zookeeper构建RPM所需的一切,注意: 应下载用于zookeeper3.4.10的commit也就是58c775d这个commit,3.4.11的版本用这个仓库在centos7上有问题,暂未找到解决方法 然后按照说明进行 123456cd zookeeper-rpmsrpmdev-setuptreespectool -g zookeeper.specrpmbuild -bs --nodeps --define \"_sourcedir $(pwd)\" --define \"_srcrpmdir $(pwd)\" zookeeper.spec# 这里直接执行sudo mock zookeeper-3.4.10-1.src.rpm会失败,在zookeeper-rpms的Issues中找到解决方法,输入如下命令即可sudo mock -r epel-7-x86_64 --old-chroot zookeeper-3.4.10-1.src.rpm 打包好的文件在/var/lib/mock/epel-7-x86_64/result中 12cd /var/lib/mock/epel-7-x86_64/resultyum localinstall zookeeper-3.4.10-1.x86_64.rpm 然后就可以输入命令启动zookeeper 1zookeeper start 下面是自行打包rpm,最后失败告终,血泪史中的血泪史 123456789101112131415161718192021# zookeeper-3.4.11中包含的README_packaging.txt中所说,打包成rpm需要先安装cppunit和python-setuptoolsyum install cppunityum install python-setuptools# 然后执行ant rpm# 报错,在一个java文件中import了一个包,这个包找不到,包名忘了,谷歌无果,遂放弃zookeeper-3.4.11,下载zookeeper-3.4.10# 然后执行ant rpm# 同样报错,但不是一样的错误,好在能在谷歌上搜到出错原因,就是打包rpm还要依赖其他软件包,于是安装,安装后ant继续报错,继续谷歌,发现全是缺乏依赖的软件包,然后就报错->装软件->ant->报错->...循环,最后安装的软件列表如下gccgcc-g++autoconfautomakeautogencppunit-devellibtoolpython-develrpm-buildrpmdevtoolsmock# 最后还是报错,报文件找不到,放弃,谷歌寻找ZooKeeper打包rpm的方法,最后找到了https://github.com/skottler/zookeeper-rpms 运行dubbo-admin 解压dubbo-2.5.9.tar.gz,移动至/opt目录(我习惯放在/opt目录) 12tar zxvf dubbo-2.5.9.tar.gzmv dubbo-dubbo-2.5.9 /opt 在dubbo项目根目录,执行 1mvn install -Dmaven.skip.test 上一步执行后,会在dubbo-admin/target下生成war包,将其移动至tomcat目录 123456789# 在tomcat目录下新建dubbo-admin目录cd /opt/apache-tomcat-8.5.24/mkdir dubbo-admin# 将之前生成的war包移动到这里mv /opt/dubbo-dubbo-2.5.9/dubbo-admin/target/dubbo-admin-2.5.9.war ./# 解压,指定解压后的目录名为ROOTunzip dubbo-admin-2.5.9.war -d ROOT# 可以把war包删了rm -f dubbo-admin-2.5.9.war 编辑dubbo-admin的配置文件 123456cd ROOT/WEB-INF/vim dubbo.properties# 这里可以配置dubbo注册中心的类型及地址,dubbo的root用户和guest用户的密码dubbo.registry.address=zookeeper://127.0.0.1:2181dubbo.admin.root.password=rootdubbo.admin.guest.password=guest 编辑tomcat配置文件 12345678910111213141516171819202122232425cd /opt/apache-tomcat-8.5.24/confvim server.xml# 在<server></server>中添加一个Service节点<Service name=\"dubbo-admin\"> <Connector port=\"8081\" protocol=\"HTTP/1.1\" connectionTimeout=\"20000\" redirectPort=\"8443\" /> <Connector port=\"8010\" protocol=\"AJP/1.3\" redirectPort=\"8445\" /> <Engine name=\"Catalina\" defaultHost=\"localhost\"> <Realm className=\"org.apache.catalina.realm.LockOutRealm\"> <Realm className=\"org.apache.catalina.realm.UserDatabaseRealm\" resourceName=\"UserDatabase\"/> </Realm> <Host name=\"localhost\" appBase=\"dubbo-admin\" unpackWARs=\"true\" autoDeploy=\"true\"> <Valve className=\"org.apache.catalina.valves.AccessLogValve\" directory=\"logs\" prefix=\"localhost_access_log\" suffix=\".txt\" pattern=\"%h %l %u %t &quot;%r&quot; %s %b\" /> </Host> </Engine></Service> 启动tomcat 1/opt/apache-tomcat-8.5.24/bin/startup.sh 开放8081端口 123456789# 我使用的防火墙是firewalld,用iptables的自行网上搜开放端口的方法# 查看已经开放的端口firewall-cmd --list-ports # 若没有8081端口,开放它firewall-cmd --zone=public --add-port=8081/tcp --permanent# 重启防火墙firewall-cmd --reload# 查看已经开放的端口firewall-cmd --list-ports 浏览器访问ip:8081即可看见Dubbo Admin页面","link":"/%E5%9C%A8CentOS7%E4%B8%8A%E6%90%AD%E5%BB%BAZooKeeper-Dubbo%E5%88%86%E5%B8%83%E5%BC%8F%E6%9C%8D%E5%8A%A1/"},{"title":"Java代理模式小结","text":"问题提出如果由一个类User,其中有一个方法add(),如下 123456public class User { public void add() { System.out.println(\"add...\"); }} 如果要对add()方法扩展功能,比如在输出”add…”之前,先输出一句”before…”,在输出”add…”之后输出一句”after…”,那么就需要修改add()方法,如下 12345678public class User { public void add() { System.out.println(\"before...\"); System.out.println(\"add...\"); System.out.println(\"after...\"); }} 但是直接对add()方法进行修改,不利于系统维护,也容易产生代码的重复。比如在add()以及许多方法中,需要添加记录日志的功能,则需要在这些方法中写记录日志功能的逻辑,或者调用记录日志功能的方法,这会产生很多重复的代码,不利于模块的重用。 问题的解决方案为了解决如上问题,最早使用的方案是纵向继承机制 纵向继承机制纵向继承机制即继承于一个类,从而在方法中调用父类的方法,实现功能扩展,如下 123456789101112131415public class BaseUser { //记录日志的方法 public void writeLog() { ... }}public class User extends BaseUser { public void add() { ... //调用父类方法,实现功能扩展 super.writeLog(); }} 但是这种做法也有弊端,并不能根本上解决问题,比如对BaseUser类中的writeLog()方法进行修改,将其改名位recordLog()或添加参数,则User类中的add()方法也要进行修改。这样也是不利于维护的。 横向抽取机制(代理模式)为了从根本上解决问题,出现了代理模式,代理模式即为委托类创建代理类,通过代理类可以调用委托类的部分功能,并添加一些额外的业务处理。代理模式分为静态代理和动态代理 静态代理: 自己手动创建代理类,代理类在编译期间就已经确定 12345678910111213141516171819202122232425262728293031323334public interface Dao { public void add();}//委托类public class DaoImpl implements Dao { public void add() { System.out.println(\"add...\"); }}//代理类public class DaoProxy implements Dao { private DaoImpl daoImpl; public DaoProxy(DaoImpl daoImpl) { this.daoImpl = dapImpl; } public void add() { System.out.println(\"beagin\"); daoImpl.add(); System.out.println(\"after\"); }}//测试类public class ProxyTest { public static void main(String[] args) { DaoImpl daoImpl = new DaoImpl(); DaoProxy daoProxy = new DaoProxy(daoImpl); daoProxy.add(); }} 动态代理: 代理类不是由自己编写,而是在运行时期生成,下面详细介绍动态代理 动态代理动态代理分为jdk动态代理和cglib动态代理,Spring的AOP动态代理就是通过这两者实现的 jdk动态代理jdk动态代理即使用jdk中提供反射类Proxy和回调接口InvocationHandler实现jdk动态代理,并要求委托类必须实现至少一个接口 实现步骤: 定义业务逻辑 123456789public interface Dao { public void add();}public class DaoImpl implements Dao { public void add() { System.out.println(\"add...\"); }} 利用反射类Proxy和回调接口InvocationHandler实现jdk动态代理 123456789101112131415161718192021222324252627282930import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;class TestInvocationHandler implements InvocationHandler { private Object target; public TestInvocationHandler(Object target) { this.target = target; } //实现InvocationHandler接口的invoke方法,用于执行目标对象 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(\"before...\"); Object result = method.invoke(target, args); System.out.println(\"after...\"); return result; } //生成代理对象 public Object getProxy() { //定义代理类的加载者 ClassLoader loader = Thread.currentThread().getContextClassLoader(); //被代理对象实现的接口列表 Class<?>[] interfaces = target.getClass().getInterfaces(); //使用Java字节码技术生成代理对象并返回 return Proxy.newProxyInstance(loader, interfaces, this); }} 测试类 12345678910public class ProxyTest { public static void main(String[] args) { Dao dao = new DaoImpl(); TestInvocationHandler handler = new TestInvocationHandler(dao); //daoProxy就是生成的代理对象,它继承于Proxy类,且实现了Dao接口 Dao daoProxy = (Dao) handler.getProxy(); //执行add()方法,实际上是执行handler对象的invoke方法,传入的参数分别为当前代理对象,当前执行方法和方法参数 daoProxy.add(); }} cglib动态代理jdk动态代理需要实现类通过接口定义业务方法,对于没有接口的类,jdk毫无办法,这就需要cglib了。 cglib是一个强大、高性能、高质量的Code生成类库,它封装了asm(Java字节码操控框架) cglib通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑 由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理 实现步骤: 需要被代理的类 12345public class Dao { public void add() { System.out.println(\"add...\"); }} 方法拦截器 1234567891011121314151617181920212223import java.lang.reflect.Method;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;//方法拦截器public class TestProxy implements MethodInterceptor { //增强原有方法 @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { //前置代理 System.out.println(\"before...\"); Object result = proxy.invokeSuper(obj, args); //调用父类方法 //后置代理 System.out.println(\"end...\"); return result; }} 产生Dao代理类的工厂类 12345678910111213141516171819202122import net.sf.cglib.proxy.Enhancer;public class DaoFactory { private static Dao dao; static { dao = new Dao(); } public static Dao getDao() { return dao; } //获取代理类 public static Dao getProxyInstance(TestProxy proxy) { Enhancer en = new Enhancer(); en.setSuperclass(Dao.class); en.setCallback(proxy); return (Dao) en.create(); }} 测试 123456public class Test { public static void main(String[] args) { Dao dao = DaoFactory.getProxyInstance(new TestProxy()); dao.add(); }} Spring中AOP的代理方式 如果目标对象实现了接口,默认情况下会采用jdk动态代理实现AOP 如果目标对象实现了接口,可以强制使用cglib动态代理实现AOP 强制使用cglib实现方式: 添加cglib库 在Spring配置文件中加入 1<aop:aspectj-autoproxy proxy-target-class=\"true\"/> 如果目标对象没有实现接口,AOP必须由cglib动态代理实现 Spring会自动在jdk动态代理和cglib动态代理之间切换","link":"/Java%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F%E5%B0%8F%E7%BB%93/"}],"tags":[{"name":"Java","slug":"Java","link":"/tags/Java/"},{"name":"C","slug":"C","link":"/tags/C/"},{"name":"Hexo","slug":"Hexo","link":"/tags/Hexo/"},{"name":"ZooKeeper","slug":"ZooKeeper","link":"/tags/ZooKeeper/"},{"name":"Dubbo","slug":"Dubbo","link":"/tags/Dubbo/"}],"categories":[{"name":"技术","slug":"技术","link":"/categories/%E6%8A%80%E6%9C%AF/"},{"name":"C","slug":"技术/C","link":"/categories/%E6%8A%80%E6%9C%AF/C/"},{"name":"Java","slug":"技术/Java","link":"/categories/%E6%8A%80%E6%9C%AF/Java/"},{"name":"Hexo","slug":"技术/Hexo","link":"/categories/%E6%8A%80%E6%9C%AF/Hexo/"},{"name":"Dubbo","slug":"技术/Dubbo","link":"/categories/%E6%8A%80%E6%9C%AF/Dubbo/"}]}