简介
代理模式(Proxy),为其他对象提供一种代理,以控制对这个对象的访问。
代理模式一般具有以下三个特点:1) 需要具有两个角色,执行者与被代理人; 2)对于被代理人来说,这件事情是一定要做的,但是我自己又不想做或者没有时间做,找代理 ; 3)需要获取到被代理的人个人资料;
代理模式,归根到底做的是字节码重组。它可以在每一个方法调用之前加一些代码,在方法调用之后再加一些代码。
UML结构图
Subject类,定义了 RealSubject和Proxy的共用接口,这样就可以在任何使用RealSubject的地方使用Proxy。
abstract class Subject
{
public abstract void Request();
}
RealSubject类,定义Proxy所代表的真实实体
class RealSubjec : Subject{
public override void Request() {
console.WriteLine("真实的请求");
}
}
Proxy类,保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。
class Proxy : Subject {
RealSubject realSubject;
public override void Request() {
if(realSubject == null) {
realSubject = new RealSubject();
}
realSubject.Request();
}
}
客户端代码
static void Main(string[] args) {
Proxy proxy = new Proxy();
proxy.Request();
Console.Read();
}
应用场景
比如租房中介场景中的中介与你、代买火车票场景中的黄牛与你、通过媒婆找对象场景中的媒婆与你。
在我们经常使用的Spring框架中,通过AOP实现的事物代理就是通过代理模式实现的。
动态代理实战
这里我们实现的是动态代理。在java中,动态代理分两种实现方式,一种是jdk的动态代理,一种是cglib的动态代理。
jdk动态代理
JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的 业务实现类对象 以及 方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。
首先,我们要定义一个接口,来声明被代理人要执行的操作。
public interface Person {
void findLove();
String getSex();
String getName();
}
其次,被代理人要实现这个接口,具体要怎么做。
public class Xingxing implements Person{
private String sex = "女";
private String name = "xingxing";
public void findLove() {
System.out.println("我叫:" + name +",性别:"+ sex+",我找对象的要求是:");
System.out.println("身高要求180cm以上,体重70kg");
System.out.println("有房有车的");
System.out.println("高富帅");
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
再者,代理要持有被代理人的引用,执行代理操作。
public class Meipo implements InvocationHandler{
private Person target;
// 获取被代理人的个人资料
public Object getInstance(Person target) throws Exception {
this.target = target;
Class clazz =target.getClass();
System.out.println("被代理对象的class是:" + clazz);
return Proxy.newProxyInstance(target.getClass().getClassLoader(),clazz.getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是媒婆:您的性别是:" + this.target.getSex() +"得给您找个异性才行");
System.out.println("开始进行海选.....");
System.out.println("-------------");
method.invoke(this.target, args);
System.out.println("-----------");
System.out.println("如果合适的话,就准备办事");
return null;
}
}
最后,客户端调用代理,实现被代理人的请求。
public static void main(String[] args) {
// 直接调用
// new Xingxing().findLove();
// 通过jdk代理实现
try {
Person obj = (Person)new Meipo().getInstance(new Xingxing());
System.out.println(obj.getClass());
obj.findLove();
//原理
//1. 拿到被代理对象的引用,然后获取他的接口
//2. JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口
//3. 把被代理对象的引用也拿到了
//4. 重新动态生成一个class字节码
//5. 然后编译
// 获取字节码内容,并输出
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{obj.getClass()});
FileOutputStream os = new FileOutputStream("$Proxy0.class");
os.write(data);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
JDK动态代理的代理对象在创建时,需要使用业务实现类所实现的接口作为参数(因为在后面代理方法时需要根据接口内的方法名进行调用)。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。
cglib动态代理
cglib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。
public class Zhangsan {
public void findLove() {
System.out.println("肤白貌美大长腿");
}
}
接口,看下代理类的实现
public class YMMeipo implements MethodInterceptor {
public Object getInstance(Class clazz) throws Exception {
// 通过反射机制,给他实例化
Enhancer enhancer = new Enhancer();
// 把父类设置为谁
// 这一步就是告诉cglib,生成的子类需要继承哪个类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 第一步 生成源代码
// 第二步 编译成class文件
// 第三步 加载到jvm中,并返回被代理对象
return enhancer.create();
}
// 同样是做了字节码重组这样一件事
// 对于使用API的用户来说,无感知
@Override
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我是媒婆:得给您找个异性才行");
System.out.println("开始进行海选.....");
System.out.println("-------------");
// 这个object的引用是由cglib给我们new出来的
//cglib new 出来以后的对象,是被代理对象的子类(继承了我们自己写的类)
//oop,在new子类之前,实际上默认先调用了我们super()方法的
// new子类的同时,必须先new出来父类,这就相当于是间接的持有了我们父类的引用
//子类重写了父类的所有的方法
//我们改变子类对象的某些属性,是可以间接的操作到父类的属性的
methodProxy.invokeSuper(obj, objects);
System.out.println("-----------");
System.out.println("如果合适的话,就准备办事");
return null;
}
}
最后,客户端调用这样实现就完成了。
public static void main(String[] args) {
// JDK动态代理是通过接口来进行强制转换的
// 生成以后的代理对象,可以强制转换为接口
// CGLib的动态代理是通过生成一个被代理对象的子类,然后重写父类的方法
// 生成以后的对象,可以强制转换为被代理对象(也就是用自己写的类)
// 子类引用赋值给父类
try {
Zhangsan obj = (Zhangsan)new YMMeipo().getInstance(Zhangsan.class);
obj.findLove();
} catch (Exception e) {
e.printStackTrace();
}
}
区别
JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理。