代理模式
定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
代理模式有三种不同的形式:静态代理、动态代理(JDK代理、接口代理)、Cglib 代理(可以在内存中动态的创建对象,而不需要实现接口,属于动态代理的范畴)
目的:1. 通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要的复杂性
- 通过代理对象对原来的业务进行增强
抽象对象:声明了真实对象和代理对象的公共接口
真实对象:代理对象所代理的真实对象,最终被引用的对象。被代理的对象可以是远程对象、创建开销大的对象或者需要安全控制的对象。实现了抽象对象接口
代理对象:包含真实对象,从而操作真实对象,想当于访问者与真实对象之间的中介。实现了抽象对象接口
静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承相同的父类。
代码示例
抽象对象
package top.gldwolf.proxy.staticproxy;
/**
* 抽象对象,代理对象和被代理对象都要实现这个接口
*/
public interface ITeacherDao {
void teach();
}
被代理对象
package top.gldwolf.proxy.staticproxy;
/**
* 被代理的对象(目标对象、真实对象)
*/
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师正在授课中......");
}
}
代理对象
package top.gldwolf.proxy.staticproxy;
/**
* 代理对象
*/
public class TeacherDaoProxy implements ITeacherDao {
// 将被代理对象聚合到代理对象里来
private ITeacherDao teacherDao;
/**
* 通过构造器将被代理对象聚合到代理对象中
* @param target
*/
public TeacherDaoProxy(ITeacherDao target) {
this.teacherDao = target;
}
@Override
public void teach() {
// 扩展的功能可以在真正方法执行前后操作
System.out.println("代理开始......");
// 调用被代理对象的方法
teacherDao.teach();
System.out.println("代理结束......");
}
}
测试类
package top.gldwolf.proxy.staticproxy;
import org.junit.Test;
public class TestDriver {
@Test
public void testStaticProxy() {
// 创建目标对象
TeacherDao target = new TeacherDao();
// 创建代理对象,同时将被代理对象传递给代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(target);
// 通过代理对象,调用被代理对象的方法
teacherDaoProxy.teach();
}
}
静态代理的优缺点
- 优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
- 缺点:
- 因为代理对象需要与目标对象实现一样的接口,所以需要很多的代理类
- 一旦接口增加方法,目标对象与代理对象都需要维护
动态代理
- 代理对象不需要实现接口,但是目标对象要实现接口,否则不能使用动态代理
- 代理对象的生成是利用 JDK 的 API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK 代理、接口代理
JDK 中生成代理对象的 API
- 代理类所在的包:java.lang.reflect.Proxy
- JDK 实现代理只需要使用
newProxyInstance()
方法,但是该方法需要接收三个参数,完整的写法是:static Object newProxyInstance(ClassLoader loader, Class<?>[] interface, InvocationHandler h)
实现动态代理要包含以下几个组件:
- 抽象接口
- 目标对象(被代理的对象,实现了抽象接口)
- 代理工厂(聚合了一个目标对象,有一个方法返回一个代理对象,不需要实现抽象接口)
代码示例
抽象接口
package top.gldwolf.proxy.dynamic;
/**
* 动态代理也要有一个接口,但是只有被代理类需要实现这个接口
*/
public interface IteacherDao {
void teach();
}
目标对象
package top.gldwolf.proxy.dynamic;
/**
* 被代理类(目标类)
*/
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师正在授课......");
}
}
代理工厂
package top.gldwolf.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理工厂类
*/
public class ProxyFactory {
// 维护一个目标对象(注意,类型为 Object)
private Object target;
// 通过构造器,对 target 进行初始化
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 给目标对象生成一个代理对象
*
* 根据传入的对象(TeacherDao),即目标对象
* 利用反射机制,返回一个代理对象
* 然后通过代理对象,使用目标方法
* @return 代理对象执行目标方法后的返回值
*/
public Object getProxyInstance() {
/**
* 说明:
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interface,
* InvocationHandler h)
* 1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取类加载器的方式是固定的:obj.getClass().getClassLoader();
* 2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确定类型
* 3. InvocationHandler h: 事件处理器,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行的目标对象的方法作为参数传入
*/
// 获取目标对象的类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
// 获取目标对象实现的所有接口
Class<?>[] interfaces = target.getClass().getInterfaces();
// 创建一个事件处理器
InvocationHandler handler = new InvocationHandler() {
// 这个 proxy 就是 newProxyInstance() 方法返回的代理类对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理开始......");
// 利用反射,调用目标对象(被代理对象)的目标方法
// result: 目标对象执行方法的返回值
Object result = method.invoke(target, args);
System.out.println("动态代理结束......");
return result;
}
};
// 返回代理对象
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
测试类
package top.gldwolf.proxy.dynamic;
import top.gldwolf.proxy.dynamic.ITeacherDao;
/**
* JDK 动态代理测试类
*/
public class DynamicProxyClient {
public static void main(String[] args) {
// 创建一个目标类实例
TeacherDao target = new TeacherDao();
// 创建一个代理工厂,并将目标对象传入进去
ProxyFactory proxyFactory = new ProxyFactory(target);
// 调用 getProxyInstance() 方法来获取代理对象
ITeacherDao proxyInstance = (ITeacherDao) proxyFactory.getProxyInstance();
// 使用代理对象,调用目标对象的方法
proxyInstance.teach();
proxyInstance.hello();
}
}
Cglib 代理
- 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可以使用目标对象子类来实现代理,这个代理就是 Cglib 代理。
- Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象,从而实现对目标对象功能扩展,有些地方也将 Cglib 代理归属到动态代理的范畴。
- Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 Java 类与实现 Java 接口,被许多的 AOP 框架所使用,例如 Spring AOP,实现方法拦截等功能。
在 AOP 编程中如何选择代理模式:
- 目标对象需要实现接口,用 JDK 代理
- 目标对象不需要实现接口,用 Cglib 代理
Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
注意:
- 被代理的类不能是用 final 修饰的类,否则会报错
- 目标对象的方法如果是 final/static,那么就不会被拦截,即:不会执行目标对象方法外的其它业务方法
实现 Cglib 代理要包含以下几个组件:
- 目标对象(被代理的对象)
- 代理工厂
- 聚合了一个目标对象
- 需要实现 MethodInterceptor 接口,需要实现 intercept() 方法来完成对被代理对象(目标对象)方法的调用
- 需要写一个 getProxyInstance() 方法,来返回一个代理对象
代码示例
目标对象
package top.gldwolf.proxy.cglib;
/**
* 被代理对象(目标对象)
*/
public class TeacherDao {
public void teach() {
System.out.println("老师正在授课......");
}
}
代理工厂
package top.gldwolf.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Cglib 代理工厂类
*/
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();
}
/**
* 重写 intercept() 方法,会调用目标对象的方法
*
* 所有创建的被代理的方法都调用这个方法而不是原来的方法,
* 原来的方法要么使用反射通过 Method 对象来调用,
* 要么使用方法代理来调用
* @param obj 自身,被 enhance 的对象
* @param method 被拦截的方法
* @param args 参数列表
* @param methodProxy 用来调用原始的方法(未被代理的方法),可以根据需要调用任意多次
* @return 任意与原始方法兼容的返回值类型的值,如果原始方法为 void,则会忽略这个值
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib 代理开始......");
Object result = method.invoke(target, args);
System.out.println("Cglib 代理结束......");
return result;
}
}
测试类
package top.gldwolf.proxy.cglib;
/**
* Cglib 代理的测试客户端
*/
public class CglibProxyClient {
public static void main(String[] args) {
// 创建目标对象
TeacherDao target = new TeacherDao();
// 创建代理工厂对象
ProxyFactory proxyFactory = new ProxyFactory(target);
// 获取代理对象
TeacherDao proxyInstance = (TeacherDao) proxyFactory.getProxyInstance();
// 使用代理对象调用目标方法
proxyInstance.teach();
}
}
代理模式的变体
防火墙代理
内网通过代理穿透防火墙,实现对公网的访问
缓存代理
比如:当请求图片文件等资源时,先到缓存代理中取,如果取到资源则返回,如果取不资源,再到公网或者数据库中取,然后缓存
远程代理
远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息
同步代理
主要使用在多线程中,完成多线程间同步的工作