spring动态代理的理解(java)
java动态代理的理解
代理模式是设计模式的其中一种,也是Java相关框架中的重要应用。我也是初学者, 个人见解, 不喜勿喷, 简单的说就是需要进行功能增强的代理类和原本真实对象的被代理类会实现同样的接口,代理类的字节码文件是在jvm运行的时候动态生成该类(下面进行的介绍),但是代理类会多去继承一个Java中的Proxy 类,代理类负责为代理类(也就是生成真是对象的java类)预处理信息、增强信息、过滤信息最终把已经增强的转发给代理类。然而,回想之后,代理类又是谁生成的呢? 因此,还需要一个类去动态的生成代理类,这个类在编写的时候还需要用到一个Java中的invocationhandler类,这个类是用于增强被代理类中的方法,也就是谁继承了invocationhandler,谁就要去实现该接口对需要增强的类的方法(该接口中的invoke方法), 并且通过调用代理类生成器的生成代理类方法,就会去调用该实现类的invoke方法, 这是个人自己的理解,所说的动态生成器类就是在这个生成动态代理类的Java类中,不能有别的自己编写的Java类的引用(可以在该类中看是否有import导入自己编写的类),万事俱备, 只欠东风, 那就是搞一个测试, 去看下按照自己的理解是否可行。
1.首先先编写dao和service的代码
dao实现部分代码
public class EmployeeDAOImpl implements IEmployeeDAO { public void save(Employee emp) { System.out.println("保存员工"); } public void update(Employee emp) { System.out.println("修改员工"); } }
service实现部分代码
public class EmployeeServiceImpl implements IEmployeeService { private IEmployeeDAO dao; public void setDao(IEmployeeDAO dao) { this.dao = dao; } public void save(Employee emp) { dao.save(emp); System.out.println("保存成功"); } }
2.编写需要增强被代理对象的方式, 增强其功能, 比如事务增强
//模拟事务管理器: public class TransactionManager { public void begin() { System.out.println("开启事务"); } public void commit() { System.out.println("提交事务"); } public void rollback() { System.out.println("回滚事务"); } }
3.编写生成代理类的代理类生成器
先介绍下proxy类和invocationhandler类中用到的方法
proxy类:
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
Proxy类就是用来动态生成一个代理对象的类,我就叫它为动态代理类生成器吧。它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
这个方法的作用就是获取到增强版的被代理对象,也就是代理类,其接收三个参数,这三个参数所代表的含义:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException loader: 一个ClassLoader对象,就是定义了由哪个对象的classloader来对要生成的代理对象进行加载,一般使用的是被代理对象的类加载器 interfaces: 一个Interface对象的数组,就是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),
这样我就能调用这组接口中的方法了, 通俗的讲就是被代理类所实现的接口,而代理类也需要这个接口,否则它怎么知道你要用到哪个方法呢 h: 一个InvocationHandler对象,就是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上,也就是需要怎么实现对被代理对象的加强
invocationhandler类:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。比较抽象, 按照我的理解就是, 你用动态代理类生成器的时候,生成完代理对象了, 但是你仅仅是生成了这个代理对象, 你还需要告诉别人, 你是怎么加强代理对象的呢 。InvocationHandler这个接口的唯一的一个方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
这个方法的作用就是对动态代理类生成的增强版的被代理对象的方法,也就是代理类的种方法,如何加强的,其接收三个参数,这三个参数所代表的含义:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable proxy: 所代理的那个真实对象 method: 所要调用真实对象的某个方法的Method对象 args: 调用真实对象某个方法时接受的参数
重头戏来了, 在这里我是直接让TransactionManagerAdvice类去实现了InvocationHandler这个类,这个也就是上面提到的用于实现对代理类中方法的增强, 通过invoke方法去增强。TransactionManagerAdvice既是动态代理类生成器(用到的是proxy类中的newProxyInstance方法), 也是对被代理对象(也就是真实对象)增强的方式(用到的是invocationhandler接口中的invoke方法),其中这两个成员变量, 我用的是基于xml的DI中的setter方法注入,因此也提供了两个成员变量的setter方法。
动态代理类生成器如下:
//事务的增强操作 public class TransactionManagerAdvice implements InvocationHandler { private Object target;//真实对象(对谁做增强) private TransactionManager txManager;//事务管理器(模拟) public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public void setTarget(Object target) { this.target = target; } //创建一个代理对象 public <T> T getProxyObject() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), //类加载器,一般跟上真实对象的类加载器 target.getClass().getInterfaces(), //真实对象所实现的接口(JDK动态代理必须要求真实对象有接口) this);//如何做事务增强的对象,谁继承了invocationhandler,谁就是需要做增强的对象,并且真实对象调用方法会去调用 //该实现类的invoke方法, 自己的理解 } //如何为真实对象的方法做增强的具体操作 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().startsWith("get") || method.getName().startsWith("list")) { return method.invoke(target, args);//放行 } Object ret = proxy; txManager.begin(); try { //--------------------------------------------------------------- ret = method.invoke(target, args);//调用真实对象的方法 //--------------------------------------------------------------- txManager.commit(); } catch (Exception e) { e.printStackTrace(); txManager.rollback(); } return ret; } }
4.编写测试类
@SpringJUnitConfig
//用于加载配置文件的内容,不加参数默认去找: 测试类名称-context.xml public class App { //用于生成增强代理类的代理类生成器 @Autowired private TransactionManagerAdvice advice; //代理对象:com.sun.proxy.$Proxy19 @Test void testSave() throws Exception { //获取代理对象 IEmployeeService proxy = advice.getProxyObject(); proxy.save(new Employee()); } }
5.配置文件的内容如下(配置文件的名称: App-context.xml)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!--因为 <bean id="employeeDAO" class="cn.wolfcode.dao.impl.EmployeeDAOImpl" /> <bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager" /> <bean id="employeeService" class="cn.wolfcode.service.impl.EmployeeServiceImpl"> <property name="dao" ref="employeeDAO" /> </bean> <!-- 配置一个事务增强的类, 也就是代理类生成器 --> <bean id="transactionManagerAdvice" class="cn.wolfcode.tx.TransactionManagerAdvice"> <property name="target" ref="employeeService"/> <property name="txManager" ref="transactionManager"/> </bean> </beans>
5.运行保存(save)测试,结果如下:
最终大功告成, 测试通过,成功。
个人笔记: 理解延伸
看看动态代理在底层是如何进行操作的
先从动态代理生成的部分开始
第一步: 通过proxy类中的静态方法newproxyinstance方法,根据传入的参数,动态生成出代理类的Java文件,只不过他不会出现在编译时期,而是直接到达运行时期,利用类加载器生成该份字节码文件而已
第二步: 就是通过代理对象的invoke方法对所用的方法进行增强,因为在前面的TransactionManagerAdvice类中已经定义了如何增强功能, 所以在这个动态生成的代理类中的invoke方法就是多态特性调用自己编写的invoke方法
第三步: 测试方法
1.首先先借助一个类:这个类的作用是生成代理类的字节码文件, 并且把这份字节码文件存放在被代理类的包下面(也就是代理类的字节码文件), 之前我也是很好奇,为什么代理类还要这样得到, 这也就是前面也没提到的, 动态代理是不会生成代理类的Java文件的, 因为动态代理的过程中,我们并没有实际看到代理类的代码实现,而且动态代理中被代理对象和代理对象是通过InvocationHandler接口实现类和proxy类中的newproxyinstance方法来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。生成动态代理类的代码如下
import java.io.FileOutputStream; import cn.wolfcode.service.impl.EmployeeServiceImpl; import sun.misc.ProxyGenerator; public class DynamicProxyClassGenerator { public static void main(String[] args) throws Exception { generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy"); } //生成代理类的字节码文件-->Java反编译工具-->Java文件 public static <T>void generateClassFile(Class<T> targetClass, String proxyName) throws Exception { //根据类信息和提供的代理类名称,生成字节码 byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces()); String path = targetClass.getResource(".").getPath(); System.out.println(path); FileOutputStream out = null; //保留到硬盘中 out = new FileOutputStream(path + proxyName + ".class"); out.write(classFile); out.close(); } }
2.生成字节码文件之后,找到这份字节码文件,通过jd-gui反编译工具生成 .java文件, 下面的就是动态代理的代理类的代码实现
public final class EmployeeServiceProxy extends Proxy implements IEmployeeService { private static final long serialVersionUID = 1L; private static Method method_equals; private static Method method_toString; private static Method method_hashCode; private static Method method_update; private static Method method_save; public EmployeeServiceProxy(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } static { try { method_equals = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") }); method_toString = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); method_hashCode = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); method_update = Class.forName("cn.wolfcode.service.IEmployeeService").getMethod("update",new Class[] { Class.forName("cn.wolfcode.domain.Employee") }); method_save = Class.forName("cn.wolfcode.service.IEmployeeService").getMethod("save",new Class[] { Class.forName("cn.wolfcode.domain.Employee") }); } catch (Exception e) { } } public final boolean equals(Object paramObject) { try { return ((Boolean) this.h.invoke(this, method_equals, new Object[] { paramObject })).booleanValue(); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() { try { return (String) this.h.invoke(this, method_toString, null); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() { try { return ((Integer) this.h.invoke(this, method_hashCode, null)).intValue(); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void save(Employee paramEmployee) { try { this.h.invoke(this, method_save, new Object[] { paramEmployee });//多态调用,用的是TransactionManagerAdvice中的invoke方法 return; } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void update(Employee paramEmployee) { try { this.h.invoke(this, method_update, new Object[] { paramEmployee });//多态调用 return; } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } }
}
上面标红的就是在上面代理类生成器中的invoke方法
3.最终通过测试方法的代理类生成器获取代理类对象,然后调用save方法进行测试,成功
总结
我们可以对InvocationHandler的实现类看做一个中介类,理解成它就是代理类对象的中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
部分参考:https://www.cnblogs.com/gonjan-blog/p/6685611.html