java 代理模式-静态代理与动态代理
最近在研究SpringAOP,当然要学习AOP就要知道这么健硕、强大的功能的背后究竟隐藏着怎样不可告人的“秘密”??
接下来就是查阅了许多资料详细的研究了一下Java的代理模式,感觉还是非常非常重要的,
我们作为一个有“内涵的”程序员就更应该掌握啦!(本文需要细心、带有审视的目光来甄别其中的内容)
在学习代理模式的时候我首先要提出几个问题,
1、什么是代理模式?
举个例子吧:我们生活中的租房问题。假如我们去租个房子,我们大多数情况下是不会知道房主(就是真正租房,一手货源)的,我们是不是都是先去某些租房平台,或者去找当地的中介去询问何时的房子。我们通过九牛二虎之力在中介那里找到了个物美价廉的房子后,你的租金是不是交给了中介,中介还会收取一些额外的推荐费啦,押金啦、手续费等之类的,那么好,这样的一小段,就已经出来了其中两大核心对象了。
房主(把房子交给中介的人):被代理对象
中介(租给你房子的人):代理对象
2、代理模式有哪些作用?
1.可以隐藏目标的的具体实现(还是拿上面租房的例子来说,房主把房子交给了中介,并和中介谈好了价格我 7你3啊。然后当我们去租房子的时候,是中介正面把房子租给了我们,而真正背后卖房子的并未出面,这样就隐藏了背后人的信息和提高了背后人的安全)
2.可以在不修改目标类代码的情况下,对其增加新的功能。(上面例子来说:房东把房子交给中介的时候价格可能只有1000,但是房东可以卖到5000,然后卖出去后在把1000给房东,自己收入4000,这样原房东不但收到了应由的钱,中介还收入了更多的额外费用)。
3、代理模式有哪几种?分别都有什么不同?
在我们Java程序中代理模式分为:静态代理和动态代理(动态代理又分为:JDK动态代理和CGLIB动态代理)
至于什么不同,接下来正式我们着重要学习的内容
静态代理
什么是静态代理呢?
- 顾名思义,就是静态的,死的,一旦创建了就不允许修改了或者说很难修改(指的是程序运行期间)
- 专业一些的解释
- 如若代理类在程序运行之前就已经存在,那么这种代理方式被称为静态代理。这种情况下的代理类通常都是我们在Java代码中定义的。
- 通常情况下静态代理类和目标类通常都会实现同一个接口或者派生自同一父类
具体实现:
被代理类与代理类共同实现的接口
package cn.arebirth.staticproxy; /**
* 需要实现的共同接口
* 因为要保证代理类要不改变被代理类原来功能的基础上增加新的功能
*/
public interface RentalHouse {
/**
* 出租房子
*/
void rent();
}
被代理类(房东)
package cn.arebirth.staticproxy; /**
* 房东(目标类、被代理类)
*/
public class Host implements RentalHouse { @Override
public void rent() {
System.out.println("我是房东,出租500平米的大房子");
}
}
代理类(中介)
package cn.arebirth.staticproxy; /**
* 静态代理类(中介、代理类)
* 注意:需要被代理类实现相同的接口
*/
public class StaticProxy implements RentalHouse {
private RentalHouse rentalHouse; public StaticProxy(RentalHouse rentalHouse) {
this.rentalHouse = rentalHouse;
} @Override
public void rent() {
System.out.println("我是中介,收你500推荐费");
//调用被代理类的租房方法
rentalHouse.rent();
System.out.println("我是中介,我又想收你1000块钱!");
}
}
测试类
package cn.arebirth.staticproxy; public class Test {
public static void main(String[] args) {
//创建被代理度下行
Host host = new Host();
/**
* 创建代理对象,并把被代理对象传递给代理对象,
* 因为他们都实现相同的接口,实现了相同的方法,这样的话传递的对象可以是房东1 房东2 ...
*/
StaticProxy proxy = new StaticProxy(host);
proxy.rent();
}
} 输出结果:
我是中介,收你500推荐费
我是房东,出租500平米的大房子
我是中介,我又想收你1000块钱!
试想一下,如果有两个房东,三个,四个,甚至更多个房东的话,我们怎么写?
被代理类
package cn.arebirth.staticproxy; /**
* 房东1(目标类、被代理类)
*/
public class Host1 implements RentalHouse { @Override
public void rent() {
System.out.println("我是房东1,出租500平米的大房子");
}
} package cn.arebirth.staticproxy; /**
* 房东2(目标类、被代理类)
*/
public class Host2 implements RentalHouse { @Override
public void rent() {
System.out.println("我是房东2,出租500平米的大房子");
}
}
代理类(中介)
package cn.arebirth.staticproxy; /**
* 静态代理类(中介、代理类)
* 注意:需要被代理类实现相同的接口
*/
public class StaticProxy implements RentalHouse{
//什么价位的房子
private int moneuy; public Agent(int moneuy) {
this.moneuy = moneuy;
} @Override
public void renting() {
//出租房东的租房
//中介调用的租房方法仍然是房东的租房方法
System.out.println("收取50元推荐费");
if (moneuy <= 800) {//金额小于等于800的时候
Host1 host = new Host1();
host.rent();
} else {
Host2 host = new Host2();
host.rent();
} System.out.println("收取500元押金费用");
}
}
测试类
public class Test{
public static void main(String[] args) {
StaticProxy proxy= new StaticProxy(1000);
proxy.renting();
}
} 输出结果:
我是房东2,出租500平米的大房子
静态代理的缺点:
我们仔细来观察下,随着我们的被代理对象的增多,也是就是房东越来越多,那我们的被代理类就会越来越冗余,中介的压力也就会越来越大。
动态代理
常用的动态代理又分为JDK动态代理和CGLIB动态代理
那么两者的使用场景又是什么呢??
如果目标对象实现了接口,就是上面我们举到的例子,房东和中介共同实现的接口类似,这样的话就采用JDK动态代理
如果目标对象没有实现接口,必须采用CGLIB动态代理
具体实现:
要实现的共同接口
package cn.arebirth.jdkproxy; /**
* 需要实现的共同接口
*/
public interface RentalHouse {
/**
* 出租房子
*/
void rent();
}
房东(被代理对象)
package cn.arebirth.jdkproxy; /**
* 房东(目标类、被代理类)
*/
public class Host implements RentalHouse { @Override
public void rent() {
System.out.println("我是房东,出租500平米的大房子");
}
}
核心来了!(JDK动态代理实现类)package cn.arebirth.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* JDK动态代理类
*/
public class JdkProxy implements InvocationHandler {
//接收要被代理的类
RentalHouse rentalHouse; public JdkProxy(RentalHouse rentalHouse) {
this.rentalHouse = rentalHouse;
} /**
* @return 执行该方法就会产生代理类
*/
public Object getProxy() {
/**
* Proxy.getProxyInstance(param1,param2,para3);
* 参数一:传入一个classLoader()对象
* 参数二:传入被代理类所实现的接口的数组,因为代理类底层要根据接口产生
* 参数三:参数类型是InvocationHandler即可,this即当前类,我们当前类实现了此接口
*/
Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{RentalHouse.class}, this);
return proxy;
} /**
* 这个方法类似于上面我所讲述的静态代理模式里面的中介类的rent()方法
* @param proxy 代理对象--中介
* @param method 代理对象中的方法
* @param args 代理对象方法中的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是中介,收你500推荐费");
Object invoke = method.invoke(rentalHouse, args);
System.out.println("我是中介,我又想收你1000块钱!"); //返回执行方法后的返回值
return invoke;
}
}
测试类
package cn.arebirth.jdkproxy; public class Test {
public static void main(String[] args) {
//创建JdkProxy动态代理对象类,并把需要被代理的对象传递进去
JdkProxy jdkProxy = new JdkProxy(new Host()); //获得代理类 这里一定要写他们共同实现的接口 利用java多态的特性,如果直接写真实类型是会报错的
RentalHouse proxy= (RentalHouse)jdkProxy.getProxy();
proxy.rent();
}
} 输出结果:
我是中介,收你500推荐费
我是房东,出租500平米的大房子
我是中介,我又想收你1000块钱!
我想当你看到这里一定会感到困惑,为什么我们获取到了代理对象后执行的执行代理对象的方法,明明是房东的方法,怎么显示的好像是JDK动态代理类里面的invoke()方法??或许还有其他的困惑,我们将在下边一一讲解如何实现的原理。
既然想知道如何实现的,那么我们就要从底层出发,来看看,底层的Proxy代理到底帮我们生成了一个怎样的代理类。
再开始之前我希望接下来的代码与操作你是跟着我同步进行的,这样子才会更深刻有更好的理解,当然不排除你是个人脑机器模拟器
开始吧!
我们想要看底层如何实现的,那么我们首先就要获得代理类的class文件
下面是我写的一个获取JDKProxy动态代理所生成的代理文件的工具类
package cn.arebirth.jdkproxy; import sun.misc.ProxyGenerator; import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException; public class ProxyUtil {
/**
* @param proxyName 生成文件代理类的名字
* @param interClass 代理类所实现的接口的class数组形式
* @param path 写出路径
*/
public static void writeProxyClassToHardDisk(String proxyName, Class[] interClass, String path) {
byte[] bytes = ProxyGenerator.generateProxyClass(proxyName, interClass); FileOutputStream out = null; try {
out = new FileOutputStream(path + File.separator + proxyName+".class");
out.write(bytes);
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
然后我们在测试类里面使用它
package cn.arebirth.jdkproxy; public class Test {
public static void main(String[] args) {
//创建JdkProxy动态代理对象类,并把需要被代理的对象传递进去
JdkProxy jdkProxy = new JdkProxy(new Host()); //获得代理类
RentalHouse proxy = (RentalHouse) jdkProxy.getProxy();
proxy.rent();
//写入JDK动态代理生成的代理类
ProxyUtil.writeProxyClassToHardDisk(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces(), "F:\\Tools\\DevelopmentTools");
}
}
最后生成出来的代理类是一个class字节码文件,我们需要使用反编译工具来查看,我使用的是Luten(这个工具打开方式特殊,百度自行查找)
import cn.arebirth.jdkproxy.*;
import java.lang.reflect.*; public final class $Proxy0 extends Proxy implements RentalHouse
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0; public $Proxy0(final InvocationHandler invocationHandler) {
super(invocationHandler);
} public final boolean equals(final Object o) {
try {
return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
} public final void rent() {//这个就是编译器自动为我们实现了的接口中的方法
try {
super.h.invoke(this, $Proxy0.m3, null); //并调用了invoke
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
} public final String toString() {
try {
return (String)super.h.invoke(this, $Proxy0.m2, null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
} public final int hashCode() {
try {
return (int)super.h.invoke(this, $Proxy0.m0, null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
//在类第一次加载的时候,给全局变量进行初始化,看看里面有没有我们稍微眼熟的东西
static {
try {
$Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
$Proxy0.m3 = Class.forName("cn.arebirth.jdkproxy.RentalHouse").getMethod("rent", (Class<?>[])new Class[0]);//这里就是我们所实现的接口中的方法
$Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
$Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
}
catch (NoSuchMethodException ex) {
throw new NoSuchMethodError(ex.getMessage());
}
catch (ClassNotFoundException ex2) {
throw new NoClassDefFoundError(ex2.getMessage());
}
}
}
在看到这里的时候,我们大概能了解到,原来我们在使用我们代理类调用租房的方法的时候,它里面并没有我们写的代码而是执行了一个
super.h.invoke(this, $Proxy0.m3, null);
那么super是谁?就是它自动继承的Proxy类
那么h是什么?就是Proxy类的InvocationHandler
那么我们看这个InvocationHandler类是不是感觉有那么一丢丢的眼熟啊,回过头看,这就是我们在写JDK动态代理类的时候实现的那个接口。
!!!!!!!!!!!!!
然后它又调用了invoke(this, $Proxy0.m3, null);
第一个参数:代理类对象
第二个参数:要执行的代理类对象的方 Method对象,这个Method对象的值已经由我们的编译器帮我们构建好了,我们只需要第一个次加载这个类的时候他就会自动赋值了(static代码块)
第三个参数:方法里面的参数,当没有参数的时候就是NULL
带着你的疑惑,从头在开始好好敲一遍代码,捋一遍,相信我,你将有更大的收货!!
下面是简略画的思路图,试着用画图来画出你的思维
那么说完了这个JDK动态代理,我们有没有发现什么缺点??找茬时间开始
是不是这个JDK动态代理必须要依赖接口才能实现,如果没有接口的话,那么JDK动态代理也就凉凉了对吧,!
那么好,接下来我们将说一种更加强大的动态代理方式CGLIB,它的实现原理只要我们懂了JDK动态代理,那么下面的就是小儿科啦哈哈~
CGLIB的代理将不再需要接口也可以生成代理类,但是它需要导包!
当然,你也可以选择其他版本的JAR包!
===
开始
被代理类
package cn.arebirth.cglibproxy; /**
* 被代理类
*/
public class Host {
public void rent() {
System.out.println("Host:rental house");
}
}
生成代理对象的cglib类
package cn.arebirth.cglibproxy; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxy implements MethodInterceptor { /**
* 获取代理类
*
* @return
*/
public Object getProxy() {
Enhancer enhancer = new Enhancer(); //设置被代理对象
enhancer.setSuperclass(Host.class);
//设置回调方法 当前对象
enhancer.setCallback(this); //创建代理对象
Object o = enhancer.create();
return o;
} /**
* @param o 被代理对象
* @param method 被代理对象方法
* @param objects 被代理对象方法中的参数
* @param methodProxy 代理类中的方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("中介收取手续费");
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("中介收取中介费");
return o1;
}
}
测试类
package cn.arebirth.cglibproxy; public class Test {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
Host proxy = (Host) cglibProxy.getProxy();//强制转换为我们的房主
proxy.rent();
}
} 输出结果:
中介收取手续费
Host:rental house
中介收取中介费
我们动态代理的最大好处就是,可以在没有接口的情况下,只有一个类,我们就可以动态的代理,在程序运行的时候动态的为他创建代理类
最后让我们大概的总结下:
代理模式:
静态代理
动态代理:JDK动态代理 CGLIB动态代理
代理模式的三个要素
A.抽象的类或接口 完成一件怎样的事情
B 被代理对象 事情操作具体内容
C 代理对象 帮助我们完成事情的同时 可以增加其他的东西
具体的列子:我们找中介租房子
A 抽象的类或者接口 租房子
B 被代理对象 房东
C 代理对象 中介
代理模式的好处
A 房东可以安心的做自己的事情 (被代理对象可以做自己的事情)
B 我们有了问题可以直接找中介 (被代理对象变得比较安全)
C 可以增强代码的扩展性
JDK动态代理和CGLIB动态代理的使用场景
我们可以这样记一下,只要被代理类没有实现接口,我们就必须使用CGLIB动态代理
java 代理模式-静态代理与动态代理的更多相关文章
- 代理模式(Proxy)--动态代理(CGLIB)
上一篇:代理模式(Proxy)--动态代理(jdk) (1)CGLIB技术是第三方代理技术,可以对任何类生成代理,代理的原则是对目标对象进行继承代理 (2)如果目标对象被final修饰,则无法被CGL ...
- 代理模式(Proxy)--动态代理(JDK)
在是上一篇博客中实现了静态代理. 在上篇的结尾提到了一个问题: 思考:如果我们下需要对火车,自行车实现相同的代理,我们又该如何实现呢? 这篇博客就来解决这个问题: 解决这类问题需要用到动态代理技术,实 ...
- 【Java】代理模式、反射机制-动态代理
关于代理模式和动态代理参考自:https://www.cnblogs.com/gonjan-blog/p/6685611.html 这里通过参考博客中的例子整理个人理解. 代理模式: 访问某个类的方法 ...
- Java 代理模式 (二) 动态代理
代理模式 代理(Proxy)是一种设计模式, 提供了对目标对象另外的访问方式:即通过代理访问目标对象. 这样好处: 可以在目标对象实现的基础上,增强额外的功能操作.(扩展目标对象的功能). 代理模式的 ...
- 【Java】代处理?代理模式 - 静态代理,动态代理
>不用代理 有时候,我希望在一些方法前后都打印一些日志,于是有了如下代码. 这是一个处理float类型加法的方法,我想在调用它前打印一下参数,调用后打印下计算结果.(至于为什么不直接用+号运算, ...
- 代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)
代理模式 代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问. 那么为什么要使用代理模式呢? 1.隔离,客户端 ...
- 代理模式 静态代理、JDK动态代理、Cglib动态代理
1 代理模式 使用代理模式时必须让代理类和被代理类实现相同的接口: 客户端通过代理类对象来调用被代理对象方法时,代理类对象会将所有方法的调用分派到被代理对象上进行反射执行: 在分派的过程中还可以添加前 ...
- java 设计模式之单利模式以及代理模式(静态)
1:单利模式: public class Singleton { private static Singleton uniqueInstance = null; private Singleton() ...
- 动态代理的两种方式,以及区别(静态代理、JDK与CGLIB动态代理、AOP+IoC)
Spring学习总结(二)——静态代理.JDK与CGLIB动态代理.AOP+IoC 目录 一.为什么需要代理模式 二.静态代理 三.动态代理,使用JDK内置的Proxy实现 四.动态代理,使用cg ...
随机推荐
- Python基础总结之初步认识---class类的继承(下)。第十五天开始(新手可相互督促
年薪百万的步伐慢了两天hhhh严格意义是三天.最近买了新的玩具,在家玩玩玩!~~~~ 今天开始正式认识类的继承.类的继承是怎么继承呢?看下代码: class Animal(object): #父类 d ...
- Switch分销技术解读
Switch分销技术解读 来源:环球旅讯|2009-03-13 当Switch在海外成熟运作近40年后,该业务终于进入中国市场.但对于中国业者来说,知道Switch的人很少,了解Switch的人更少. ...
- Redis简单梳理及集群配置
**REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统. Redis是一个开源的使用ANSI C语言编写.遵 ...
- xlistview错误
apply plugin: 'com.android.library' android { compileSdkVersion buildToolsVersion '26.0.1' defaultCo ...
- 第一次亲密接触——二狗子初识 CDN
二狗子是国内知名XXX大学的在校学生,作为一名编程爱好者,他利用业余时间搭建了一个网站,把平时的学习心得和技术分享全都 PO 在自己的网站上.渐渐地,二狗子的网站因为文章质量高,技术分享全面,受到了很 ...
- 对博弈活动中蕴含的信息论原理的讨论,以及从熵角度看不同词素抽象方式在WEBSHELL文本检测中的效果区别
1. 从赛马说起 0x1:赛马问题场景介绍 假设在一场赛马中有m匹马参赛,令第i匹参赛马获胜的概率为pi,如果第i匹马获胜,那么机会收益为oi比1,即在第i匹马上每投资一美元,如果赢了,会得到oi美元 ...
- ReactJS:最大更新深度超出错误
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside com ...
- iOS学习——iOS 宏(define)与常量(const)的正确使用
概述 在iOS开发中,经常用到宏定义,或用const修饰一些数据类型,经常有开发者不知怎么正确使用,导致项目中乱用宏与const修饰.你能区分下面的吗?知道什么时候用吗? #define HSCode ...
- Coablt strike官方教程中文版
安装和设置 系统要求 Cobalt Strike的最低系统要求 2 GHz +以上的cpu 2 GB RAM 500MB +可用空间 在Amazon的EC2上,至少使用较高核数的CPU(c1.medi ...
- 利用QGIS下载地图数据
这段时间做了一些利用地理信息进行定位导航的系列工作,其中很重要的一部分是如何获取到地图数据,比如道路的矢量图.某一区域的栅格图,我用到的主要工具是QGIS.QGIS是一个跨平台的免费应用,其中集成了对 ...