一、代理模式的概念
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
有三个角色:调用者,代理对象,真实对象
按理说,顾客可以直接从厂家购买产品,但是现实生活中,很少有这样的销售模式。一般都是厂家委托给代理商进行销售,顾客跟代理商打交道,而不直接与产品实际生产者进行关联。
所以,代理就有一种中间人的味道。
二、静态代理
我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?
电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。
现在用代码来进行模拟。
首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。
public interface Movie {
void play();
}
然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。
public class RealMovie implements Movie {
@Override
public void play() {
System.out.println("您正在观看电影 《肖申克的救赎》");
}
}
这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放。那么 Proxy 代理呢?
//Cinema是代理对象
public class Cinema implements Movie {
RealMovie movie;//目标对象、被代理对象
public Cinema(RealMovie movie) {
super();
this.movie = movie;
}
@Override
public void play() {
guanggao();
movie.play();
}
public void guanggao(){
System.out.println("电影马上开始了,先放广告!");
}
}
Cinema 就是 Proxy 代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。现在,我们编写测试代码。
public class ProxyTest {
public static void main(String[] args) {
RealMovie realmovie = new RealMovie();
Movie movie = new Cinema(realmovie);
movie.play();
}
}
画图讲解:
现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。下面要介绍的内容就是动态代理。
三、JDK动态代理
既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。
那么在动态代理的中这个动态体现在什么地方?
上一节代码中 Cinema 类是代理,我们需要手动编写代码让 Cinema 实现 Movie 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 Cinema 这个类。这就是它被称为动态的原因。也许概念比较抽象。现在实例说明一下情况。
新建一个类Handler实现InvocationHandler接口
public class Handler implements InvocationHandler {
private Movie realMovie;
public Handler(Movie movie) {
super();
this.realMovie = movie;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("放电影之前先放一些广告");//这里是代理对象附加的功能
method.invoke(realMovie,args);//调用目标对象的对应方法,这才是真正要做的事情
System.out.println("放映结束了,再搞点促销");//这里是代理对象附加的功能
return null;
}
}
通过JDK的动态代理功能创建一个代理类Proxy.newProxyInstance,这个类和目标类实现了相同的接口
public class Test {
public static void main(String[] args) {
//设置系统属性,生成的java代理类源码会保存在工程目录中
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//1.先创建一个Movie的对象
RealMovie realMovie = new RealMovie();
//2.创建一个Handler对象
InvocationHandler handler = new Handler(realMovie);
//3.创建一个代理对象
//第一个参数Test.class.getClassLoader()是类加载器,可以使用当前类的类加载器
//第二个参数RealMovie.class.getInterfaces()是目标对象实现的接口列表
//第三个参数handler就是我们自己实现的invocationHandler
Movie proxyInstance = (Movie)Proxy.newProxyInstance(Test.class.getClassLoader(), RealMovie.class.getInterfaces(), handler);
//4.调用代理对象的方法
proxyInstance.play();
}
}
动态代理语法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler handler)
下面讲解它的 3 个参数意义。
- loader 是类加载器
- interfaces 代码要用来代理的接口
- handler 一个 InvocationHandler 对象
InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。
- proxy 代理对象
- method 代理对象调用的方法
- args 调用的方法中的参数
因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
注意:要想使用JDK的方法生成动态代理,目标类必须实现某一个接口。
生成JDK动态的步骤:
1、创建被代理对象(一定要实现接口)
2、创建一个InvocationHandler的对象,实现invoke的方法,在invoke方法里面,通过反射调用被代理的对象的方法,也可以在目标方法的前后增加自己的逻辑
3、调用 Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(), new Class[]{Movie.class}, handler);生成代理对象
//第一个参数是类加载器,可以使用当前类的类加载器
//第二个参数是目标对象实现的接口列表
//第三个参数就是我们自己实现的invocationHandler
4、调用代理对象的方法
四、CGLIB动态代理
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
注意:Cglib不需要目标类实现任何接口就可以生成目标类的代理类
在工程中引入两个jar文件asm-7.1.jar和cglib-3.3.0.jar
创建一个类,这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理。
public class SayHello {
public void say(){
System.out.println("hello everyone");
}
}
创建一个类CglibProxy
public class CglibProxy implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("在这里可以增加需要在目标方法之前执行的逻辑");
//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("在这里可以增加需要在目标方法之后执行的逻辑");
return result;
}
}
测试一下
public class TestCglib {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
//通过生成子类的方式创建代理类
SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);
proxyImp.say();
}
}