JAVA代理那些事儿

mac2026-04-23  7

深入浅出JAVA代理

1.先看一个房屋租赁例子

问题:此时若有人来整房东,派很多人来找房东假租房,这会导致房东一天到晚都忙且没收获。带来这个问题就是:重复,且责任不分离,其实房东最关系的就是签合同和收房租。

静态代理

1.代理模式

客户端直接使用的都是代理对象,不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介的作用。 1.1、代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系; 1.2、代理模式的职责:把不是真实对象该做的事情从真实对象上撇开——职责分离。

2.定义流程

在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。

3.代码实现

代理接口

package cn.dale.spring.static_proxy.service; public interface IEmployeeService { void save(String name); void update(String name); }

真实类或委托类

package cn.dale.spring.static_proxy.service.impl; import cn.dale.spring.static_proxy.service.IEmployeeService; public class EmployeeServiceImpl implements IEmployeeService { public void save(String name) { System.out.println("保存" + name); } public void update(String name) { System.out.println("更新" + name); } }

代理类

package cn.dale.spring.static_proxy.service.impl; import org.springframework.beans.factory.annotation.Autowired; import cn.dale.spring.static_proxy.service.IEmployeeService; import cn.dale.spring.static_proxy.tx.MyTx; public class EmployeeServiceProxy implements IEmployeeService { @Autowired private IEmployeeService service;//观察能否调用到另一个实现类EmployeeServiceImpl的save()方法 @Autowired private MyTx tx; public void setService(IEmployeeService service) { this.service = service; } // public void setTx(MyTx tx) { // this.tx = tx; // } public void save(String name) { tx.begin(); try { service.save(name); tx.commit(); } catch (Exception e) { tx.rollback(); } finally { System.out.println("释放资源"); } } public void update(String name) { tx.begin(); try { service.update(name); tx.commit(); } catch (Exception e) { tx.rollback(); } finally { System.out.println("释放资源"); } } }

事务处理类

package cn.dale.spring.static_proxy.tx; import org.springframework.stereotype.Component; @Component(value="tx") public class MyTx { public void begin(){ System.out.println("开启事务"); } public void commit(){ System.out.println("提交事务"); } public void rollback(){ System.out.println("回滚事务"); } }

配置文件 测试用例:

package cn.dale.spring.static_proxy; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import cn.dale.spring.static_proxy.service.IEmployeeService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class static_proxyTest { @Autowired private IEmployeeService proxy; @Test public void testStatic_Proxy() { proxy.save("HelloWorld!"); proxy.update("你好世界!"); } }

控制台输出结果:

4.静态代理优缺点

优点: 业务类只需要关注业务逻辑本身,保证了业务类的重用性。 把真实对象隐藏起来了,保护真实对象。 缺点: 代理对象的某个接口只服务于某一种类型的对象,也就是为每个真实类创建一个代理类,比如项目还有其他 service 呢。 若需要代理的方法很多,则要为每一种方法都进行代理处理。 若接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法。

动态代理

1.问题

就是静态代理的缺点:需要为每个真实类创建一个代理类,随着程序规模变大导致代理类急剧膨胀。可以通过动态代理解决。

2.字节码加载

如何动态的创建一份字节码? 由于 JVM 通过字节码的二进制信息加载类的,如果我们在运行期系统中,遵循 Java 编译系统组织 .class 文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。如此,就完成了在代码中动态创建一个类的能力了。

3.动态代理动态生成字节码

动态代理类是在程序运行期间由 JVM 通过反射等机制动态的生成的,所以不存在代理类的字节码文件,动态生成字节码对象,代理对象和真实对象的关系是在程序运行时期才确定的。

4.实现动态代理方式

4.1、针对有接口:使用 JDK 动态代理; 4.2、针对无接口:使用 CGLIB 或 Javassist 组件。

接下来详细讲解JDK动态代理

1、前提

委托类(真实类),必须实现接口。

2、JDK 动态代理 API

2.1、java.lang.reflect.Proxy 类

Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

主要方法:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler hanlder) 方法职责:为指定类加载器、一组接口及调用处理器生成动态代理类实例 参数: loader :类加载器,一般传递真实对象的类加载器 interfaces:代理类需要实现的接口 handler:代理执行处理器,说人话就是生成代理对象要帮你做什么 返回:创建的代理对象

