代理模式是什么
代理模式是一种设计模式, 简单说即是在不改变源码的情况下, 实现对目标对象的功能扩展.
比如有个歌手对象叫 Singer, 这个对象有一个唱歌方法叫 sing().
- public class Singer{
- public void sing(){
- System.out.println("唱一首歌");
- }
- }
假如你希望, 通过你的某种方式生产出来的歌手对象, 在唱歌前后还要想观众问好和答谢, 也即对目标对象 Singer 的 sing 方法进行功能扩展.
- public void sing(){
- System.out.println("向观众问好");
- System.out.println("唱一首歌");
- System.out.println("谢谢大家");
- }
但是往往你又不能直接对源代码进行修改, 可能是你希望原来的对象还保持原来的样子, 又或许你提供的只是一个可插拔的插件, 甚至你有可能都不知道你要对哪个目标对象进行扩展. 这时就需要用到 java 的代理模式了. 网上好多用生活中的经理人的例子来解释 "代理", 看似通俗易懂, 但我觉得不适合程序员去理解. 程序员应该从代码的本质入手.
Java 的三种代理模式
想要实现以上的需求有三种方式, 这一部分我们只看三种模式的代码怎么写, 先不涉及实现原理的部分.
1. 静态代理
- public interface ISinger {
- void sing();
- }
- /**
- * 目标对象实现了某一接口
- */
- public class Singer implements ISinger{
- public void sing(){
- System.out.println("唱一首歌");
- }
- }
- /**
- * 代理对象和目标对象实现相同的接口
- */
- public class SingerProxy implements ISinger{
- // 接收目标对象, 以便调用 sing 方法
- private ISinger target;
- public UserDaoProxy(ISinger target){
- this.target=target;
- }
- // 对目标对象的 sing 方法进行功能扩展
- public void sing() {
- System.out.println("向观众问好");
- target.sing();
- System.out.println("谢谢大家");
- }
- }
测试
- /**
- * 测试类
- */
- public class Test {
- public static void main(String[] args) {
- // 目标对象
- ISinger target = new Singer();
- // 代理对象
- ISinger proxy = new SingerProxy(target);
- // 执行的是代理的方法
- proxy.sing();
- }
- }
总结: 其实这里做的事情无非就是, 创建一个代理类 SingerProxy, 继承了 ISinger 接口并实现了其中的方法. 只不过这种实现特意包含了目标对象的方法, 正是这种特征使得看起来像是 "扩展" 了目标对象的方法. 假使代理对象中只是简单地对 sing 方法做了另一种实现而没有包含目标对象的方法, 也就不能算作代理模式了. 所以这里的包含是关键.
缺点: 这种实现方式很直观也很简单, 但其缺点是代理对象必须提前写出, 如果接口层发生了变化, 代理对象的代码也要进行维护. 如果能在运行时动态地写出代理对象, 不但减少了一大批代理类的代码, 也少了不断维护的烦恼, 不过运行时的效率必定受到影响. 这种方式就是接下来的动态代理.
2. 动态代理 (也叫 JDK 代理)
跟静态代理的前提一样, 依然是对 Singer 对象进行扩展
- public interface ISinger {
- void sing();
- }
- /**
- * 目标对象实现了某一接口
- */
- public class Singer implements ISinger{
- public void sing(){
- System.out.println("唱一首歌");
- }
- }
这回直接上测试, 由于 java 底层封装了实现细节 (之后会详细讲), 所以代码非常简单, 格式也基本上固定.
调用 Proxy 类的静态方法 newProxyInstance 即可, 该方法会返回代理类对象
static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h )
接收的三个参数依次为:
ClassLoader loader:
指定当前目标对象使用类加载器, 写法固定
Class<?>[] interfaces:
目标对象实现的接口的类型, 写法固定
InvocationHandler h: 事件处理接口, 需传入一个实现类, 一般直接使用匿名内部类
测试代码
- public class Test{
- public static void main(String[] args) {
- Singer target = new Singer();
- ISinger proxy = (ISinger) Proxy.newProxyInstance(
- target.getClass().getClassLoader(),
- target.getClass().getInterfaces(),
- new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("向观众问好");
- // 执行目标对象方法
- Object returnValue = method.invoke(target, args);
- System.out.println("谢谢大家");
- return returnValue;
- }
- });
- proxy.sing();
- }
- }
总结: 以上代码只有标黄的部分是需要自己写出, 其余部分全都是固定代码. 由于 java 封装了 newProxyInstance 这个方法的实现细节, 所以使用起来才能这么方便, 具体的底层原理将会在下一小节说明.
缺点: 可以看出静态代理和 JDK 代理有一个共同的缺点, 就是目标对象必须实现一个或多个接口, 加入没有, 则可以使用 Cglib 代理.
3.Cglib 代理
前提条件:
需要引入 cglib 的 jar 文件, 由于 Spring 的核心包中已经包括了 Cglib 功能, 所以也可以直接引入 spring-core-3.2.5.jar
目标类不能为 final
目标对象的方法如果为 final/static, 那么就不会被拦截, 即不会执行目标对象额外的业务方法
- /**
- * 目标对象, 没有实现任何接口
- */
- public class Singer{
- public void sing() {
- System.out.println("唱一首歌");
- }
- }
- /**
- * Cglib 子类代理工厂
- */
- public class ProxyFactory implements MethodInterceptor{
- // 维护目标对象
- private Object target;
- public ProxyFactory(Object target) {
- this.target = target;
- }
- // 给目标对象创建一个代理对象
- public Object getProxyInstance(){
- //1. 工具类
- Enhancer en = new Enhancer();
- //2. 设置父类
- en.setSuperclass(target.getClass());
- //3. 设置回调函数
- en.setCallback(this);
- //4. 创建子类 (代理对象)
- return en.create();
- }
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- System.out.println("向观众问好");
- // 执行目标对象的方法
- Object returnValue = method.invoke(target, args);
- System.out.println("谢谢大家");
- return returnValue;
- }
- }
这里的代码也非常固定, 只有标黄部分是需要自己写出
测试
- /**
- * 测试类
- */
- public class Test{
- public static void main(String[] args){
- // 目标对象
- Singer target = new Singer();
- // 代理对象
- Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance();
- // 执行代理对象的方法
- proxy.sing();
- }
- }
总结: 三种代理模式各有优缺点和相应的适用范围, 主要看目标对象是否实现了接口. 以 Spring 框架所选择的代理模式举例
在 Spring 的 AOP 编程中:
如果加入容器的目标对象有实现接口, 用 JDK 代理
如果目标对象没有实现接口, 用 Cglib 代理
来源: http://www.bubuko.com/infodetail-3162490.html