设计模式之代理模式 - morris131/morris-book GitHub Wiki


title: 设计模式之代理模式 date: 2018-09-19 11:10:22 categories: 设计模式 tags: [设计模式,代理模式]

设计模式之代理模式

定义

为其他对象提供一种代理以控制对这个对象的访问。

类图

代理模式类图

优点

  • 职责清晰
  • 高扩展性
  • 智能化

缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂

适用场景:

  • Spring AOP

分类

根据代理类的创建时机和创建方式的不同,代理模式可以分为静态代理和动态代理。 静态代理:在程序运行前就已经存在的编译好的代理类 动态代理:在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能。

静态代理

静态代理是指在程序运行前由程序员创建或特定工具自动生成源代码并对其编译生成.class文件。静态代理的实现只需要三步:首先,定义业务接口;其次,实现业务接口;然后,定义代理类并实现业务接口;最后便可通过客户端进行调用。

IUserService.java

package com.morris.pattern.proxy;

public interface IUserService {
	void login(String name);
}

UserServiceImpl.java

package com.morris.pattern.proxy;

public class UserServiceImpl implements IUserService {

	@Override
	public void login(String name) {
		System.out.println(name + " is logining...");
	}

}

UserServiceProxy.java

package com.morris.pattern.proxy;

public class UserServiceProxy implements IUserService {
	
	private IUserService userService = new UserServiceImpl();

	@Override
	public void login(String name) {
		System.out.println("before invoke UserServiceImpl login()");
		userService.login(name);
		System.out.println("after invoke UserServiceImpl login()");
	}

}

StaticProxyDemo.java

package com.morris.pattern.proxy;

public class StaticProxyDemo {
	
	public static void main(String[] args) {
		UserServiceProxy proxy = new UserServiceProxy();
		proxy.login("morris");
	}

}

运行结果如下:

before invoke UserServiceImpl login()
morris is logining...
after invoke UserServiceImpl login()

JDK动态代理

JDK动态代理的思维模式与之前的静态代理模式是一样的,也是面向接口进行编码,创建代理类将具体类隐藏解耦,不同之处在于代理类的创建时机不同,动态代理需要在运行时因需实时创建。

JDKDynamicProxy.java

package com.morris.pattern.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKDynamicProxy implements InvocationHandler {
	
	private Object object;
	
	public JDKDynamicProxy(Object object) {
		this.object = object;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("method:" + method.getName() + ", args:" + args);
		System.out.println("before invoke UserServiceImpl login()");
		method.invoke(object, args);
		System.out.println("after invoke UserServiceImpl login()");
		return null;
	}

}

JDKDynamicProxyDemo.java

package com.morris.pattern.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class JDKDynamicProxyDemo {
	
	public static void main(String[] args) {
		IUserService userService = new UserServiceImpl();
		InvocationHandler handler = new JDKDynamicProxy(userService);
		IUserService proxy = (IUserService) Proxy.newProxyInstance(JDKDynamicProxyDemo.class.getClassLoader(), new Class[]{IUserService.class}, handler);
		proxy.login("morris");
	}

}

运行结果如下:

method:login, args:[Ljava.lang.Object;@154ebadd
before invoke UserServiceImpl login()
morris is logining...
after invoke UserServiceImpl login()

注意:在JDKDynamicProxy的invoke方法中打印proxy会引起StackOverflowError。

打印proxy引起StackOverflowError原因分析

参考<java查看动态代理生成的代码>生成class文件,然后反编译结果如下:

package com.sun.proxy;

import com.morris.pattern.proxy.IUserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0
  extends Proxy
  implements IUserService
{
  private static Method m1;
  private static Method m0;
  private static Method m3;
  private static Method m2;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void login(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m3 = Class.forName("com.morris.pattern.proxy.IUserService").getMethod("login", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

可以看到,调用toString方法的时候,调用了h的invoke方法,而h就是InvocationHandler的实例,所以是递归调用,所以就会出现上述所说的java.lang.StackOverflowError错误。

CGLIB动态代理

JDK动态代理拥有局限性,那就是必须面向接口编程,没有接口就无法实现代理,我们也不可能为了代理而为每个需要实现代理的类强行添加毫无意义的接口,这时我们需要Cglib,这种依靠继承来实现动态代理的方式,不再要求我们必须要有接口。

CglibDynamicProxy.java

package com.morris.pattern.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibDynamicProxy implements MethodInterceptor {

	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("method:" + method.getName() + ", args:" + args);
		System.out.println("before invoke CglibDynamicProxy intercept");
		proxy.invokeSuper(obj, args);
		System.out.println("after invoke CglibDynamicProxy intercept");
		
		return null;
	}
}

CglibDynamicProxyDemo.java

package com.morris.pattern.proxy;

import net.sf.cglib.proxy.Enhancer;

public class CglibDynamicProxyDemo {
	public static void main(String[] args) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(UserServiceImpl.class);
		enhancer.setCallback(new CglibDynamicProxy());
		
		IUserService userService = (IUserService) enhancer.create();
		userService.login("morris");
	}
}

注意:测试类中只能用enhancer.setSuperclass(UserServiceImpl.class)而不能用enhancer.setSuperclass(IUserService.class)否则会抛出NoSuchMethodError异常。

⚠️ **GitHub.com Fallback** ⚠️