2.2、java.lang.reflect.InvocationHandler 接口

主要方法:public Object invoke(Object proxy, Method method, Object[] args) 方法职责:负责集中处理动态代理类上的所有方法调用,让使用者自定义做什么事情,对原来方法增强。 参数: proxy :生成的代理对象 method:当前调用的真实方法对象 args :当前调用方法的实参 返回:真实方法的返回结果

3、操作步骤

3.1、定义封装事务操作的一个模拟类。 3.2、实现 InvocationHandler 接口,实现 invoke 方法,实现增强操作。 3.3、在 Spring 配置文件中配置 InvocationHandler 实现类、事务操作模拟类、真实对象,让其帮我们创建对象组装依赖。 3.4、在单元测试类中注入 InvocationHandler 的 bean,在测试方法中手动使用 Proxy 创建代理对象,调用代理对象的方法

4、代码实现

事务操作类

package cn.dale.spring.jdk_proxy.tx; import org.springframework.stereotype.Component; @Component public class MyTx { public void begin(){ System.out.println("开启事务"); } public void commit(){ System.out.println("提交事务"); } public void rollback(){ System.out.println("回滚事务"); } }

真实类或委托类,就是房东

package cn.dale.spring.jdk_proxy.service.impl; import org.springframework.stereotype.Service; import cn.dale.spring.jdk_proxy.service.IEmployeeService; @Service(value="service") public class EmployeeServiceImpl implements IEmployeeService { public void save(String name) { System.out.println(1/0); System.out.println("保存" + name); } public void update(String name) { System.out.println("更新" + name); } }

代理执行处理器类

