代理
代理模式是一种结构型设计模式,它允许我们通过添加一个代理对象来控制对另一个对象的访问。代理对象和实际对象具有相同的接口,使得客户端在不知情的情况下可以使用代理对象进行操作。代理对象在与客户端进行交互时,可以控制对实际对象的访问,以实现一些额外的功能,例如访问计数、延迟加载、权限控制等。
代理分为静态代理和动态代理两种
静态代理
静态代理是指在编译期间就已经确定代理类和被代理类的关系。代理类需要手动编写,并且每个被代理类都需要一个对应的代理类
特点
- 编译期确定:在编译代码时,代理类的代码就已经确定,之后不会再发生变化。
- 代码冗余:当有多个被代理类或者被代理类的方法很多时,会产生大量重复的代理代码。
- 灵活性差:如果被代理类的接口发生变化,代理类也需要相应地修改。
下边我们举一个例子帮助大家进行了解
使用接口进行静态代理
假设我们有一个叫cai的歌手,他只会进行singing和dance两件事情,但是当它要开演唱会时,他要进行收钱才开始操作,但是收钱这个操作他只会专心执行sing和dance这个操作或者说他不会首先这个操作。这时就需要它的经纪人来帮助他进行收钱操作(代理)
定义cai歌手
public class Cai implements Singer {@Overridepublic void singing() {System.out.println("cai 唱歌");}@Overridepublic int dance() {System.out.println("cai 跳舞");return 0;}
}
定义cai代理
public class Caidaili implements Singer{private Singer cai = new Cai();@Overridepublic void singing() {System.out.println("先收钱");cai.singing();}@Overridepublic int dance() {System.out.println("先收钱");cai.dance();return 0;}
}
他们都要实现Singer这个接口(代理模式的核心思想是通过代理对象来控制对真实对象的访问,代理对象和真实对象需要对外提供一致的服务。接口定义了一组方法签名,实现同一个接口能保证代理对象和被代理对象具有相同的方法,客户端可以以相同的方式调用它们,从而实现对被代理对象的透明访问。)
public interface Singer {void singing();int dance();
}
最后使用main方法进行调用
public static void main(String[] args) {Singer singer = new Caidaili();singer.singing();singer.dance();}
此时来了一位新的歌手为wu,他同样会singing和dance,在假设其自己的方法和代理都实现时,我们只需要修改主代码中的代理对象即可实现
使用继承方式进行静态代理
同样的使用继承方式我们也可以实现上述操作
public class Singer {public void dance(){System.out.println("cai 跳舞");}public int singing(){System.out.println("cai 唱歌");return 100;}
}
使用被代理类继承代理类
public class SingerSub extends Singer {@Overridepublic void dance() {System.out.println("收钱");super.dance();}@Overridepublic int singing() {System.out.println("收钱");return super.singing();}
}
最后进行执行
public class Main {public static void main(String[] args) {Singer singer = new SingerSub();singer.singing();singer.dance();}
}
但是因为没有接口的实现,我们每次进行都要使用代理类来承接这个被代理类
结合上边的例子问题也随之出现,因为我们在代理类中实例化了被代理类,每一个被代理类都要一个代理类在其内部进行实例化,每出现一个被代理类都要生成一个新的代理类进行代理,极为不方便。
此时,我们就需要一个拥有强大业务能力的“经纪人”(动态代理),来代理所有的被代理对象,不用我们进行频繁的更换经纪人。
动态代理
从 静态代理 章节中为我们可知,静态代理存在着诸多的问题,最主要的问题是静态代理类需要对被代理类做手动的方法映射。造成这个问题的原因是代理对象是通过硬编码得到的,是在程序编译前就已经存在的,所以顺着这个思路,我们不难得到一个方向,如何代理对象不是通过硬编码提前写好的,而是在程序运行中动态生产的,且生成的代理对象可以对被代理类的方法做自动的映射,那么问题不就解决了吗?是的,这也就是动态代理的大致解决方案。
JDK代理(基于接口实现)
我们同样以上边的cai歌手的例子为例
定义一个Singer的接口
public interface Singer {void dance();int singing();
}
创建一个cai歌手进行实现它
public class Cai implements Singer {@Overridepublic void dance() {System.out.println("Cai 在跳舞");}@Overridepublic int singing() {System.out.println("Cai 在唱歌");return 0;}
}
最后我们使用JDK代理来进行代理cai类
public static void main(String[] args) {Cai cai = new Cai();//第一个参数:类加载器//第二个参数:代理类需要实现的接口数组//第三个参数:InvocationHandler接口,其中包含了一个invoke方法,该方法会在每次调用代理对象的方法时被触发Singer o = (Singer) Proxy.newProxyInstance(Cai.class.getClassLoader(), new Class[]{Singer.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if("singing".equals(method.getName())){System.out.println("先收钱");}else if("dance".equals(method.getName())){System.out.println("先收钱");}Object invoke = method.invoke(cai, args);//使用反射,将该方法作用到cai对象上return invoke;}});o.singing();}
当我们想要修改被代理类,我们只需要修改
减少了,代理类的创建
cglib代理(基于继承进行实现)
创建一个cai对象
public class Cai {public void dance(){System.out.println("cai 跳舞");}public String singing(){System.out.println("cai 唱歌");return "谢谢";}}
创建代理cai的方法
public class CreateCglibProxy {public static Object getProxy(Object o){Enhancer enhancer = new Enhancer();//生成代理对象enhancer.setSuperclass(o.getClass());//将代理对象设置为被代理对象的子类enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object proxyObject, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {if("singing".equals(method.getName())){System.out.println("先收钱");}else if("dance".equals(method.getName())){System.out.println("先收钱");}Object invoke = methodProxy.invokeSuper(proxyObject, args);return invoke;}});return enhancer.create();}
}
最后进行执行
public class Main {public static void main(String[] args) {Cai cai = new Cai();Cai proxy = (Cai) CreateCglibProxy.getProxy(cai);proxy.dance();}
}