Java 动态代理原理及其在mybatis中的应用

本文探讨了代理模式的基本概念,从静态代理到Java动态代理的原理及实现,详细解释了如何通过Proxy.newProxyInstance()创建代理对象。此外,文章还展示了Java动态代理在Mybatis框架中的应用,通过sqlSession.getMapper()方法生成接口的动态代理实例,揭示了Mybatis底层的代理逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代理是一种基本的设计模式,代理模式的主要作用是为其他对象(被代理的对象,下面称为原对象)提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理对象既可以将客户端的请求完全转发给原对象,也可以提供一些额外或不同的操作。这篇文章从静态代理模式说起,然后到Java的动态代理最后分析一个动态代理在mybatis应用的实例。

1.静态代理

静态代理相当于为每个需要被代理的类构建一个代理类出来,然后将代理类的对象作为被代理类对象提供给外部操作。下面是一个简单的示例:

public interface Car {

     double getPrice();
     String getName();
}

public class Bicycle implements Car {

    private double price = 200;
    private String name  = "bicycle";

    @Override
    public double getPrice() {
        System.out.println("bicycle's price is " + price);
        return price;
    }

    public String getName() {
        System.out.println("name is " + name);
        return name;
    }
}

public class SimpleProxy implements Car {

    private Car proxied; //被代理对象

    public SimpleProxy(Car proxied) {
        this.proxied = proxied;
    }

    //对价格的访问会被直接拦截掉
    public double getPrice() {
        System.out.println("car price is 1000");
        return 1000;
    }

    //对名字的访问会被直接转发给被代理对象
    public String getName() {
        return proxied.getName();
    }
}

Car是一个接口而Bicycle是Car的一个具体子类,SimpleProxy也实现了Car接口并且内部还只有一个Car类型的引用,所以可以作为Bicycle代理对象使用。下面是对上面代理的测试:

public class TestProxy {

    public static void getCarInfo(Car car) {
        car.getPrice();
        car.getName();
    }

    public static void main(String[] args) {
        //代理对象需要持有被代理对象的引用,用于做请求的转发
        Car car = new SimpleProxy(new Bicycle());
        getCarInfo(car);
    }
}
/**
car price is 1000
name is bicycle
*/

对于getCarInfo方法来说,它接受的参数是接口类型,所以无法真正知道获得的是Bicycle对象还是SimpleProxy对象,因为这两种都实现了Car接口。这样的情况下,SimpleProxy实际上已经被插入到客户端和Bicycle对象之间作为一个中间人的角色,它拦截了对被代理对象方法的调用,对于其中一些方法它直接返回了自定义的信息,对于另外一些方法它转发给了被代理对象。这种静态代理的好处在于通过代理类就可以对原对象的某些操作进行修改,而不用去修改原对象的代码。

2.java 动态代理

对于上面展示的静态代理来说,需要对每一个类型(实现同一个接口的子类)都手动的编写一个代理类,在实际应用中这是一个很大的限制(比如说有时候所需要代理的类是从外部引用的,没有源码)。而Java的动态代理解决了这个问题,它可以动态的创建代理并动态的处理对所代理方法的调用。不过与静态代理不同,动态代理不会为被代理对象中的每一个方法都提供一个代理实现,而是将在动态代理上做的所有调用都重定向到一个单一处理器(方法)上,然后在这个方法上根据调用的类型进行转发。先来看一个具体的实例,对上面的SimpleProxy进行改写,将其变成动态代理模式:

public class DynamicProxyHandler implements InvocationHandler {

    //被代理对象
    private Object proxied;

    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    //proxy是代理对象,Method是代理对象中被调用的谋改革方法,args是调用这个method是传入的参数.
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         //这里只是简单的打印出被拦截的方法,然后调用proxied对象的相应方法进行处理
        System.out.println("拦截了: "+method.getName());
        return method.invoke(proxied,args);
    }
}

public class TestProxy {

    public static void getCarInfo(Car car) {
        car.getPrice();
        car.getName();
    }

    public static void main(String[] args) {
        Bicycle bicycle = new Bicycle();
        //使用Proxy创建一个动态代理
        Car car = (Car) Proxy.newProxyInstance(bicycle.getClass().getClassLoader(), new Class<?>[]{Car.class}, new DynamicProxyHandler(bicycle));
        getCarInfo(car);
    }
}

/**
拦截了: getPrice
bicycle's price is 200.0
拦截了: getName
name is bicycle
*/

