深入浅出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 {@Autowiredprivate IEmployeeService service;//观察能否调用到另一个实现类EmployeeServiceImpl的save()方法@Autowiredprivate 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 {@Autowiredprivate IEmployeeService proxy;@Testpublic 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{@Autowiredprivate Object service;//真实对象的引用,类型是Objectpublic Object getService() {return service;}@Autowiredprivate MyTx tx;//事务处理对象的引用//负责集中处理动态代理类上的所有方法调用,让使用者自定义做什么事情,对原来方法做增强//proxy代理对象,method调用的方法,args方法调用参数@Overridepublic 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 {//注入代理执行处理器对象@Autowiredprivate TxInvocationHandler handler;@Testpublic void testSave() {//根据提供的条件动态生成代理类及创建其对象IEmployeeService service = (IEmployeeService)Proxy.newProxyInstance(handler.getService().getClass().getClassLoader(), handler.getService().getClass().getInterfaces(), handler);//调用代理类的方法service.save("HelloWorld!");}@Testpublic 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{@Autowiredprivate Object service;public Object getService() {return service;}@Autowiredprivate MyTx tx;@Overridepublic 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 {//注入代理执行处理器对象@Autowiredprivate TxInvocationHandler handler;@Testpublic 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 接口的实现类。

JAVA代理那些事儿相关推荐

  1. java 代理的三种实现方式

    Java 代理模式有如下几种实现方式: 1.静态代理. 2.JDK动态代理. 3.CGLIB动态代理. 示例,有一个打招呼的接口.分别有两个实现,说hello,和握手.代码如下. 接口: public ...

  2. 关于IBM Lotus的JAVA代理进入jar包的说明

    有2种方法,一般用第一种: 方法1: 在JAVA代理里引入JAR包: import javax.rmi.*; //(就是这样引入) public class JavaAgent extends Age ...

  3. 通过 Lotus Domino Java 代理消费 Web 服务

    Web 服务是一种允许两台或更多的计算机在网络中交互的系统设计.这种服务的主要优点是,它是在多台不同操作系统的计算机和应用服务器之间发送对象的标准解决方法.例如,我们的公司使用 Web 服务从一台运行 ...

  4. java编写代理服务器_如何编写Java代理

    java编写代理服务器 对于vmlens (轻量级Java竞争条件捕获器),我们使用Java代理来跟踪字段访问. 这是我们学习的实现此类代理的经验教训. 开始 使用"静态公共静态无效值pre ...

  5. java代理模式_Java代理

    java代理模式 本文是我们名为" 高级Java "的学院课程的一部分. 本课程旨在帮助您最有效地使用Java. 它讨论了高级主题,包括对象创建,并发,序列化,反射等. 它将指导您 ...

  6. Java代理初学者指南

    尽管Java初学者很快学会了键入public static void main来运行他们的应用程序,但是即使是经验丰富的开发人员也常常不知道JVM对Java流程的两个附加入口点的支持: premain ...

  7. 如何编写Java代理

    对于vmlens (轻量级Java竞争条件捕获器),我们使用Java代理来跟踪字段访问. 这是我们学习的实现此类代理的经验教训. 开始 使用" static public static vo ...

  8. java代理通俗简单解析

    1         代理 1.1            代理的概念和作用 代理的概念很好理解,就像黄牛代替票务公司给你提供票,经纪人代理艺人和别人谈合作.Java的代理是指实现类作为代理类的属性对象, ...

  9. Java代理设计模式(Proxy)的具体实现:静态代理和动态代理

    Java代理设计模式(Proxy)的具体实现:静态代理和动态代理 实现方式一:静态代理 静态代理方式的优点 静态代理方式的缺点 Java动态代理实现方式一:InvocationHandler Java ...

最新文章

  1. linux c getrlimit sysconf 系统限定 实例
  2. ubuntu16.04 打开chrome弹出“Enter password to unlock your login keyring”解决方法
  3. 外部表不是预期的格式怎么解决_1分钟拆解:如何将10多个工作表sheet,合并成一张?...
  4. python pyqt5实现自定义点击事件_Python 图形用户界面实战 : PyQt5 实现摘要算法计算...
  5. Word2013 设置默认缩进
  6. google gflags使用指南
  7. xgene:肿瘤相关基因 EGFR,,Her2,,TP53,,ALK
  8. 微信模板消息html,微信推送模板消息,偶发出现报错errcode
  9. 职业理想规划计算机专业,计算机专业的职业生涯规划书范文
  10. matlab2c使用c++实现matlab函数系列教程-imag函数
  11. GisToSWMM5简介
  12. rabbitmq集群安装与配置(故障恢复)
  13. 分析了网易云数十万歌单后写出2020年的最全歌单推荐
  14. 广域网接口规范(v.35)
  15. java functionex_Atitit. atiJavaExConverter4js  新的特性
  16. 小字辈(左子右兄加强版)
  17. att格式汇编指令_关于ATT汇编
  18. Qorvo 扩展 750V SiC FET 范围
  19. 使用pyspark 的udf进行tensorflow 模型的预测报错 _pickle.PicklingError: Could not serialize object:
  20. 基于freeSWITCH的sip协议利用WebRTC 实现实时视频聊天

热门文章

  1. html制作古诗带图画大全,古诗词的手抄报图画设计模板
  2. 分享 朋友圈 微博 QQ空间
  3. 使用 Ambire 参加 Arbitrum 奥德赛活动的指南
  4. android猜猜红桃A游戏源码
  5. Android 仿美团外卖底部顶起 lottie 封装
  6. python3进阶高级_python高级进阶
  7. 怎么提取视频音频?音视频分离的妙招
  8. Linux基础指令(覆盖80%)
  9. Python函数式编程之偏函数
  10. C++ GDI绘图思考题1 足球场的绘制