基本介绍
代理模式的核心思想就是:为一个对象(被代理对象)提供一个代理对象,并且通过代理对象控制对原来被代理对象的访问。可以简单理解为通过代理对象访问目标对象。这样做最大的好处就是可以在目标对象实现的基础上,增强额外的功能,起到扩展目标对象的效果。
被代理的对象可以是远程对象、创建时开销大的对象、需要安全控制的对象等。实现代理模式有不同的形式,主要有:静态代理、动态代理(也被称为JDK代理或接口代理)、Cglib代理(不需要实现接口,在内存中动态创建代理对象)。
单纯的通过文字描述大家可能对代理模式的理解还是不够深刻,我们可以通过一个简单的UML类图来加深对该模式的理解。
代理模式UML类图:
UML类图讲解:
TargetObject:表示目标对象也就是被代理的对象。
ProxyObject:代表的是代理对象。
Client:代表客户端,使用代理对象的类。
以上就是代理模式的一些基本概念,通过这些介绍相信大家对代理模式已经有了一个简单的认识,接下来就让菜鸟详细介绍一下上面提出的三种实现代理模式的方式,从而更加深入的了解代理模式。
静态代理
使用静态代理时需要让目标对象和代理对象一起实现相同的接口或者继承相同的父类。这样做的目的就是为了通过调用代理对象中和目标对象相同的方法来实现调用目标对象的方法,从而达到代理的效果。
静态代理UML类图:
代码实现:
代理对象和目标对象实现的共同接口
public interface CommonInterface {
void method();
}
目标对象
public class TargetObject implements CommonInterface {
@Override
public void method() {
System.out.println("被代理的类中的方法执行了!");
}
}
代理对象
public class ProxyObject implements CommonInterface {
// 通过接口聚合被代理的类
private CommonInterface target;
// 构造器
public ProxyObject(CommonInterface target) {
this.target = target;
}
@Override
public void method() {
System.out.println("开始静态代理!");
// 执行目标类的方法。
this.target.method();
System.out.println("结束静态代理!");
}
}
客户端测试类
public class Client {
public static void main(String[] args) {
TargetObject targetObject = new TargetObject();
ProxyObject proxyObject = new ProxyObject(targetObject);
proxyObject.method();
}
}
执行结果
总结
优点:实现起来简单,并且容易理解,只要确保目标对象和代理对象实现共同的接口或继承相同的父类就可以在不修改目标对象的前提下进行扩展。
缺点:缺点也是显而易见的,必须有共同接口或父类,并且需要为每一个目标类维护一个代理类,当需要代理的类很多时会创建出大量代理类。一旦接口或父类的方法有变动,目标对象和代理对象都需要作出调整。
这种方式虽说简单但是灵活性不足。
动态代理(JDK代理、接口代理)
使用JDK代理的基本前提是目标对象必须要实现接口。使用该方式代理对象是不需要手动编写代理类的,代理类是通过JDK的API动态的在内存中创建的。
动态代理UML类图
代码实现JDK代理
目标对象需要实现的接口
public interface TargetInterface {
void method();
}
目标对象
public class TargetObject implements TargetInterface {
@Override
public void method() {
System.out.println("被代理的类中的方法执行了!");
}
}
代理工厂
public class ProxyFactory {
// 目标对象
private Object target;
// 构造器
public ProxyFactory(Object target) {
this.target = target;
}
// 通过JDK的API在内存中动态生成一个代理对象。
public Object getProxyInstance() {
/**
* 参数介绍
* ClassLoader loader:目标对象的类加载器
* Class<?>[] interfaces:目标对象实现的接口
* InvocationHandler h:事件处理器。执行目标对象的方法时,
* 会触发该处理器中的invoke方法。
*/
return Proxy
.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
/**
* 此处用到了java8的Lambda表达式。看不太懂的有可以用传统方式。
* 传统方式:new InvocationHandler() { // 重写invoke方法 }
*
* 参数说明:
* Object proxy:代理类的实例
* Method method:目标对象方法
* Object[] args:目标对象方法入参
*/
(proxy, method, args) -> {
System.out.println("JDK动态代理开始!");
// 执行目标类的方法。returnVal是方法的返回值,没有返回值时为空。
Object returnVal = method.invoke(target, args);
System.out.println("JDK动态代理结束!");
return returnVal;
});
}
}
客户端测试类
public class Client {
public static void main(String[] args) {
TargetObject targetObject = new TargetObject();
ProxyFactory proxyFactory = new ProxyFactory(targetObject);
// 注意:此处强转的类型只能是目标类实现的接口类型!!!
TargetInterface proxyInstance = (TargetInterface) proxyFactory.getProxyInstance();
proxyInstance.method();
}
}
执行结果
总结
优点:使用JDK动态代理,可以将代理类的创建推迟到运行时期在内存中创建,这样就可以大大减少代理类的创建和维护。
缺点:目标类必须实现接口,还增加了代码的理解难度。
Cglib代理
基本介绍以及注意事项
1、使用Cglib进行动态代理的目标类不需要强制要求实现接口。
2、Cglib代理也叫做子类代理,它是在内存中动态的创建一个目标类的子类对象从而实现对目标类的代理。Cglib代理也是属于动态代理的一种。
3、Cglib是一个强大的高性能代码生成工具,它可以在程序运行时期扩展类与实现接口。许多AOP框架都有使用Cglib。
4、Cglib的底层是通过字节码处理框架ASM来转换字节码并生成新类的。
5、注意:使用Cglib代理的类不能是final修饰的,并且被代理类中final和static修饰的方法不会被代理。
Cglib代理UML类图
示例代码
目标对象
public class TargetObject {
public void method() {
System.out.println("被代理的类中的方法执行了!");
}
}
代理工厂
public class ProxyFactory implements MethodInterceptor {
// 目标对象
private Object target;
// 构造方法
public ProxyFactory(Object target) {
this.target = target;
}
// 创建目标对象
public Object getProxyInstance() {
// 创建一个工具类
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(target.getClass());
// 设置回调函数
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
/**
* 重写intercept,完成对目标对象中方法的扩展
*
* 参数介绍:
* Object o:cglib动态生成的对象。
* Method method:目标类中的方法。
* Object[] objects:方法的参数。
* MethodProxy methodProxy:根据目标类的方法生成的代理方法。
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("Cglib代理模式开始!");
/**
* invoke方法入参说明:
* Object obj:方法所对应的类的对象,在该代码示例中指的就是目标类。
* Object... args:方法的入参。
*/
Object invokeValue = method.invoke(target, objects);
System.out.println("Cglib代理模式结束!");
return invokeValue;
}
}
客户端测试类
public class Client {
public static void main(String[] args) {
TargetObject targetObject = new TargetObject();
ProxyFactory proxyFactory = new ProxyFactory(targetObject);
TargetObject proxyInstance = (TargetObject) proxyFactory.getProxyInstance();
// 执行代理对象的方法,会触发ProxyFactory类的intercept方法,从而实现对目标对象的调用。
proxyInstance.method();
}
}
执行结果
总结:
优点:不需要额外维护代理类,并且也不需要强制要求被代理的目标对象实现接口,使得代码更加的灵活。
缺点:和JDK的动态代理一样,增加了代码的理解难度。
总结
通过“菜鸟”的介绍相信大家对代理模式也有了一定的认识。以上介绍的三种方式其使用时的选择时机需要根据实际情况考虑,一般当我们需要代理的类少的时候,可以考虑使用静态代理,但是静态代理的选择一般都比较少,因为其维护成本要高,并且局限也大。当被代理的目标类实现了接口时可以考虑使用JDK代理,反之当目标类没有实现接口时就只能选择使用Cglib代理了。
今天的分享就到这里了,如果感觉“菜鸟”写的文章还不错,记得点赞加关注呦!你们的支持就是我坚持下去的动力。文章哪里写的有问题的也希望大家可以指出,我会虚心受教。