上面这段代码首先实现了InvocationHandler接口,这个接口里面其实就一个invoke()方法:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

每一个动态代理对象都会关联一个InvocationHandler对象,当代理对象的任何方法被调用的时候,都会被转发到InvocationHandler对象的invoke()方法上去的。在上面DynamicProxyHandler的invoke()方法中,只是简单的打印出被拦截方法的名称然后直接调用了DynamicProxyHandler中持有的实际对象中相关的方法。
还有一点需要注意的就是invoke()方法中传入的proxy对象是代理对象,这是为了防止需要区分请求的来源,在很多情况下我们是不需要关心这一点的,但是如果再次调用这个对象上的方法,会被转发给当前的invoke()对象,这样可能会造成死循环。
在测试代码中通过Proxy.newProxyInstance()来创建了一个代理对象,Proxy类也是java.lang.reflect包下的成员。这个方法接受三个参数:

 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

首先是一个ClassLoader对象,一般可以通过已载入的类获得;然后是一个Class对象数组,这是被代理对象实现的接口的Class对象列表;最后就是前面提到的InvocationHandler对象,这里是DynamicProxyHandler对象。
动态代理的一般模式是会在代理对象上执行被代理的操作,然后使用Method.invoke()方法将请求转发给被代理对象处理,并传入必须的参数。这样看起来作用并不是很大,但实际使用的时候是可以对传入的参数进行处理的并且还可以根据Method中获取的信息来决定如何对方法进行转发,处理过程是可以非常灵活的。

3.java 动态代理的实现原理