package cn.dale.spring.jdk_proxy.handler; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import cn.dale.spring.jdk_proxy.tx.MyTx; @Component public class TxInvocationHandler implements InvocationHandler{ @Autowired private Object service;//真实对象的引用,类型是Object public Object getService() { return service; } @Autowired private MyTx tx;//事务处理对象的引用 //负责集中处理动态代理类上的所有方法调用,让使用者自定义做什么事情,对原来方法做增强 //proxy代理对象,method调用的方法,args方法调用参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = null; tx.begin(); try { //调用真实对象的方法 ret = method.invoke(service, args); tx.commit(); } catch (Throwable e) { tx.rollback(); } return ret; } }

配置文件,由于事务处理器对象和事务处理器代理类都用注解配置了,故配置文件的相关bean给注释了. 测试用例:

package cn.dale.spring.jdk_proxy; import java.lang.reflect.Proxy; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import cn.dale.spring.jdk_proxy.handler.TxInvocationHandler; import cn.dale.spring.jdk_proxy.service.IEmployeeService; /** * @author dale */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-jdk_proxy.xml") public class jdk_proxyTest { //注入代理执行处理器对象 @Autowired private TxInvocationHandler handler; @Test public void testSave() { //根据提供的条件动态生成代理类及创建其对象 IEmployeeService service = (IEmployeeService)Proxy.newProxyInstance (handler.getService().getClass().getClassLoader(), handler.getService().getClass().getInterfaces(), handler); //调用代理类的方法 service.save("HelloWorld!"); } @Test public void testUpdate() { IEmployeeService service = (IEmployeeService)Proxy.newProxyInstance (handler.getService().getClass().getClassLoader(), handler.getService().getClass().getInterfaces(), handler); service.update("HelloWorld!"); } }

控制台输出结果:

5、JDK动态代理原理

1、生成动态代理的字节码 执行main方法生成字节码

import java.io.FileOutputStream; import sun.misc.ProxyGenerator; @SuppressWarnings("restriction") public class DynamicProxyClassGenerator { public static void main(String[] args) throws Exception { generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy2"); } public static void generateClassFile(Class<?> 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、 通过反编译工具查看字节码文件

观察:save 方法,发现底层其实依然在执行 InvocationHandler 中的 invoke 方法。

public final void save(String paramString) throws { try { //h是增强处理器 this.h.invoke(this, m4, new Object[] { paramString }); return; } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); }

3、调用流程 4、优缺点

优点:对比静态代理,发现不需手动地提供那么多代理类。 缺点: 1. 真实对象必需实现接口(JDK 动态代理特有)。 2. 动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判 断。 3. 对多个真实对象进行代理的话,若使用 Spring 的话配置太多了。 4. 要手动创建代理对象,用起来麻烦。

接下来详细讲解CGLIB动态代理和原理

1、JDK 动态代理的问题

JDK 动态代理要求真实类必须实现接口。而 CGLIB 与 JDK 动态代理不同是,真实类不用实现接口,生成代理类的代码不一样且代理类会继承真实类。

2、CGLIB 动态代理 API

org.springframework.cglib.proxy.Enhancer,类似 JDK 中 Proxy,用来生成代理类创建代理对象的。 org.springframework.cglib.proxy.InvocationHandler,类似 JDK 中 InvocationHandler,让使用者自定义做什么事情,对原来方法增强。

3、操作步骤

3.1、修改 TransactionHandler 实现 org.springframework.cglib.proxy.InvocationHandler 接口,其他不变。 3.2、修改单元测试类中的测试方法,改用 Enhancer 来生成代理类创建代理对象的。

4、代码实现

代理执行处理器类

package cn.dale.spring.cglib_proxy.handler; import java.lang.reflect.Method; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import cn.dale.spring.cglib_proxy.tx.MyTx; @Component(value="txx") public class TxInvocationHandler implements org.springframework.cglib.proxy.InvocationHandler{ @Autowired private Object service; public Object getService() { return service; } @Autowired private MyTx tx; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = null; tx.begin(); try { ret = method.invoke(service, args); tx.commit(); } catch (Exception e) { tx.rollback(); } return ret; } }

其他的组件和JDK动态代理一样! 测试用例:

package cn.dale.spring.cglib_proxy; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cglib.proxy.Enhancer; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import cn.dale.spring.cglib_proxy.handler.TxInvocationHandler; import cn.dale.spring.cglib_proxy.service.impl.EmployeeServiceImpl; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-cglib_proxy.xml") public class Cglib_ProxyTest { //注入代理执行处理器对象 @Autowired private TxInvocationHandler handler; @Test public void testCglib_Proxy() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(handler.getService().getClass().getClass());//设置代理类的父类对象为真实对象 enhancer.setCallback(handler);//设置真实对象方法增强 EmployeeServiceImpl proxy = (EmployeeServiceImpl)enhancer.create();//根据提供的条件生成动态代理类及其对象 proxy.save("HelloWorld!");//调用代理对象方法 proxy.update("HelloWorld!"); } }

5、调用流程

动态代理总结

1、动态代理图示

2、JDK动态代理总结

2.1、Java 动态代理是使用 java.lang.reflect 包中的 Proxy 类与 InvocationHandler 接口这两个来完成的。 2.2、要使用 JDK 动态代理,真实类必须实现接口。** 2.3、JDK 动态代理将会拦截所有 pubic 的方法(因为只能调用接口中定义的方法),这样即使在接口中增加了新的方法,不用修改代码也会被拦截。 2.4、动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断。

3、CGLIB 动态代理总结

3.1、CGLIB 可以生成真实类的子类,并重写父类非 final 修饰符的方法。 3.2、要求类不能是 final 的,要拦截的方法要是非 final、非 static、非 private 的。 3.3、动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断。

4、关于性能

JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承委托类的。 从性能上考虑:Javassit > CGLIB > JDK。 MyBatis 延迟加载对象,采用的是 Javassit 的方式。 对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范。 若委托类实现了接口,优先选用 JDK 动态代理。 若委托类没有实现任何接口,使用 Javassit 和 CGLIB 动态代理。 动态代理问题:对多个 service 对象增强配置太多,还有要手动创建代理对象,在使用时不是面向接口,还要编写 InvocationHandler 接口的实现类。

最新回复(0)