代理模式是一种结构型设计模式。代理模式的思想是:给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
代理模式涉及3个角色:
- 抽象对象(Subject):代理和目标的共同接口。共同的接口使得任何可以使用目标对象的地方都可以使用代理对象。
- 目标对象(Target):要被代理的对象,也称为委托者。
- 代理对象(Proxy):内部包含了一个目标对象的引用,从而代替目标对象为客户提供服务。
静态代理
静态代理模式可以在不修改目标对象的功能前提下,对目标功能扩展。结构图如下:
代码实现:
/* 抽象角色 */
public interface Subject {
void method1();
}
/* 目标(委托类) */
public class Target implements Subject {
@Override
public void method1() {
System.out.println("method 1");
}
}
/* 代理 */
public class Proxy implements Subject {
Target target; // 包装一个目标类型对象
public Proxy(Target target) {
this.target = target;
}
@Override
public void method1() {
System.out.println("===start==="); // 目标方法执行前的操作
target.method1();
System.out.println("=== end ==="); // 目标方法执行后的操作
}
}
// 测试
class ProxyTest {
public static void main(String[] args) {
Subject subject = new Proxy(new Target());
subject.method1();
}
}
运行结果:
===start===
method 1
=== end ===
可以看到,静态代理模式的缺点是很明显的:代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理;另外,如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,代码维护的复杂度很大。
JDK动态代理
动态代理类的原理是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和目标类(委托类)的关系是在程序运行时确定。
java.lang.reflect.Proxy
是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
java.lang.reflect.Proxy
的newProxyInstance()
方法要求传入3个参数,其中最后一个参数是一个调用处理器对象,即实现了java.lang.reflect.InvocationHandler
这个接口的类的实例。InvocationHandler
定义了一个invoke()
方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对目标类(委托类)的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
代码实现:
/* 抽象角色 */
public interface Subject {
void method1();
}
/* 目标类(委托类) */
public class Target implements Subject {
@Override
public void method1() {
System.out.println("method 1");
}
}
/* 动态代理生成类 */
public class ProxyFactory {
// 封装一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 获取代理对象的方法
public Object getProxy() {
return java.lang.reflect.Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("===start==="); // 目标方法执行前的操作
Object result = method.invoke(target, args);
System.out.println("=== end ==="); // 目标方法执行后的操作
return result; // 目标方法的返回值
}
);
}
}
// 测试
class ProxyTest {
public static void main(String[] args) {
Subject target = new Target();
Subject proxy = (Subject) new ProxyFactory(target).getProxy();
proxy.method1();
}
}
运行结果:
===start===
method 1
=== end ===
结构图:
可以看到,代理工厂和目标是完全松耦合的,根据传入的目标的类型不同,代理工厂可以生成不同类型的代理。这解决了静态代理模式代理类和目标耦合性强的问题。
另外java.lang.reflect.Proxy.newProxyInstance()
方法的第二个参数是目标对象实现的接口的类型,所以,使用JDK代理,目标类必须实现了接口。而且,代理的类型应该是接口的类型,像上面的测试代码,如果改成下面这样就会出错,因为代理对象无法强转成Target
类型。
public static void main(String[] args) {
Target target = new Target();
Target proxy = (Target) new ProxyFactory(target).getProxy(); // 类型转换异常
proxy.method1();
}
Cglib动态代理
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
JDK的动态代理有一个限制,就是使用动态代理的对象必须至少实现一个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP。
Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类。因此,要使用Cglib,必须引入cglib.jar
和asm.jar
。另外Spring也集成了cglib功能,使用Spring框架抢建的项目中可以直接使用Cglib的功能。
代码实现:
/* 目标,注意这里不再需要实现接口了 */
public class Target{
public void method1() {
System.out.println("method 1");
}
}
/* 代理工厂,注意和JDK代理工厂的区别 */
public class ProxyFactory implements MethodInterceptor {
// 封装一个目标对象
Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 实现MethodInterceptor接口中的拦截器方法,实现对目标的操作
@Override
public Object intercept(Object object, Method method,
Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("===start==="); // 目标方法执行前的操作
Object result = method.invoke(target, args);
System.out.println("=== end ==="); // 目标方法执行后的操作
return result; // 目标方法的返回值
}
// 获取代理对象的方法
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 将目标类(委托类)设为父类
enhancer.setCallback(this); // 设置回调
return enhancer.create(); // 生成目标类(委托类)的子类对象(即代表对象)并返回
}
}
// 测试
class ProxyTest {
public static void main(String[] args) {
Target target = new Target(); // 注意这里的类型不再是接口的类型了,因为并不存在接口
Target proxy = (Target) new ProxyFactory(target).getProxy();
proxy.method1();
}
}
运行结果:
===start===
method 1
=== end ===
结构图:
可以看到,跟JDK动态代理模式一样,Cglib动态代理模式与目标也是松耦合的,而且这里更简化了,目标并不须要实现任何接口。
代理模式的使用场景
- 当我们想要隐藏某个类时,可以为其提供代理类。
- 当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中金进行权限判断来进行不同权限的功能调用)。
- 当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行)。