我们平常去电影院看电影,电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。
注意这里有电影、电影院、客户、广告等元素
首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。
public interface Movie {
void play();
}
然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。
这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放
//"被代理类"对象;
public class RealMovie_TomAndJerry implements Movie {
public void play() {
System.out.println("正在观看电影:《猫和老鼠》");
}
}
Proxy 代理。Cinema 就是 Proxy 代理对象,它有一个 play() 方法。除了调用 play() 方法,它还放广告。
代理对象的私有字段的类型应该为接口类型。
public class Cinema implements Movie {
// 私有一个被代理类的父类引用,这样做是为了适应所有的被代理类对象,只要实现了接口就好;
private Movie movie;
// 传入被代理类对象,这里的作用是初始化"代理类"中的"被代理类"对象;
public Cinema(Movie movie) {
super();
this.movie = movie;
}
// 代理类本身自带功能;
public void play() {
advertisement(true);
movie.play();
advertisement(false);
}
//增强服务和功能;
public void advertisement(boolean isStart) {
if (isStart) {
System.out.println("电影马上开始了");
} else {
System.out.println("电影马上结束了");
}
}
}
客户去电影院看电影
public class Client {
public static void main(String[] args) {
RealMovie_TomAndJerry realMovie_tomAndJerry = new RealMovie_TomAndJerry();
Movie movie = new Cinema(realMovie_tomAndJerry);
movie.play();
}
}
/*
电影马上开始了
正在观看电影:《猫和老鼠》
电影马上结束了
*/
上新电影了呢
public class RealMovie_Soul implements Movie { public void play() { System.out.println("正在播放《心灵奇旅》"); } }
客户看完《猫和老鼠》,再看《心灵奇旅》
public class Client { public static void main(String[] args) { RealMovie_TomAndJerry realMovie_tomAndJerry = new RealMovie_TomAndJerry(); Movie movie = new Cinema(realMovie_tomAndJerry); movie.play(); RealMovie_Soul realMovie_soul = new RealMovie_Soul(); movie = new Cinema(realMovie_soul); movie.play(); } } /* 电影马上开始了 正在观看电影:《猫和老鼠》 电影马上结束了 电影马上开始了 正在播放《心灵奇旅》 电影马上结束了 */
现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
优:
劣:
动态代理的角色和静态代理的一样 .
动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
核心 : InvocationHandler接口 和 Proxy类
1.InvocationHandler(调用处理器)
public interface InvocationHandler
//InvocationHandler接口 是 proxy代理实例 的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。
(当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:)
/**
* proxy:代理类代理的真实代理对象
* method:我们所要调用某个对象真实的方法的Method对象
* args:代理实例上方法调用中传递的参数值的对象数组,如果接口方法不带参数, null
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
//处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。
2.Proxy
Proxy类它提供了很多方法,但是我们最常用的是newProxyInstance方法。
Proxy类方法:
getInvocationHandler:返回指定代理实例的调用处理程序
getProxyClass:给定类加载器和接口数组的代理类的java.lang.Class对象。
isProxyClass:当且仅当使用getProxyClass方法或newProxyInstance方法将指定的类动态生成为代理类时,才返回true。
newProxyInstance:返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
遇到的bug:假设getInstance()方法里面return newProxyInstance(),在测试类里实例化被代理类的对象时,
Son instance=(Son) new getInstance(object); 则会报错 com.sun.proxy.$Proxy0 cannot be cast to
应改为 Father instance= (Father) new getInstance(object)
因为newProxyInstance定义为:返回指定接口的代理类的实例
因为被代理的类没有继承接口,而是继承了一个基类
问:为什么动态代理必须针对接口?
**答:**JDK动态代理的原理是根据定义好的规则,用传入的接口创建一个新类,这就是为什么采用动态代理时为什么只能用接口引用指向代理,而不能用传入的类引用执行动态类。Proxy.newProxyInstance返回的是接口类型 而我 却用实现类来接受 就出现了类型不匹配的问题(总之一句话,jdk动态代理必须是接口。cglib 都行。)
/**
*loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
*interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
*h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
定义抽象接口(被代理类)
public interface mathematics {
public void add();
public void subtract();
public void multiply();
public void divide();
}
定义具体对象
public class Calculate implements mathematics {
public void add() {System.out.println("加法");}
public void subtract() {System.out.println("减法");}
public void multiply() {System.out.println("乘法");}
public void divide() {System.out.println("除法");}
}
定义动态代理
public class ProxyInvocationHandler implements InvocationHandler {
private Object object;//编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
//实例化对象
public Object getInstance(Object object){
this.object = object;//合并setObject方法到此
return Proxy.newProxyInstance(this.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
//方法调用器
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object invoke = method.invoke(object, args);
return invoke;
}
//代理类增加的方法,不改变具体类的源码
public void log(String msg){
System.out.println("调用"+msg+"方法");
}
}
测试
public class Test {
public static void main(String[] args) {
mathematics mathematics = new Calculate();
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
mathematics instance = (mathematics) invocationHandler.getInstance(mathematics);
// instance.add();
instance.multiply();
}
}
/*
调用multiply方法
乘法
*/
public class demo {
//Subject:接口,这个接口含有代理类和被代理类共同需要做的事,action方法
interface Subject {
void action();
}
//RealSubject:被代理类,也就是真实的类,真正做事的类
static class RealSubject implements Subject {
public void action() {
System.out.println("我是被代理类!!真正做事的类");
}
}
//ProxySubject:代理类,也就是代理对象,中介
//涉及到动态代理需要实现InvocationHandler接口
static class ProxySubject implements InvocationHandler {
// (实现了接口的)被代理类对象的引用声明;
private Object object;
public Object getNewInstance(Object object) {
// 实例化被代理类的对象;
this.object = object;
// 返回一个动态代理类的对象;
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
/*
这里的newProxyInstance的三个参数:(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
1.第一个参数是需要传入类的加载器,这里指的是被代理类的类加载器,简单来说就是和被代理类使用相同的类加载器;(不一定)
2.第二个参数是需要传入类的接口,也就是说,这个类实现了哪些接口,我都要传过来;
3.第三个参数是需要传入的一个InvocationHandler对象,指的是代理类对象,也就是调用这个函数的this对象(ProxySubject对象);
*/
// 当通过动态代理类的对象发起对被重写的方法的调用时,都会转换为对以下invoke方法的调用;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强代码(前);
System.out.println("前增强功能!!");
// 被代理类的方法;
Object value = method.invoke(object, args);
// 增强代码(后);
System.out.println("后增强功能!!");
return value;
}
}
public static class DynamicProxyDemo {
public static void main(String[] args){
// 1.创建被代理类对象;
RealSubject realSubject = new RealSubject();
// 2.创建一个实现了InvocationHandler接口的类的对象;
ProxySubject proxySubject = new ProxySubject();
// 3.父类引用指向子类对象;
Subject subject = (Subject)proxySubject.getNewInstance(realSubject);
// 4.执行代理类的方法;
subject.action();
}
}
}
代理类
ProxySubject类
//ProxySubject:代理类,也就是代理对象,中介
//涉及到动态代理需要实现InvocationHandler接口
static class ProxySubject implements InvocationHandler {
// 实现了接口的 被代理类的对象 引用声明;
private Object object;
public Object getNewInstance(Object object) {
// 实例化被代理类的对象;
this.object = object;
// 返回一个代理类的对象;
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
// 当通过代理类的对象发起对被重写的方法的调用是,都会转换为对以下invoke方法的调用;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强代码(前);
System.out.println("前增强功能!!");
// 被代理类的方法;
Object value = method.invoke(object, args);
// 增强代码(后);
System.out.println("后增强功能!!");
return value;
}
}
该类为代理类。其持有一个被代理类的父类引用object,在getNewInstance这个函数中,我们传入需要被代理的类对象,将ProxySubject中的Object实例化,同时根据这个传入的object对象,获取其类加载器,接口,以及其代理类对象;通过Proxy类本身的静态方法newProxyInstance得到一个代理类对象,并将其返回,后续我们就会操作这个返回的代理类对象。
其中还有一个invoke方法,这里大概的意思就是当我们上述方法创建的代理类对象进行方法调用的时候,都会转化为对invoke方法的调用,例如我们调用通过getNewInstance方法产生的代理类的sell方法,这个sell方法就会传入invoke中,然后转化成对invoke的调用(这里的invoke方法其实就相当于代理类的sell方法,但是它没有写死,而是你传入什么方法,我就调用这个方法,实现了动态调用,与被代理类完全分割开来,完成解耦)。
万能的体现
public class Test {
public static void main(String[] args) {
mathematics mathematics = new Calculate();
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
mathematics instance = (mathematics) invocationHandler.getInstance(mathematics);
instance.multiply();
System.out.println("==================================");
Rent rent = new Host();
Rent instance1 = (Rent) invocationHandler.getInstance(rent);
instance1.rent();
System.out.println("==================================");
a a = new b();
a instance2 = (com.kuang.demo03.a) invocationHandler.getInstance(a);
instance2.func();
}
}
/*
调用multiply方法
乘法
==================================
调用rent方法
房屋出租
==================================
调用func方法
哈哈
*/
Rent rent = new Host();
Rent instance1 = (Rent) invocationHandler.getInstance(rent);
instance1.rent();
System.out.println("==================================");
a a = new b();
a instance2 = (com.kuang.demo03.a) invocationHandler.getInstance(a);
instance2.func();
}
}
/*
调用multiply方法
乘法
==================================
调用rent方法
房屋出租
==================================
调用func方法
哈哈
*/