目录
1.静态代理代码示例
先来个静态代理示例代码热热身,假如我要玩一个游戏,那么就可能需要以下几个步骤,登录,打怪,升级,那么我们就需要一个打游戏的接口了IGamePlayer。
public interface IGamePlayer {
// 登录
void login(String user, String password);
// 打怪
void killBoss();
// 升级
void upgrade();
}
那有接口就得有实现类把,来实现我说的登录、打怪、与升级对把,在这里就演示一下示例,输出是谁登录,是谁打游戏,是谁升级的。
public class GamePlayer implements IGamePlayer {
private String name;
public GamePlayer(String name) {
this.name = name;
}
@Override
public void login(String user, String password) {
System.out.println(user + "登录成功!");
}
@Override
public void killBoss() {
System.out.println(this.name + "在打怪!");
}
@Override
public void upgrade() {
System.out.println(this.name + "升级成功!");
}
}
然后用测试类看看输出
public static void main(String[] args) {
IGamePlayer gamePlayer = new GamePlayer("杜大侠");
gamePlayer.login("dd","123456");
gamePlayer.killBoss();
gamePlayer.upgrade();
}
那么假如现在我在上班,我又想我的游戏升级怎么办呢,对,找人帮我打,那么程序怎么实现呢?需要有一个代理类,帮我打游戏的代理类,而且是帮我打,所以登录是我的账号,最后升级什么的都得是我的
public class GamePlayerProxy implements IGamePlayer {
// 他帮得是给我打游戏,所以需要知道玩家,持有对玩家得引用
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
@Override
public void login(String user, String password) {
// 因为给我打游戏要登录我的账号
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
测试一下输出,发现和跟我自己玩的时候输出是一模一样的
public static void main(String[] args) {
// 被代理对象的资源
IGamePlayer gamePlayer = new GamePlayer("杜大侠");
// 代理对象,把我的引用传给代理类,让它帮助我
IGamePlayer gameProxy = new GamePlayerProxy(gamePlayer);
gameProxy.login("dd", "123456");
gameProxy.killBoss();
gamePlayer.upgrade();
}
那么上面代码示例静态代理模式会有问题,不好扩展不好维护,假如此时IGamePlayer接口又添加其他方法,那么GamePlayerProxy代理类也是要添加方法的,那假如原方法的参数改变了或者返回值改变了那么我们的GamePlayerProxy代理类也需要相应的改变,如果被代理对象再加一些额外的功能,但是代理对象类又不需要做,那么GamePlayerProxy代理类还必须要实现方法,这样的设计很头疼啊
那么再假如代理人帮我又打游戏,又要帮我挂qq,那么我是不是要既实现打游戏,又要实现挂qq类呢,那样代理类就会变得很臃肿,而且如果还有其他各种各有需要代理类干的活,那么就证明还会有无数的类,那就会造成类文件的膨胀。
综上所述,静态代理能帮助我们实现代理方式,但是还是有很多不足的,我们只是希望代理类帮我们做额外我们想做的事情,例如aop那种方式记录日志那种,那我们可不可以实现那种自动的生成的类,代码运行时生成的代理类,满足这两种就是动态代理,那么jdk就已经开发好了动态代理。我们来实现一下
2.动态代理代码示例
代理类要用动态代理的话一定要实现InvocationHandler,然后还是要有被代理对象的引用IGamePlayer和构造方法帮助我们传递被代理类的信息,那么实现一下在打怪之前先穿一下衣服把
public class DynamicProxyHandler implements InvocationHandler {
private IGamePlayer gamePlayer = null;
public DynamicProxyHandler(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
/**
* 那么现在在打怪之前想让它穿一个装备
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在打怪之前戴上装备,所以就需要判断是否是打怪方法
if (method.getName().equals("killBoss")) {
System.out.println("戴上麻痹戒指!");
}
// 调用被代理对象的方法
return method.invoke(gamePlayer, args);
}
}
测试输出信息
public static void main(String[] args) {
IGamePlayer gamePlayer = new GamePlayer("杜大侠");
InvocationHandler handler = new DynamicProxyHandler(gamePlayer);
IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(gamePlayer.getClass().getClassLoader(),
new Class[]{IGamePlayer.class}, handler);
System.out.println(proxy.getClass());
proxy.login("dd", "123456");
proxy.killBoss();
proxy.upgrade();
}
为其他对象提供一种代理以控制对这个对象的访问,也就是说我们不能做或者不想做的事情都可以委托他人来帮我们做,受益人是我们自己,在生活中有什么例子是代理模式呢,外卖、黄牛买票,黄牛买票最终也是我们自己坐车,外卖小哥帮你把外卖送到,也是你自己吃,代理模式是代理人帮你做一些事,最终体现都是在被代理人那里
可以了,简单的代码就能操作我们想要的结果,这样就简化多了,但是这个Proxy类做什么了呢,我们来输出一下Proxy,发现是$Proxy0的类名称,很奇怪
System.out.println(proxy.getClass());
3.查看jdk动态代理类Class文件
那么代码的动态代理演示完了,我们在来在main方法把这个类文件输出到磁盘里,在项目的根目录就会有这样的文件和Class类输出变量就会在相应的目录显示动态代理生成的类。
// 输出变量
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
所有动态生成的类都继承Proxy
类里面有m0-m5的生成的方法
静态代码块里会把这几个m0-m5的都对应起来了,并且object对象的toString,hashCode啊,都是类必须要有的
还有我刚实现的自定义的方法,也都有相应的体现
咱们找下m5调用的体现,killBoss不就是我们打怪的接口么,然后h好熟悉啊,这个不就是我们代理测试实现的那个接口么,super就是调用父类,这里它的父类就是Proxy类,再调用invoke,invoke就是我们下图图三我们自己实现代理类而调用的方法,调用增强升级功能代码完毕,再调用
method.invoke(gamePlayer, args);被代理类的方法
4.代理类流程
Client假如就是main方法,doSomething就是调用任何方法它都会生成$Proxy类,$Proxy类调用invoke方法,走到我们自己定义的任何代理类方法调用invoke开始做增强代码逻辑的操作,然后再调用被代理对象的方法。
5.代理类源码介绍
看源码最好用ctrl + alt+b 哦!
那么这个jdk生成的代理类是用什么技术实现生成完让jvm直接能用呢?就是反射,大概看下源码
首先对我们传进来的接口InvocationHandler全部克隆了一遍
生成一个代理类class对象
根据代理类对象cl获取构造器
根据构造器和InvocationHandler实现类,创建代理类实例
核心就是Class<?> cl = getProxyClass0(loader, intfs);我们点进去看一下实现
接口定义了类不能超过65535,它怎么去拿class类对象的呢?
return proxyClassCache.get(loader, interfaces) 这里讲如果说缓存已经存在直接返回,否则用ProxyClassFactory创建。为什么有缓存呢,就是提高我们查找类的效率
接着来看下ProxyClassFactory,也是Proxy的内部类,首先代理名字前缀定义好了,很熟悉啊$Proxy,然后代理类名字计数器,从0开始,因为可能有多个代理类
真正创建class对象就是apply方法,首先进入到这个方法都是一些验证工作,跳过
验证所有的非public接口是不是在同一个package,如果是非public的,使用的包就是和被代理类一样的包名
如果都是public接口的话,包名统一都是com.sun.proxy
然后包名+类名前缀+序号生成代理类的名字
生成class字节码的文件
把代理类的字节码加载到jvm中,返回代理类class对象,是一个native方法
我们来看一下class字节码文件,分为4大类
generateClassFile主要就是把所有的方法组装成ProxyMethod对象, ProxyMethod对象就是方法名称、参数、返回类型等等的信息
还会添加从Object添加继承的方法
还会添加接口自定义的方法
第二步就是获得所有的字段和方法信息
第三步就是写入字节码(class)文件
写入jvm识别的方式,按照jvm规范书写的方式
6.静态代理和动态代理
静态带来是必须要有java源文件,通过java编译器转换为.calss文件,通过转换转为byte类加载器再进行加载。
动态代理直接再运行时就生成byte文件直接通过类加载器进行加载
还有一点jdk动态代理要求必须是实现接口那种方式,否则不能进行动态代理
Cglib支持不是接口的类能进行动态代理,但是Cglib不能代理被final修饰的方法
Springboot 2.x版本spring已经添加了cglib,如果是实现接口类型的就用jdk动态代理,如果不是接口类型就用Cglib
7.Mybatis动态代理的使用
7.1 mapper语句
这是一段Mybatis的一个查询数据库数据的代码,通过mapper找到配置文件的sql语句,执行sql语句获取数据,但是mapper.selectBlogById()它是没有实现类的,那是怎么实现操作方法的呢
打印一下类信息,发现此时的mapper其实是动态代理出来的类了
发现输出的是jdk的动态代理
那MyBatis中的hadler是什么呢,是MapperProxy里的invoke方法,也就是说能够不实现接口就直接调用来运行sql语句是通过了jdk动态代理实现的,sql操作都在mapperMethod里的execute里实现的
7.2 Mybatis插件
Mybatis拦截器啊,分页插件啊等一些其他插件都是使用了动态代理,那么mybats里有专门的Plugin代理类, 下图二Plugin类的invoke
7.3 Mybatis的连接池
如果不指定spring管理数据库连接,mybatis也是有连接池的,而池的操作mybatis也是采用动态代理,为什么呢,因为connection的连接自己是不可能把自己还给池子里的,因为connection本身没有池啊!那么只能通过代理方式增强连接池的功能,代理类帮它把connection放回池操作等等。
invoke里的实现就是假如你要释放连接,那么判断方法是不是CLOSE,然后把连接放入dataSource容器,最后return也都是调回被代理方法的本身操作的方法
7.4 Mybatis的日志
Mybatis会打印执行sql日记,肯定不能在业务里写,所以也需要代理模式
ConnectionLogger打印日志
随随便便就能找到Mybatis这么多代理实现方式,其实还有很多,,生成语句啊,结果啊等等。以上就是今天全部内容!