撇开模式,只看代理。日常生活中我们经常听说代理,特别是搭梯子的时候,代理一词经常被提及。
字面义就是你想要做某事,但由于有这样那样的原因,你无法完成,而别人可以完成,你只需要提供一些信息给代理商就可以享受到代理的服务。
一、静态代理
举个例子,小徐是一个三十多岁的程序员,因为一直都处在996的工作制中,所以三十多了还找不到女朋友。家里人很着急,于是小徐的母亲托熟人帮忙介绍。在这里,小徐的母亲就是一个代理,小徐是原始对象。既然要代理,那么首先得有一个约束,这里我们使用一个叫Subject的借口来约束代理类和被代理类。
public interface Subject {
void findGirlfriend();
}
然后让小徐和他母亲一起实现这个接口,同时小徐的母亲要持有小徐对象,这样才能知道小徐的需求:
//小徐
public class RealSubject implements Subject {
@Override
public void findGirlfriend() {
System.out.println("找一个能接受996的媳妇");
}
}
//母亲
public class Proxy implements Subject {
private RealSubject rs;
public Proxy(RealSubject rs){
this.rs = rs;
}
@Override
public void findGirlfriend() {
System.out.println("给儿子找对象");
System.out.println("儿子的要求是:");
rs.findGirlfriend();
System.out.println("这要求有点难啊,只能尽力了!");
}
}
测试一下:
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy(new RealSubject());
proxy.findGirlfriend();
}
}
输出结果:
给儿子找对象
儿子的要求是:
找一个能接受996的媳妇
这要求有点难啊,只能尽力了!
可以看到,在代理对象中,不仅执行了实际对象的方法,还额外补充了一些功能。如果仅看上面的代码可能会觉得,代理模式虽然有其优势,但是引入了额外的接口和代理类,优点还不够明显。不过,在这里真实对象RealSubject类是在同一个jvm中的,要引用只需要注入到构造器中或者通过其他方法就可以获得。那么如果真实对象在另一个jvm中呢?这里就需要用到jdk为我们提供的rmi技术类。该技术我会在分布式的地方详细讲解。这里重点还是放在设计模式的思想上。
二、动态代理
静态代理每次她母亲都得拿到小徐对象才能执行找女朋友方法,那小徐的公司公然违反了中国劳动法,让小徐根本没有时间每次都被母亲带着去找女朋友怎么办呢?动态代理就登场了。
首先创建一个媒婆接口:
public interface Matchmaker {
void findGirlFriend();
}
接口很简单,只定义了一个找女朋友的接口。小徐要实现该接口,并提出自己的要求:
public class Xiaoxu implements Matchmaker {
@Override
public void findGirlFriend() {
System.out.println("找一个能接受996的媳妇");
}
}
然后我们来写一个动态代理类:
public class DynamicProxy implements InvocationHandler {
//被代理的对象
private Object target;
//获得被代理对象实例
public Object getInstance(Object target) {
this.target = target;
Class clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("先拿到小徐的要求:");
Object obj = method.invoke(this.target, args);
System.out.println("难度较大,需要加钱!");
return obj;
}
}
首先我们需要实现jdk为我们提供好的InvocationHandler接口,并重写invoke方法。同时写一个getInstance方法对外提供被代理对象的实现。
测试代码:
public class DynamicProxyTest {
public static void main(String[] args) {
try{
Matchmaker xiaoxu = (Matchmaker) new DynamicProxy().getInstance(new Xiaoxu());
xiaoxu.findGirlFriend();
}catch (Exception e){
e.printStackTrace();
}
}
}
在第5行打断点,debug的时候会发现并没有直接调用Xiaoxu的findGirlFriend方法,而是调用类动态代理类的invoke方法。而在该方法里的method.invoke方法确调用了Xiaoxu的findGirlFriend方法。这是为什么呢?这是jdk利用字节码重组技术为我们提供的便捷服务。
字节码重组:
- 拿到被代理对象的引用,利用反射获得该代理对象的所有接口;
- Proxy重新生成了一个实现了1中拿到的所有接口的类;
- 动态生成java代码,把新加的业务逻辑方法由一定的逻辑代码去调用;
- 编译新生成的类;
- 重新加载到jvm中运行
从字节码重组我们看到,动态代理有一个限制,那就是被代理对象必须实现接口,否则动态代理将无法使用,那么这么解决这个问题呢?
三、cglib代理
cglib代理就解决类必须实现接口的问题,首先是Xiaoxu类,可以看到这里没有实现任何接口:
public class Xiaoxu {
public void findGirlFriend(){
System.out.println("找一个能接受996的媳妇");
}
}
再来看看cglib代理类:
public class CglibProxy implements MethodInterceptor {
public Object getInstance(Class<?> clazz) throws Exception{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("小徐的要求是:");
Object obj = methodProxy.invokeSuper(o, objects);
System.out.println("难度有点大,要加钱!");
return obj;
}
}
然后是测试类:
public class CglibProxyTest {
public static void main(String[] args) {
try {
Xiaoxu xiaoxu = (Xiaoxu) new CglibProxy().getInstance(Xiaoxu.class);
xiaoxu.findGirlFriend();
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到,cglib的原理其实就是拿到被代理对象,并且创建一个被代理对象的子类的方式来实现动态代理的。这样就可以避免被代理对象必须实现接口的问题。不过如果被代理类被私有化,也就是不给创建其子类,cglib就没办法实现动态代理的功能了。
总结
静态代理和动态代理的区别:
- 静态代理只能通过手写完成代理操作,如果被代理类增加新的方法,代理类需要同步新增;
- 动态代理采用在运行时动态生成代码的方式,取消类对被代理类的扩展限制;
- 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码
代理模式的优缺点:
优点:
- 代理模式能将代理对象与真实被调用的目标对象分离;
- 一定程序上降低类系统的耦合度,扩展性好;
- 可以起到保护目标对象的作用;
- 可以对目标对象的功能增强;
缺点:
- 代理模式会造成系统设计中类的数量增加;
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度。