Java动态代理之JDK代理
为什么要使用动态代理呢?因为静态代理需要为每一个被代理类都建立一个代理类,这样就会产生很多代理类,并且每当被代理类增减接口时,我们需要同步维护代理类。这样既冗余又增加了维护成本。所以为了避免产生很多代理类,就出现了动态代理。
JDK动态代理要求,被代理类必须要实现接口。JDK动态代理包括两部分内容,一部分是基础内容,另一部分是代理工厂类。基础内容是接口和实现类。
基础内容代码:
package com.zaoren.proxy; /** * 接口 * @author thinkpad * */ public interface IUserDao { void save(); }
package com.zaoren.proxy; public class UserDao implements IUserDao { public void save() { System.out.println("------------保存user信息"); } }
代理工厂代码为:
package com.zaoren.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 动态代理工厂,即JDK代理工厂 * @author thinkpad * */ public class ProxyFactory { private IUserDao userDao; public ProxyFactory(IUserDao userDao) { this.userDao = userDao; } public Object getInstance() { return Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("--------->>>JDK代理调用目标前操作"); method.invoke(userDao, args); System.out.println("--------->>>JDK代理调用目标后操作"); return null; } } ); } }
其中,我们使用Proxy类创建代理实例时传入了三个参数,分别为被代理类的classLoader、被代理类所实现的所有接口、invoke方法的具体实现。
测试代码为:
package com.zaoren.proxy; public class MainClass { public static void main(String[] args) { IUserDao userDao = new UserDao(); IUserDao jdkProxy = (IUserDao)new ProxyFactory(userDao).getInstance(); jdkProxy.save(); } }
控制台输出结果为:
由控制台输出可见,结果符合我们的预期。
但是我重新审视代码时,发现我的代码并不能避免产生很多代理类,因为代理工厂类中的代理实例是一个具体的类型,那么如果我要代理其他类,这个代理工厂就不能用了,需要建立新的代理工厂才行。
要知道,JDK动态代理是使用反射的机制来实现的,而反射机制的特点之一就是传入的参数可以不是具体的类型。所以我把代理工厂类中的被代理类设置为Object类型,属性名改为target,其他地方不变,就可以了,控制台的输出结果和之前的一样,但是现在我们可以用这个代理工厂代理其他的类了,而不需要建立新的代理工厂。
修改后的代理工厂类代码为:
package com.zaoren.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 动态代理工厂,即JDK代理工厂 * @author thinkpad * */ public class ProxyFactory { private Object target; public ProxyFactory(Object userDao) { this.target = userDao; } public Object getInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("--------->>>JDK代理调用目标前操作"); method.invoke(target, args); System.out.println("--------->>>JDK代理调用目标后操作"); return null; } } ); } }
然后,我又思考,在程序运行过程中,整个JDK的动态代理机制具体是如何实现的呢?分析了一遍后,我发现整个机制总的来说就是反射,机制的的具体实现其实就是临时创建一个代理类,这个代理类实现了基础实现类的接口,创建代理类需要的材料是我们通过代码提供的。我们提供的最主要的材料是扩展后的方法,即在用Proxy类来创建代理实例时,我们提供了invoke方法的具体实现。至于代理类具体是怎样一步一步的创建的,和代理类是怎样被调用的,我就不知道了。
--------------------------------------
通过上面的代理工厂类,我们可以代理不同的类。但是上面的代码是有局限的,因为代理不同的类时,甚至是同一个被代理类的不同的方法时,代理类所扩展的内容都是相同的。如果我们想对不同的被代理类、不同的被代理类的方法扩展不同的内容,又该怎样实现呢?
问题的关键在于,我们要能够在invoke方法中区分不同的被代理类和被代理类的不同方法。那么应该怎样区分呢?我们可以通过反射的知识来获知当前的被代理的类是哪一个、被代理的方法是哪一个。
调用Method对象的toString方法,可以得到当前被代理的方法的详细信息,包括方法的访问修辞符、返回值、所属类、方法名、参数等。有了这些信息,就能够区分不同的被代理类和不同的被代理方法了。
为了实现,我有新建了一个接口和实现类,代码如下:
package com.zaoren.proxy; /** * 订单接口 * @author thinkpad * */ public interface IOrderDao { void save(); }
package com.zaoren.proxy; public class OrderDao implements IOrderDao { public void save() { System.out.println("---------保存订单信息"); } }
修改后的代理工厂类如下:
package com.zaoren.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 动态代理工厂,即JDK代理工厂 * @author thinkpad * */ public class ProxyFactory { private Object target; public ProxyFactory(Object userDao) { this.target = userDao; } public Object getInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("method = "+method.toString()); if(method.toString().contains("com.zaoren.proxy.IUserDao.save")){ System.out.println("--------->>>JDK代理调用目标前操作:记录日志"); method.invoke(target, args); System.out.println("--------->>>JDK代理调用目标后操作:记录日志2"); }else if(method.toString().contains("com.zaoren.proxy.IOrderDao.save")) { System.out.println("--------->>>JDK代理调用目标前操作:其他操作"); method.invoke(target, args); System.out.println("--------->>>JDK代理调用目标后操作:其他操作2"); } return null; } } ); } }
测试代码为:
package com.zaoren.proxy; public class MainClass { public static void main(String[] args) { IUserDao userDao = new UserDao(); IUserDao userDaoProxy = (IUserDao)new ProxyFactory(userDao).getInstance(); userDaoProxy.save(); IOrderDao orderDao = new OrderDao(); IOrderDao orderDaoProxy = (IOrderDao)new ProxyFactory(orderDao).getInstance(); orderDaoProxy.save(); } }
控制台输出结果为:
可见,我们己经实现了区分的目的。
备注:不知道大家有没有发现,控制台输出的method的值是接口的方法名,而不是实现类的方法名。这个应该是java的多态机制,实际上执行的是实现类的方法,但是概念上是接口的方法。