1. 代理模式
代理模式:为一个对象提供一个替身,以控制对这个对象(被代理)的访问。即通过代理对象访问目标对象。这样的好处是:可以在目标对象实现的基础上,增强额外的功能操作(符合开闭原则)。
被代理的对象可以是远程对象、创建开销大的对象、需要安全控制的对象。
代理有三种形式:静态代理、动态代理(可称JDK代理或接口代理)、Cglib代理(可以在内存中动态创建对象,而不需要实现接口,属于动态代理的范畴)
通俗来讲,其实就是加了一个中介,比如我们可以自己看书学习,也可以让老师(代理对象)看书(目标对象)后来教我们,这样的优势是老师讲的时候可以对书(目标对象)扩展额外知识
2. 静态代理
静态代理:在使用时需要定义接口或父类,被代理的对象(目标对象)与代理对象一起实现相同的接口或者继承相同的父类。
类图:
就拿老师代理看书的例子,目标接口:
public interface LookBook {
void study();
}
目标接口的实现:
public class LookBookDao implements LookBook {
@Override
public void study() {
System.out.println("看书");
}
}
使用代理:
/**
* 需要去实现或者继承同一个接口或类
*/
public class LookBookProxy implements LookBook{
// 通过聚合,这是真实/目标对象
private LookBook lookBook;
public LookBookProxy(LookBook lookBook) {
this.lookBook = lookBook;
}
@Override
public void study() {
System.out.println("代理...");
lookBook.study();
System.out.println("老师讲授,扩展书外知识");
}
}
客户端:
public class Client {
public static void main(String[] args) {
// 创建目标对象
LookBookDao lookBookDao = new LookBookDao();
// 创建代理对象
LookBookProxy lookBookProxy = new LookBookProxy(lookBookDao);
// 看似操作的是代理对象,实际上操作的是目标对象,只不过我们不知道,而且代理对象还可以在原目标对象基础上扩展功能。
lookBookProxy.study();
}
}
静态代理的优缺点:
- 优点:在不修改目标对象的方法的功能的前提下,能通过代理对象对目标功能扩展。
- 缺点:如果目标类有多个那么可能需要创建很多代理类,工作量很大,不易管理。代理类可以有很多个(不同功能),一旦接口增加方法,那么目标类和代理类都需要维护,工作量也是很大。
例子:实现了Runnable接口通过Thread的start()调用就用到了静态代理。
3. 动态代理
动态代理:代理对象不需要实现接口,但是目的对象实现接口,否则不能动态代理。代理对象的生成是利用JDK中的API(flect包中的Proxy类)动态的在内存中构建代理对象。
动态代理也称为JDK代理或接口代理。
类图:
现在修改上面例子的代码,除了LookBook和LookBookDao不用修改,其他需要修改:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
/**
* newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* ClassLoader loader:当前目标对象的类加载器,获取加载器的方法是固定的
* Class<?>[] interfaces:目标对象实现的接口类型,使用泛型的方式确认类型
* InvocationHandler h:事情处理,执行目标对象的方法,会触发事件处理器方法,把当前执行的目标对象的方法作为参数传入
*/
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("JDK代理开始!!");
// 使用反射,调用目标对象的方法
// 返回执行的结果,如果没有返回值则返回null
Object returnValue = method.invoke(target, args);
System.out.println("JDK代理提交!!");
return returnValue;
}
});
}
}
客户端:
public class Client {
public static void main(String[] args) {
// 创建目标对象
LookBook lookBookDao = new LookBookDao();
// 创建代理对象,返回的类型可以强转
LookBook proxyInstance = (LookBook) new ProxyFactory(lookBookDao).getProxyInstance();
// 内存中就动态生成代理对象
System.out.println(proxyInstance.getClass()); // 输出:class com.sun.proxy.$Proxy0,只要看到$Proxy0,说明是代理对象
// 调用方法
proxyInstance.study();
}
}
输出:
如果在接口新增方法:
public interface LookBook {
void study();
String hello(String bookName);
}
实现类:
public class LookBookDao implements LookBook {
@Override
public void study() {
System.out.println("看书");
}
@Override
public String hello(String bookName) {
return "Hello " + bookName;
}
}
那么我们不需要去修改代理类,直接在客户端调用:
public class Client {
public static void main(String[] args) {
// 创建目标对象
LookBook lookBookDao = new LookBookDao();
// 创建代理对象,返回的类型可以强转
LookBook proxyInstance = (LookBook) new ProxyFactory(lookBookDao).getProxyInstance();
// 内存中就动态生成代理对象
System.out.println(proxyInstance.getClass()); // 输出:class com.sun.proxy.$Proxy0,只要看到$Proxy0,说明是代理对象
// 调用方法
proxyInstance.study();
// 调用有参方法
String bookName = proxyInstance.hello("《HeadFirst设计模式》");
System.out.println(bookName);
}
}
输出:
可以手动Debug试试。主要的点就是利用反射,让其运行时创建代理对象。
3. Cglib代理
静态代理和JDK代理都是要求目标对象实现一个接口,但有时候目标对象只是一个单独的对象,并没有实现任何接口,这时就可以使用目标对象子类实现代理,这就是Cglib代理。
Cglib代理也称为子类代理,会在内存中创建一个子类对象从而实现对目标对象功能扩展,有些书也把Cglib代理归类到动态代理中。
Cglib代理是一个高性能的代码生成包,可以在运行期间扩展Java类与实现Java接口,被广泛应用在许多AOP框架,比如Spring AOP实现方法拦截,不过Spring AOP其实有两种代理可供选择:
- 目标对象需要实现接口,用JDK代理;
- 目标对象不需要实现接口,用Cglib代理。
Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新类。
3.1 实现
需要导入cglib的jar包,ProxyFactory实现cglib包中的MethodInterceptor接口,重写接口的方法。在写getProxyInstance()方法时也需要使用到cglib包中的一个类:Enhancer类,看下面代码。
注意:
- 在内存中动态构建子类,注意代理的类不难为final,否则报错:java.lang.IllegalArgumentException。因为是需要创建子类。
- 目标对象的方法如果为final/static,那么就不会被拦截。
类图:
代码:去掉LookBook接口,留下LookBookDao。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 固定写法,如果要剖析需要到底层。现在不学
* @return
*/
public Object getProxyInstance() {
// 1. 创建一个工具类
Enhancer enhancer = new Enhancer();
// 2. 设置父类
enhancer.setSuperclass(target.getClass());
// 3. 设置回调函数
enhancer.setCallback(this);
// 4. 创建子类,即代理对象
return enhancer.create();
}
/**
* 这下面其实跟JDK代理类似
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(" Cglib代理模式开始");
Object returnValue = method.invoke(target, objects);
System.out.println(" Cglib代理模式提交");
return returnValue;
}
}
客户端:
public class Client {
public static void main(String[] args) {
// 创建目标对象
LookBookDao lookBookDao = new LookBookDao();
// 创建代理对象,返回的类型可以强转
LookBookDao proxyInstance = (LookBookDao) new ProxyFactory(lookBookDao).getProxyInstance();
// 内存中就动态生成代理对象
System.out.println(proxyInstance.getClass()); // 输出:class com.sun.proxy.$Proxy0,只要看到$Proxy0,说明是代理对象
// 调用代理对象的方法,触发interceptor方法,从而实现对目标对象的调用
proxyInstance.study();
}
}
输出:
应用:Spring的AOP会用到动态代理。
4. 代理模式的变体
4.1 防火墙代理
内网通过代理穿透防火墙,实现对公网的访问。
4.2 缓存代理
比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则成功,否则再到公网或者数据库去取,然后缓存。
4.3 远程代理
远程对象的本地代表,通过它可以把远程对象当成本地对象使用。远程代理通过网络和真正的远程对象沟通信息。比如RPC,dubbo
4.4 同步代理
主要使用在多线程中,完成多线程间的同步工作。
参考:韩顺平老师的设计模式