你还不懂设计模式? - 代理模式

代理模式(Proxy Pattern),就是使用一个代理类来代理另外一个类的功能。在Java相关的技术框架中使用非常广泛。搞清楚代理模式,是读懂Java相关技术框架源码的基础。
下面来看一下代理模式实现的几种方式:

  • 静态代理
    先创建一个代理接口:
public interface IUserService {
    void say(String words);
}

再创建一个接口的实现类

public class UserService implements IUserService {
    @Override
    public void say(String words) {
        System.out.println("hello ,"+words);
    }
}

这就是Java开发中常用的一种方式。然后再创建这个接口的静态代理类

public class UserServiceStaticProxy implements IUserService {
    /**
     * 接收保存目标对象
     */
    private IUserService target;

    public UserServiceStaticProxy(IUserService target) {
        this.target = target;
    }

    @Override
    public void say(String words) {
        System.out.println("添加代理之前逻辑");
        try {
            target.say(words);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("添加处理发生异常之后的逻辑");
        }
        System.out.println("添加代理之后的逻辑");
    }
}
  1. 优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.
  2. 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护

因此,静态代理模式是不推荐使用的。

  • 动态代理
    实际上动态代理是使用最为广泛的。而且也有几种不同的实现。如下:
  1. JDK动态代理
    在Java源码的java.lang.reflect包中提供了一个Proxy的工具类。用于生成一个类的动态代理。

使用Java动态代理需要满足以下条件:
① 代理的目标对象必须要实现一个接口,这也是Java动态代理的局限性所在
② 代理的目标对象是public的非抽象的类,如果不是public,则代理必须定义在目标对象所在包中

Java动态代理的实现方式如下:

public class JavaDynamicProxy {
    /**
     * 维护一个目标对象
     */
    private Object target;
    public JavaDynamicProxy(Object target){
        this.target=target;
    }

    /**
     * 给目标对象生成代理对象
     * @return
     */
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    System.out.println("开始事务2");
                    //执行目标对象方法
                    Object returnValue = method.invoke(target, args);
                    System.out.println("提交事务2");
                    return returnValue;
                }
        );
    }

}

测试:

        // 目标对象
        IUserService target = new UserService();
        // 原始的类型
        System.out.println(target.getClass());
        // 给目标对象,创建代理对象
        IUserService proxy = (IUserService) new JavaDynamicProxy(target).getProxyInstance();
        // class $Proxy0   内存中动态生成的代理对象
        System.out.println(proxy.getClass());
        // 执行方法   【代理对象】
        proxy.say("dongdong");
输出结果:
class com.dong.designs.proxy.UserService
class com.sun.proxy.$Proxy3
开始事务2
hello ,dongdong
提交事务2

Process finished with exit code 0

下面再来看一下Java动态代理在Mybatis框架中的应用。 最典型的就是MapperProxy 。这是所有Mapper接口的代理实现。也是mybatis核心设计逻辑的体现。

构建InvocationHandler实例,它是Proxy生成代理对象的必须参数,包装了代理目标。

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

  @UsesJava7
  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
      throws Throwable {
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    return constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  /**
   * Backport of java.lang.reflect.Method#isDefault()
   */
  private boolean isDefaultMethod(Method method) {
    return (method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
        && method.getDeclaringClass().isInterface();
  }
}

在MapperProxyFactory中生成代理对象实例:

public class MapperProxyFactory<T> {
  //代理的接口
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //使用Proxy.newProxyInstance()API来生成代理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
  1. Cglib动态代理
    在spring aop中,可以指定生成代理的当时,Java动态代理和Cglib 。对于没有实现接口的对象。默认使用Cglib来生成代理。下面来看一下Cglib工具包是如何创建一个代理对象的。
public class CglibProxy implements MethodInterceptor {
    //维护目标对象
    private Object target;
    public CglibProxy(Object target) {
        this.target = target;
    }

    //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();

    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("开始事务...");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("提交事务...");
        return returnValue;
    }
}

Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用。Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

  1. javassist实现动态代理

Javassist是一个开源的分析、编辑和创建Java字节码的类库。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

先来看一下基本使用:

ClassPool pool = ClassPool.getDefault();
        //构建一个接口 类的全限定名
        CtClass sClass = pool.makeInterface("com.dong.designs.proxy.Animal") ;
        sClass.addMethod(CtNewMethod.make("void sayHello(String words);",sClass));
        sClass.toClass();

        //构建一个猫作为子类
        String className = "com.dong.designs.proxy."+"cat$javassit";
        CtClass ctClass = pool.makeClass(className);

        //生成两个私有属性 name 和 age
        CtField field1  = new CtField(CtClass.charType,"name",ctClass);
        field1.setModifiers(Modifier.PRIVATE);

        CtField field2 = new CtField(CtClass.intType,"age",ctClass);
        field2.setModifiers(Modifier.PRIVATE);

        ctClass.addField(field1);
        ctClass.addField(field2);
        //添加无参构造函数
        ctClass.addConstructor(CtNewConstructor.defaultConstructor(ctClass));
        //生成getter和setter方法
        ctClass.addMethod(CtNewMethod.getter("getName",field1));
        ctClass.addMethod(CtNewMethod.getter("getAge",field2));
        ctClass.addMethod(CtNewMethod.setter("setName",field2));
        ctClass.addMethod(CtNewMethod.getter("setAge",field2));

        //创建一个 sayHello的方法
        ctClass.addMethod(CtNewMethod.make("public void sayHello(String words){ System.out.println(words);}",ctClass));

        //设置Cat类实现接口Animal
        ctClass.addInterface(sClass);

        //生成class
        Class<?> aClass = ctClass.toClass();
        //实例化一个Cat类
        Object o = aClass.newInstance();
        System.out.println(o.getClass());
        //ctClass.defrost()方法后,可以继续动态修改这个类的类容。
        ctClass.defrost();
        //再给cat添加一个方法
        ctClass.addMethod(CtNewMethod.make("public void testDynamicChangeClass(){}",ctClass));
        //将cat的class文件写入到指定文件夹
        ctClass.writeFile("D:\\mypro\\letcode\\src");

执行完之后,会在对应目录生成一个 class文件。反编译之后的类容如下:

package com.dong.designs.proxy;

public class cat$javassit implements Animal {
    private char name;
    private int age;

    public cat$javassit() {
    }

    public char getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public void setName(int var1) {
        this.age = var1;
    }

    public int setAge() {
        return this.age;
    }

    public void sayHello(String var1) {
        System.out.println(var1);
    }

    public void testDynamicChangeClass() {
    }
}

再来看一下它如何能实现动态代理功能

① 先手动新增一个类,如下:

public class CatService {
    public void sayHello(String word){
        System.out.println("the cat is saying : hello "+word);
    }
}

② 使用Javassist动态在目标类方法执行之前添加自己的执行逻辑

    ClassPool classPool = ClassPool.getDefault();

    CtClass ctClass = classPool.get("com.dong.designs.proxy.CatService");
    CtMethod m = ctClass.getDeclaredMethod("sayHello");
    m.insertBefore("{ System.out.println(\"BB测试javassist动态修改class类容\"); }");
    m.insertAfter("{ System.out.println(\"AA测试javassist动态修改class类容\"); }");
    Class<?> c = ctClass.toClass();
    CatService h = (CatService)c.newInstance();
    h.sayHello("hello world");
    
输出结果:

  BB测试javassist动态修改class类容
  the cat is saying : hello hello world
  AA测试javassist动态修改class类容
  

可以看到,成功的在目标方法执行前后加上了自己的逻辑。实现了动态代理的效果。

在Dubbo框架中默认使用 javassist来实现类的动态代理。动态的生产类的代理对象。dubbo的实现比较复杂,有兴趣可以去研究一下dubbo源码中JavassistProxyFactory的详细实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值