代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求。
常用的代理方式可以粗分为:静态代理和动态代理。
代理模式
在代理模式(Proxy Pattern)中,一个类(代理类)代理另一个类(被代理的类)的功能。这种类型的设计模式属于结构型模式。
代理模式的实现往往由将被代理类委托给代理类。代理类与被代理类实现一模一样的接口。 下面举一个利用代理模式实现缓存的例子(摘自维基百科):
public interface Image {
void display();
}
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
public class ProxyImage implements Image {
private RealImage image;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (image == null) {
image = new RealImage(fileName);
}
image.display();
}
}
public class main {
public static void main(String[] args) {
Image image = new ProxyImage("test.png");
image.display(); // load and display
image.display(); // Don't need to load image again.
}
}
代理模式分为以下几类:
- 保护代理:该模式主要进行安全/权限检查。
- 缓存代理:它使用存储来加速调用。一个很好的例子是Spring中的@Cacheable方法,它能够缓存特定参数的方法的结果,再次调用该方法时,会直接从缓存返回先前计算的结果,而不调用实际代码。
- 虚拟和智能代理:这种模式为方法增加了功能,例如记录性能指标(创建@Aspect,使用@Pointcut表示所需方法并定义@Around建议)或进行延迟初始化。
与其他模式的区别:
适配器模式和代理模式之间的主要区别在于代理模式提供了完全相同的接口。装饰器模式增强了接口,而适配器模式更改了接口。
动态代理
上一节的例子中的代理模式的实现属于静态代理。静态代理的实现比较简单:编写一个代理类,实现与目标对象相同的接口,并在内部维护一个目标对象的引用。通过构造器塞入目标对象,在代理对象中调用目标对象的同名方法,并添加前拦截,后拦截等所需的业务功能。
但静态代理有个缺陷。设想我们想实现一个日志功能的代理。在方法执行前打印"方法开始执行"和"方法结束执行"的Log信息。对一个类实现代理还好,如果要对已有系统的几百个类都实现代理呢?每个类都有几百个方法需要代理呢?恐怕要复制粘贴到吐血,而且产生大量的重复代码。
对此,在Java语言上,我们可以利用JVM根据接口自动生成代理对象。
假设我们有个接口Calculate和它的实现CalculateImpl:
public interface Calculate {
int add(int x, int y);
int substract(int x, int y);
}
public class CalculateImpl implements Calculate {
public CalculateImpl() {
}
@Override
public int add(int x, int y) {
return x + y;
}
@Override
public int substract(int x, int y) {
return x - y;
}
}
1. 通过newInstance手动调用构造器构造代理类
首先通过getProxyClass(ClassLoader loader, Class<?>... interfaces)方法获取代理类,然后获取构造器,通过构造器手动调用newInstance方法。
实现InvocationHandler接口中的invoke方法
Class calculateProxyClass = Proxy.getProxyClass(Calculate.class.getClassLoader(), Calculate.class);
Constructor calculateProxyClassConstructor = calculateProxyClass.getConstructor(InvocationHandler.class);
Calculate calculateProxyImpl = (Calculate) calculateProxyClassConstructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("通过newInstance手动调用构造器构造代理类 " + method.getName() + "方法开始执行...");
Object result = method.invoke(new CalculateImpl(), args);
System.out.println(result);
System.out.println(method.getName() + "方法结束执行...");
return result;
}
});
calculateProxyImpl.add(1, 1);
calculateProxyImpl.substract(8, 1);
终端显示:
通过newInstance手动调用构造器构造代理类 add方法开始执行...
2
add方法结束执行...
通过newInstance手动调用构造器构造代理类 substract方法开始执行...
7
substract方法结束执行...
这样我们就不用像静态代理那样,继承相同的接口实现所有的方法了。
这种通过newInstance手动调用构造器构造代理类的方法好是好,但我们觉的稍微有点繁琐:一是需要去获取构造器,然后invoke方法里还有CalculateImpl的硬编码。
2. 通过newProxyInstance构造代理类
Proxy类提供了一个newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法。这样就不用去获取代理类、获取构造器然后调用newInstance方法了。
public static Object getProxy(final Object target) {
return 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("通过newProxyInstance构造代理类 " + method.getName() + "方法开始执行...");
Object result = method.invoke(target, args);
System.out.println(result);
System.out.println(method.getName() + "方法结束执行...");
return result;
}
}
);
}
Calculate target = new CalculateImpl();
Calculate targetProxy = (Calculate) getProxy(target);
targetProxy.add(6, 2);
targetProxy.substract(2, 3);
终端显示:
通过newProxyInstance构造代理类 add方法开始执行...
8
add方法结束执行...
通过newProxyInstance构造代理类 substract方法开始执行...
-1
substract方法结束执行...
注意:JDK动态代理是基于接口设计实现的,代理类必须有接口。