通过上面的例子基本上能够明白动态代理的基本用法了,但是动态代理到底是如何实现的呢,具体来说Proxy.newProxyInstance()是如何根据传入的参数生成代理对象的,我们实现的DynamicProxyHandler中的invoke()方法时什么时候由谁来负责进行调用的。为了解释上面的问题,先从Proxy.newProxyInstance()源码入手:

 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) { //安全性检查
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        //根据传入的ClassLoader和接口的Class对象,构造出代理类的Class对象
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            //拿到代理对象构造器的Constructor对象
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
                //通过反射的方式通过Constructor获取代理对象,
                //InvocationHandler作为构造器参数传入。这里返回的是Object类型的对象。
                return newInstance(cons, ih);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

上面的大致流程是通过传入的ClassLoader和interface的Class数组对象获得代理类的Class对象,并获取Class对象的构造器Constructor对象,然后将InvocationHandler作为构造器器参数通过反射调用Constructor的newInstance()方法获取到代理对象。
这里的逻辑不复杂可以算是反射机制的应用,问题的关键在getProxyClass0(loader, intfs)方法是如何根据传入的loader和intfs获得代理类Class对象的。下面是这个方法的源码:

 private static Class<?> getProxyClass0(ClassLoader loader,
                                    Class<?>... interfaces) {

         //如果这个被代理类实现的接口数目超过65535,则直接抛异常
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        //proxyClassCache是用来缓存代理类的一个Cache
        return proxyClassCache.get(loader, interfaces);
    }

这里没有具体的实现逻辑,这里先看下上面用到的proxyClassCache对象。

 private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

其中的ProxyClassFactory就是用来创建ProxyClass的工厂。在proxyClassCache.get()方法中有很多的缓存逻辑,但是如果需要的代理对象没有缓存的话,在里面是通过ProxyClassFactory的apply()方法来获取的,下面是apply()方法的源码:

          //根据传入的loader和interfaces构造代理类的Class对象
          @Override
          public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            //遍历传入接口的Class对象数组
            for (Class<?> intf : interfaces) {
                Class<?> interfaceClass = null;
                try {
                    //使用传入的ClassLoader将接口再重新载入一遍,主要是保证需要代理类和其实现的接口都是同一个类加载器加载的。
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                //如果两者不是同一个类加载器加载的,则不能构造代理对象,直接抛出异常
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                 //确认传入的Class对象确实是代表接口的
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                //确定没有重复指定接口,如果传入的接口Class对象数组中,有两个对象重复,则会抛出这个异常
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            //代理对象所属的包路径
            String proxyPkg = null;    

            //找到非public的接口,然后将代理类同样放到这个包下,
            //需要保证所有非public接口都在同一个包路径下,否则会报错。
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                //找到不到非public类型的接口,则指定默认包为
                //com.sun.proxy
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            //为代理类生成全限定类名,这里的proxyClassNamePrefix是一个固定字符串"$Proxy",而num是利用AtomicLong生成的一个唯一的数字编号
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

           //下面是真正生成代理类Class对象的地方

           //首先是通过给ProxyGenerator.generateProxyClass()方法传入刚生成的全限定和接口Class对象数组,这个方法负责生成代理类的字节码。
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
            try {
                //拿到字节码之后,就通过defineClass0()方法生成代理类的Class对象。而这其实是一个本地方法(native方法),也就不具体分析了。
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

最后来看下ProxyGenerator.generateProxyClass()方法,这个方法负责根据传入的代理类权限定名和代理类需要实现接口的Class对象数组来生成代理类的字节码数组。

public static byte[] generateProxyClass(final String name,  
                                        Class[] interfaces)  
   {  
       ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
    // 这里动态生成代理类的字节码,涉及到class文件字节码格式的拼接,比较繁琐就不进去看了。 
       final byte[] classFile = gen.generateClassFile();  

    // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码以class文件的形式存储到磁盘。 
       if (saveGeneratedFiles) {  
           java.security.AccessController.doPrivileged(  
           new java.security.PrivilegedAction<Void>() {  
               public Void run() {  
                   try {  
                       FileOutputStream file =  
                           new FileOutputStream(dotToSlash(name) + ".class");  
                       file.write(classFile);  
                       file.close();  
                       return null;  
                   } catch (IOException e) {  
                       throw new InternalError(  
                           "I/O exception saving generated file: " + e);  
                   }  
               }  
           });  
       }  

    // 返回代理类的字节码  
       return classFile;  
   }  

经过上面的一番分析,对Proxy.newInstance()方法是如何生成一个动态代理对象有了直观的概念,但是对于生成的代理类到底是怎样的一个结构还是很模糊。通过上面的分析可以知道字节码的生成是通过ProxyGenerator.generateProxyClass()实现的,所以下面通过手动调用这个方法来为上面用到的Car类生成一个代理类的字节码,然后将其写入到磁盘存储为class文件,再通过反编译的方法来查看生成的代理类的结构:

public final class $Proxy1213 extends Proxy implements Car {
    private static Method m3;
    private static Method m1;
    private static Method m4;
    private static Method m0;
    private static Method m2;

    public $Proxy1213(InvocationHandler var1) throws  {
        super(var1);
    }

   //每个方法的调用都会被转发到与这个代理对象关联的InvocationHandler对象的invoke()方法中去,传给invoke()的Mehtod对象是通过静态初始化块中生成的。

    public final String getName() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final double getPrice() throws  {
        try {
            return ((Double)super.h.invoke(this, m4, (Object[])null)).doubleValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    //在静态初始化块中通过反射取得接口中方法的Method对象和其它父类中可能会被子类重写方法的Method对象(这里主要是Object中的几个方法)
    static {
        try {
            m3 = Class.forName("com.sankuai.lkl.proxy.Car").getMethod("getName", new Class[0]);
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m4 = Class.forName("com.sankuai.lkl.proxy.Car").getMethod("getPrice", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

看了上面这个反编译的Proxy类之后,结合前面的分析,对于整个动态代理的过程应该就很清楚了。

4.java动态代理在mybatis中的使用实例

最后一部分是关于动态代理的实际应用的,在这里提供一个mybatis中的例子。在开始使用mybatis这个框架的时候,我很不能理解的一点就是定义了一个访问数据库的接口进行了一些配置之后,然后通过sqlSession.getMapper()方法就可以获得这个接口的一个对象。大致是这样的:

public interface BranchDao {

    Branch getBranchById(int id);

}

 public void test(){
        BranchDao branchDao = sqlSession.getMapper(BranchDao.class);
        branchDao.getBranchById(12);
    }

其实这里的sqlSession.getMapper()方法就是为BranchDao接口生成了一个动态代理对象。下面来看下sqlSession.getMapper()方法的具体实现:

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

它直接调用的configuration的getMapper()方法,来看下这个方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

再看mapperRegistry.getMapper():

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   //缓存了mapperProxyFactory对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //这里面就是具体产生代理对象的方法
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

再看mapperProxyFactory.newInstance()方法:

  //还是调用Proxy.newProxyInstance()来创建的反射对象
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  //mapperProxy就是提供给dao接口的代理对象
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

可以看到底层是通过Proxy.newProxyInstance()创建的代理对象,然后返回的。而且因为这里使用了泛型,所以对Proxy.newProxyInstance()创建返回的Object对象进行了强制类型转换,返回的对象就是BranchDao接口类型了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值