Java代理模式

代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

java中的代理分为:静态代理、动态代理。动态代理又分为jdk代理和Cglib代理。

静态代理

静态代理在使用时,需要定义接口或者父类,被代理的对象和代理对象需要一起实现同一个接口或者继承同一个父类。

示例:

  • 抽象接口

    1
    2
    3
    public interface User {
    String getName(String name);
    }
  • 委托类

    1
    2
    3
    4
    5
    6
    public class UserImpl implements User{
    @Override
    public String getName(String name) {
    return "name is " + name;
    }
    }
  • 代理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class UserProxy implements User {

    private User user;

    public UserProxy(User user) {
    this.user = user;
    }

    @Override
    public String getName(String name) {
    String result = user.getName(name);
    System.out.println(result);
    return result;
    }
    }
  • Demo

    1
    2
    3
    4
    5
    6
    public class Main {
    public static void main(String[] args){
    UserProxy userProxy = new UserProxy(new UserImpl());
    userProxy.getName("张三");
    }
    }

    总结:

    代理类不仅是一个隔离客户端和委托类的中介,还可以通过代理类在不修改原有代码的前提下增加一些新功能,是开闭原则(Open for Extension, Closed for Modification)最典型的实践。

    代理类可以为委托类预处理消息、过滤消息、把消息转发给委托类以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法来提供特定的服务。

  • 优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展;

  • 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,同时,一旦接口增加方法,目标对象与代理对象都要维护。

动态代理

静态代理的缺点怎么改进呢?让代理类动态的生成是不就可以了呢,也就是动态代理

动态代理两种最常见的方式:

  1. 通过实现接口的方式 -> JDK动态代理
  2. 通过继承类的方式 -> CGLIB动态代理

JDK动态代理

JDK动态代理又称接口代理,基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。

JDK动态代理主要涉及两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

InvocationHandler 和 Proxy 的主要方法介绍:

  • java.lang.reflect.InvocationHandler

    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用
    *
    * @param proxy 被代理的对象
    * @param method 要调用的方法
    * @param args 方法调用时所需要参数
    */
    Object invoke(Object proxy, Method method, Object[] args)
  • java.lang.reflect.Proxy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法
    *
    * @param loader 指定当前目标对象使用类加载器,获取加载器的方法是固定的
    * @param interfaces 目标对象实现的接口的类型,使用泛型方式确认类型
    * @param h InvocationHandler接口的子类的实例
    */
    public static Object newProxyInstance(ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h)

    推荐使用Guava中的 com.google.common.reflect.ReflectionnewProxy方法,更加方便

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
    * 构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法
    *
    * @param interfaceType
    * @param handler
    */
    public static <T> T newProxy(Class<T> interfaceType, InvocationHandler handler)
    /**
    * 返回指定接口的代理类
    *
    * @param loader 目标对象的类加载
    * @param interfaces 目标对象实现的接口类型
    */
    public static Class<?> getProxyClass(ClassLoader loader,
    Class<?>... interfaces)
    /**
    * 获取指定代理对象所关联的调用处理器
    */
    public static InvocationHandler getInvocationHandler(Object proxy)
    /**
    * 判断cl是否为一个代理类
    */
    public static boolean isProxyClass(Class<?> cl)
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface User {
String getName(String name);
}

public static void main(String[] args) {
User user = (User) Proxy.newProxyInstance(User.class.getClassLoader(), // 传入ClassLoader
User.class.getInterfaces(), // 传入要实现的接口
(object, method, para) -> {
System.out.println(method);
if (method.getName().equals("getName")) {
System.out.println("name is " + args[0]);
}
return null;
}); // 传入处理调用方法的InvocationHandler

user.getName("张三");
}

在运行期动态创建一个interface实例的步骤如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    1. 使用的ClassLoader,通常就是接口类的ClassLoader;
    2. 需要实现的接口数组,至少需要传入一个接口进去;
    3. 用来处理接口方法调用的InvocationHandler实例。
  3. 将返回的Object强制转型为接口。

Cglib动态代理

Cglib动态代理又称子类代理,基于ASM机制实现,通过生成业务类的子类作为代理类。

Cglib子类实现代理的方法:
  1. 需要引入cglib和asm的jar包文件,Spring的核心包里面已经包含了此包;
  2. 引入jar包后,就可以在内存中动态构建子类;
  3. 代理的类不能为final,否则报错;
  4. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的方法。
示例:
  • 被代理类:

    1
    2
    3
    4
    5
    public class UserImpl {
    public void getName(String name){
    System.out.println("name is " + name);
    }
    }
  • 实现MethodInterceptor接口生成方法拦截器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class UserMethodInterceptor  implements MethodInterceptor{
    /**
    * @param object 表示要进行增强的对象
    * @param method 表示拦截的方法
    * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
    * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
    */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("Before: " + method.getName());
    // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
    Object object = methodProxy.invokeSuper(o, objects);
    System.out.println("After: " + method.getName());
    return object;
    }
    }
  • Demo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import net.sf.cglib.proxy.Enhancer;

    public class CglibTest {
    public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    //继承被代理类
    enhancer.setSuperclass(UserImpl.class);
    //设置回调
    enhancer.setCallback(new UserMethodInterceptor());
    //设置代理类对象
    UserImpl user = (UserImpl) enhancer.create();
    //在调用代理类中方法时会被我们实现的方法拦截器进行拦截
    user.getName("张三");
    }
    }

在Spring的AOP编程中:

  • 如果加入容器的目标对象有实现接口,用JDK代理
  • 如果目标对象没有实现接口,用Cglib代理
分享到: