大家好,这是一个为了梦想而保持学习的博客。这是第一篇文章,分享一下我对【代理模式】的理解。 文章的风格会一直保持问答的方式讲述,这是我个人喜欢的一种风格,也是相当于模拟面试。
什么是代理?
让我们假设一个场景,一家法国的酒厂想要在中国大陆卖酒,拓展新业务。但是酒厂只想提供酒,其他的销售啊,发货啊这些事情他们并不想做,于是就在中国找了个代理。代理就负责去做原本酒厂不想做的那些事情,而酒厂就只需要提供酒就可以了,这就完成了一次业务扩展,这也就是代理存在的意义。 设计源于生活,基于以上场景,我们就可以知道代理模式的应用场景,与代理模式的工作模块。
- 应用场景:用于对原生函数——卖酒,的增加与扩展。
- 工作模块:分为被代 理对象——酒厂,与代理对象——中国的代理机构。
Java中的代理分为几种?
可以分为静态代理与动态代理两种。
静态代理主要是在编写代码时由我们手动编写代理类去实现拓展的功能,之所以称之为静态,是因为所有的代理关系全部都是固定死的。就像上面的场景中,A酒厂创建了一个代理部门,只去代理A酒厂的业务,称之为A代理,他们的关系是1对1固定的。
动态代理相对于静态代理,最大的变化就是不用手动去实现自己的代理类了,只需要通过JDK或者CGLIB去获得一个代理类,而代理类是在运行时被生成与加载的。回到上面的场景,动态代理就相当于A找了一个代理机构,代理机构为A厂找来了一位代理人A$Proxy,当A的酒运来以后,直接交给A$Proxy去运作就好了。此时B酒厂也需要找代理,也可以找到代理机构,代理机构为B厂分配一位代理人B$Proxy。以上场景中,代理机构就是我们常说的JDK动态代理或者CGLIB。
静态代理是如何实现的?进行一下优劣分析?
代理实现的前提是如何能够获取到原接口的调用。因此就可以有两种方式:组合与继承。
- 组合:与被代理类实现相同的接口,然后向代理类传入被代理类的引用,从而调用到目标函数。
- 继承:继承被代理类,重写目标函数,然后通过super调用到目标函数。
其实无论静态代理与静态代理都是基于这两种方式去实现的,静态代理具体的操作方式就按这两种方式去为需要代理的类编写对应代理类即可。实例代码中涉及到的接口与类之间的关系图如下:
对应代码如下:
// 组合:继承相同的接口,传入被代理类的引用。
public class Car3 implements MoveAble {
private Car c;//被代理类
public Car3(Car c) {
this.c = c;
}
@Override
public void move() {
long begin = System.currentTimeMillis();
System.out.println("car start");
c.move();//通过引用调用目标函数
long end = System.currentTimeMillis();
System.out.println("car stop,time:" + (end - begin));
}
}
//继承:继承被代理类,重写目标函数,然后通过super调用目标函数。
public class Car2 extends Car {
@Override
public void move() {// 重写目标函数
long begin = System.currentTimeMillis();
System.out.println("car start");
super.move();
long end = System.currentTimeMillis();
System.out.println("car stop,time:" + (end - begin));
}
}
- 优点:代理与被代理类关系明确,易于阅读与调试。
- 缺点:手动编写代理实现比较麻烦,不灵活;而且每个类都对应写一个代理类会造成代理类的数量过多。
动态代理是如何实现的?进行一下优劣分析?
搞清楚上面的静态代理原理后,动态代理原理就很好理解了。动态代理就是将我们手动编写代理实现这一过程变成了由框架自己去完成,然后再自己加载运行。框架是如何完成的呢?我们知道代码都会成为.java文件,然后编译成.class文件,最终加载进JVM成为一个类。框架就是在运行时,帮我们生成代理类对应的java/class文件,然后再加载进JVM成为一个类,最终反射创建对应的代理对象返回给我们去使用。如果对生成文件的过程感兴趣,可以去看文末的慕课网视频链接。动态代理对应的使用代码如下:
// JDK动态代理测试函数
public static void main(String[] args) {
Car c = new Car();
InvocationHandler h = new Handler(c);
// 这里返回的一定是用MoveAble接口去接,不然报错
MoveAble c1 = (MoveAble) Proxy.newProxyInstance(c.getClass().getClassLoader(), c.getClass().getInterfaces(), h);
c1.move();
/**
* 综上,JDK动态代理的目标类需要实现接口,是因为需要实现对应的子类。
* 另外核心就是InvocationHandler里面的invoke函数,里面利用组合的方式实现方法的调用。
*/
}
// 核心handler类
public class Handler implements InvocationHandler{
/**
* 被代理的对象
*/
private Object obj;
public Handler(Object obj) {
super();
this.obj = obj;
}
/**
* proxy:被代理对象
* method:代理的函数
* args:函数的参数列表
*
* 返回:Object 方法返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
System.out.println("car start");
method.invoke(obj, args);
long end = System.currentTimeMillis();
System.out.println("car stop,time:" + (end - begin));
return null;
}
}
// CGLIB动态代理测试函数
public static void main(String[] args) {
Train t = new Train();
CglibHandler cg = new CglibHandler();
Train tx = (Train) cg.getProxy(t.getClass());
tx.move();
/**
* 综上:cglib是采用继承的方式去实现的动态代理。因此不用接口就可以实现。
* 他的核心接口就是MethodInterceptor去拦截父类的所有函数,然后通过调用子类的方法去实现增强。
*
* 动态代理的两个实现方式分别和静态代理的实现对应
* JDK动态代理:组合
* CGLIB动态代理:继承。
*/
}
// 核心handler类
public class CglibHandler implements MethodInterceptor{
private Enhancer enhance = new Enhancer();
public Object getProxy(Class<?> clazz){
// 创建传入的类的子类
enhance.setSuperclass(clazz);
// 完成创建后 回调自己
enhance.setCallback(this);
return enhance.create();
}
/**
* obj:被代理类
* m:被代理函数
* args:参数列表
* proxy:代理类实例
*
* 返回值Object:方法返回值
*/
@Override
public Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable {
long begin = System.currentTimeMillis();
System.out.println("train start");
// 执行父类的那个函数
proxy.invokeSuper(obj, args);
long end = System.currentTimeMillis();
System.out.println("train stop,time:" + (end - begin));
return null;
}
}
- 优点:灵活易用,不用手动创建代理类。
- 缺点:如果频繁创建代理类,可能会让metaspace空间不足,从而触发频繁FullGC,因此如果我们需要自己用到动态代理时,一般将创建出来的代理对象进行缓存复用。
参考书籍及链接: 《模式的秘密---代理模式》
来源:oschina
链接:https://my.oschina.net/keepal/blog/3184923