设计模式~代理模式


学习代理模式内容: ★ 静态代理、 ★ 动态代理(JDK动态代理、CGLIB动态代理)、 ★ 拦截器的原理和日志记录 ★ 代理总结



一、职责分离的例子---房屋租赁 【设计模式~代理模式】1、重复
2、职责不分离
●【陪着看房、陪着谈价格、交钥匙】----不应该交个房东来重复做,不是他关心的重点,作为房东他只需要关心【签合同、收房租
----解决:把这部分重复非业务重点的代码重构抽离给第三者----中介

● 抽离之后,中介也会重复了呀,那怎么办呢? ----没事,这是人家的责任,人家就是靠这个赚钱的。

? 客户端Client----- 第三方(目的:增强Service的功能)-----服务端Service




二、代理模式 [设计模式] 中介作用 ? 1、代理模式:客户端直接使用的都是代理对象,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介作用

(1)代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系;
(2)代理模式的职责:把不是真实对象该做的事情从真实对象上撇开——职责清晰;




三、静态代理 1、概念/原理:
在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。

2、静态代理的实现过程:
■ 将功能封装成一个接口,代理类和真实类/委托类 都需要实现该接口:

  • 真实类/委托类 需要实现该接口,很好理解,才具有特定的功能。
  • 代理类 需要实现该接口,是因为这样子才知道需要为哪些功能做代理、做增强。
设计模式~代理模式
文章图片




3、静态代理的优缺点:
● 优点:职责分离、安全
1,业务类只需要关注业务逻辑本身,保证了业务类的重用性。
2,把真实对象隐藏起来了,保护真实对象。

● 缺点:代码臃肿(一个真实对象需要对应一个代理对象)、不符合开闭原则(不方便扩展和维护)。
----一个代理类只能服务某一个业务接口
1,代理对象的某个接口只服务于某一种类型的对象,也就是说每一个真实对象都得创建一个代理对象。
2,如果需要代理的方法很多,则要为每一种方法都进行代理处理。
3,如果接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法



4、总结静态代理:
1、静态代理需要实现某个接口(才知道要代理、增强什么功能)
2、静态代理要包含真实对象(真实对象是静态代理类的对象属性)----内部bean,不暴露给外界
3、测试,通过接口对象进行测试,直接调用的是静态代理对象(间接调用真实对象)
【动态代理在测试时,是通过动态代理类,先获取到代理对象,直接调用的是动态代理对象(间接调用真实对象)】
  • 获取到代理对象:jdk提供的Proxy的方法newProxyInstance

● 详细的代码:
/* 静态代理类【需要实现接口,才知道需要为哪些功能做代理、做增强】*/ public class EmployeeServiceProxy implements IEmployeService{ @Setter private IEmployeService target; //真实对象/委托对象 @Setter private TransactionManager txManager; //事务管理器 @Override public void save(Employee e) { txManager.open(); try { target.save(e); txManager.commit(); } catch (Exception e2) { txManager.rollback(); e2.printStackTrace(); } } @Override public void update(Employee e) { txManager.open(); try { target.update(e); txManager.commit(); } catch (Exception e2) { txManager.rollback(); e2.printStackTrace(); } } }/* 测试类 (先获取代理对象)*/ @SpringJUnitConfig public class App { @Autowired private IEmployeService service; @Test void testSave() throws Exception { Employee e = new Employee(); e.setName("shangke"); e.setAge(28); service.save(e); //调用接口对象【根据bean配置,实际调用的是静态代理对象】 //System.out.println(service); //接口对象的真实类型【根据bean配置,实际调用的是静态代理对象】 //System.out.println(service.getClass()); } }

● 静态代理bean对象的配置:
设计模式~代理模式
文章图片





四、动态代理 1、学习动态动态代理之前的准备工作:字节码的动态加载
(1) 先了解一下java的编译运行原理【java加载字节码原理】:
  • 编译:将源文件 .java 文件,通过编译器(javac 命令) 编译成 字节码文件 .class 文件。【编译得到字节码文件
  • 运行:通过类加载器(以二进制流形式)把字节码加载进JVM,通过java解析器(java 命令) 进行运行程序。【jvm解析字节码文件

(2) 如何动态创建一份字节码?(实现了在代码中动态创建一个类的能力):
  • 通过java的编译和运行原理,可以看到:在运行时期,是jvm通过字节码的二进制信息来加载类的。
? 所以,当我们在运行时期,通过java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。



2、静态代理和动态代理的(原理)区别:
■ 静态代理:(经历了编译和运行) 在程序运行前就已经存在代理类的字节码文件(因为通过了编译阶段),代理对象和真实对象的关系在运行前就确定了(因为通过了编译阶段)。 ■ 动态代理:(只经历了运行,咱通过某种手段得到的字节码【遵循字节码格式和结构】) 动态代理类是在程序运行期间由jvm通过反射等机制动态生成的,所以不存在代理类的字节码文件(因为没有经历编译阶段),代理对象和真实对象的关系是在程序运行期间才确定的
□ 两个原理相同点: 客户端直接使用的都是代理对象,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介作用


3、动态代理分类:JDK动态代理、CGLIB动态代理
(1)JDK动态代理: ① 使用JDK的 Proxy的newProxyInstance 创建动态代理对象
//动态代理---获取代理对象 @SuppressWarnings("unchecked") public T getProxyObject() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真实对象的类加载器 target.getClass().getInterfaces(), //真实对象实现的接口 this); //如何做事务增强的对象【增强器】 } //动态代理---获取代理对象 @SuppressWarnings("unchecked") public T getProxyObject() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真实对象的类加载器 target.getClass().getInterfaces(), //真实对象实现的接口 this); //如何做事务增强的对象【增强器】 }

● 详细的代码:
/* 动态代理:事务的增强操作 */ public class TransactionMangagerAdvice implements InvocationHandler{ @Setter private Object target; //真实对象/委托对象 @Setter private TransactionManager txManager; //事务增强器 //动态代理---获取代理对象 @SuppressWarnings("unchecked") public T getProxyObject() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真实对象的类加载器 target.getClass().getInterfaces(), //真实对象实现的接口 this); //如何做事务增强的对象【增强器】 } //如何为真实对象的方法做增强具体操作 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = null; txManager.open(); try { //====================================================== ret = method.invoke(target, args); //调用真实对象的方法 //====================================================== txManager.commit(); } catch (Exception e) { e.printStackTrace(); txManager.rollback(); } return ret; } }/* 测试类 (先获取代理对象)*/ @SpringJUnitConfig public class App { @Autowired private TransactionMangagerAdvice advice; @Test void testSave() throws Exception { Employee e = new Employee(); e.setName("shang"); e.setAge(10); e.setId(2L); //获取代理对象 IEmployeService proxy = advice.getProxyObject(); //调用代理对象的保存操作 proxy.save(e); //System.out.println(proxy); //TransactionMangagerAdvice对象的真实类型是代理对象 //System.out.println(proxy.getClass()); //对象的真实类型 } }




?(2) 动态代理原理:
  • 原理和静态代理差不多【客户端直接使用的都是代理对象,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介作用。通过代理对象间接的调用真实对象的方法】
  • 只不过,动态代理的代理类,不是由我们所创建,是我们生成字节码对应格式和结构的二进制数据加载进虚拟机,动态生成的
● 详细的代码[通过 DynamicProxyClassGenerator 生成动态代理的字节码,再通过反编译工具查看。]:
public class DynamicProxyClassGenerator { public static void main(String[] args) throws Exception { generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy"); } 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(); } }




(3) CGLIB动态代理:
  • 第三方,需要拷贝jar包【spring-frame框架已经集成了cglib动态代理】
  • 原理:继承
设计模式~代理模式
文章图片

● 详细的代码:
/* cglib动态代理:事务的增强操作 [和jdk的区别在创建代理对象方式上] */ //动态代理---获取代理对象 @SuppressWarnings("unchecked") public T getProxyObject() { Enhancer enhancer = new Enhancer(); return (T)enhancer.create(target.getClass(), this); //创建代理对象 //enhancer.setSuperclass(target.getClass()); //将继承哪一个类,去做增强 //enhancer.setCallback(this); //设置增强对象【增强器】 //return (T)enhancer.create(); //创建代理对象 }




(4)JDK动态代理和CGLIB动态代理的区别: ■ JDK动态代理:要求 真实对象 必须要实现接口。 ■ CGLIB动态代理:可以针对没有接口. 设计模式~代理模式
文章图片






? 五、代理的总结: 1、代理原理图:
设计模式~代理模式
文章图片




? 2、代理的目标/作用:为了给目标对象(真实对象)的方法做功能的增强



? 3、动态代理原理:
  • 原理和静态代理差不多【客户端直接使用的都是代理对象,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介作用。通过代理对象间接的调用真实对象的方法】
  • 只不过,动态代理的代理类,不是由我们所创建,是我们生成字节码对应格式和结构的二进制数据加载进虚拟机,动态生成的



4、JDK 动态代理 和 CGLIB 动态代理的总结:有接口-使用jdk,没有接口-使用cglib
(1) JDK 动态代理: ① JAVA 动态代理是使用 java.lang.reflect 包中的 Proxy 类与 InvocationHandler 接口这两个来完成的。
② 要使用 JDK 动态代理,委托类(真实类)必须要定义接口
JDK 动态代理将会拦截所有 pubic 的方法(因为只能调用接口中定义的方法),这样即使在接口中增加 了新的方法,不用修改代码也会被拦截。
④ 动态代理的最小单位是类(所有类中的方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法 中对要执行的方法名进行判断 [判断内容可以放到配置文件,方便后续修改和维护~]

(2) CGLIB 动态代理: ① CGLIB 可以生成委托类的子类,并重写父类非 final 修饰符的方法
② 要求类不能是 final 的,要拦截的方法要是非 final、非 static、非 private 的。
③ 动态代理的最小单位是类(所有类中的方法都会被处理);



5、性能和选择 [有接口-使用jdk,没有接口-使用cglib + 性能要求有要求-Javassit]
  • JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承委托类的。
  • 从性能上考虑:Javassit > CGLIB > JDK Struts2 的拦截器和 Hibernate 延迟加载对象,采用的是 Javassit 的方式.
  • 对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范
  • 若委托对象实现了干接口,优先选用 JDK 动态代理。 若委托对象没有实现任何接口,使用 Javassit 和 CGLIB 动态代



? 6、动态代理的应用:过滤器、拦截器、日志记录
1、过滤器Filter 2、拦截器Interceptor
  • 过滤器和拦截器差不多,只是过滤器是针对与web领域的概念,只能针对与请求和响应做增强,离不开servlet-api.jar; 而拦截器是对于整个java领域的概念,不仅可以应用到web层,还可以应用到service层。
3、日志记录log

    推荐阅读