JAVA设计模式之代理模式
设计模式
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
六大原则:
单一职责原则:
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分
里氏替换原则(Liskov Substitution Principle):
子类可以扩展父类的功能,但不能改变父类原有的功能。父类能出现的地方都可以用子类来代替,而且换成子类也不会出现任何错误或异常,而使用者也无需知道是父类还是子类,但反过来则不成立。总之,就是抽象。
依赖倒转原则(Dependence Inversion Principle):
面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互
接口隔离原则(Interface Segregation Principle):
每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
迪米特法则(最少知道原则)(Demeter Principle):
一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
合成复用原则(Composite Reuse Principle):
尽量首先使用合成/聚合的方式,而不是使用继承。
代理模式
代理模式就是设计模式中的一种,它的作用就是在不直接操作目标对象的前提下,创建一个代理对象,对目标对象进行操作和业务逻辑、功能上的增强。代理模式大体上来说分为两种:静态代理和动态代理。
代理模式的主要优点有:
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
代理对象可以扩展目标对象的功能;
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
其主要缺点是:
在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
增加了系统的复杂度;
代理模式包含如下角色:
Subject: 抽象主题角色
Proxy: 代理主题角色
RealSubject: 真实主题角色
静态代理:
静态代理:由程序员创建或特定工具自动生成源代码,再对其进行编译。在程序运行之前,代理类.class文件就已经被创建,代理类和委托类的关系在运行前就确定。
个人理解:就是由程序员或者某个工具代码手动针对目标对象进行代理。下面用个简单例子:
接口:
public interface Person { void add(); void delete(); void find(); void update(); }
实现接口的目标对象:
public class Student implements Person { public void add() { System.out.println("添加"); } public void delete() { System.out.println("删除"); } public void find() { System.out.println("查找"); } public void update() { System.out.println("更新"); } }
实现接口的代理对象:
public class StudentStaticProxy implements Person { //用于接收目标对象 private Person person; //构造方法传入目标对象 public StudentStaticProxy(Person person) { this.person = person; } public void add() { System.out.println("方法加强前"); person.add(); System.out.println("方法加强后"); } public void delete() { System.out.println("方法加强前"); person.delete(); System.out.println("方法加强后"); } public void find() { System.out.println("方法加强前"); person.find(); System.out.println("方法加强后"); } public void update() { System.out.println("方法加强前"); person.update(); System.out.println("方法加强后"); } }
测试:
public class Main { public static void main(String[] args) { //创建静态代理对象 Person person = new StudentStaticProxy(new Student()); //调用方法 person.add(); System.out.println("**********************"); person.update(); System.out.println("**********************"); person.find(); System.out.println("**********************"); person.delete(); } }
结论:
1、代理对象和真实对象要实现相同接口;
2、无需操作实际对象,在代理对象处就可对相关方法进行增强,或者修改;
静态代理的缺点:
1、需要提前确定目标对象和需要被增强的方法。
2、在上面的例子可以看出,因为代理对象增强的是具体的某个对象的某些方法,所以如果要新增一个代理对象,或者在原来的实际对象上新增一个方法,则需要把整个流程都进行相应的改动,这就体现出项目的耦合性太高,维护起来特别麻烦。
3、因为代理对象和实际对象都实现同一个接口,那么代理对象和实际对象就必然有着大量的重复代码。随着需要被代理的方法或者对象的增多,整个项目会越来越臃肿,降低项目的运行速度和性能。
动态代理:
和静态代理相比,动态代理不需要提前知道要代理什么,而且只需调用几个关键方法就可以代理接口下的所有子类(即在运行时通过反射机制进行动态代理)。
动态代理又可以分为两类:基于JDK的动态代理和基于CGLIB的动态代理。
JDK动态代理示例:
基于JDK的动态代理有两个关键点:1、Proxy类;2、InvocationHandler接口。
InvocationHandler接口的作用类似一个处理器,用关键方法invoke(),把接收到的对象通过反射来获取到所有对象的方法,然后进行转换。
Proxy类的作用是通过关键方法newProxyInstance(),来获取代理对象。
补充:newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h),三个参数:目标对象的类加载器,目标对象直接实现的接口对象,处理器对象。
接口:
public interface Person { void add(); void delete(); void find(); void update(); }
实现接口的目标对象:
public class Student implements Person { public void add() { System.out.println("添加"); } public void delete() { System.out.println("删除"); } public void find() { System.out.println("查找"); } public void update() { System.out.println("更新"); } }
实现 InvocationHandler 接口的处理器对象:
public class MyInvocationHandler implements InvocationHandler { //接口用来接收目标对象 private Person person; //构造方法,用来接收目标对象 public MyInvocationHandler(Person person) { this.person = person; } //代理对象的关键方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法加强前"); //利用反射对目标对象的所有方法进行调用 Object personProxy = method.invoke(person, args); System.out.println("方法加强后"); //返回代理对象 return personProxy; } }
测试:
public class DynamicMain { public static void main(String[] args) { //确定目标对象 Student stu = new Student(); //创建代处理器对象 MyInvocationHandler myInvocationHandler = new MyInvocationHandler(stu); //获取目标对象的类加载器 ClassLoader classLoader = stu.getClass().getClassLoader(); //获取目标对象的接口对象 Class<?>[] interfaces = stu.getClass().getInterfaces(); //获取代理对象 Person proxy = (Person) Proxy.newProxyInstance(classLoader, interfaces,myInvocationHandler); //验证方法是否被加强 proxy.add(); proxy.find(); proxy.delete(); proxy.update(); } }
CGLIB动态代理示例:
CGLIB动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
补充:因为CGLIB不是JDK自带,所以需要额外引入CGLIB的包或者依赖。
示例代码如下:
接口:
public interface Person { void add(); void delete(); void find(); void update(); }
目标对象:
public class Student implements Person { public void add() { System.out.println("添加"); } public void delete() { System.out.println("删除"); } public void find() { System.out.println("查找"); } public void update() { System.out.println("更新"); } }
调节器对象:
public class MyMethodInterceptor implements MethodInterceptor { //用来接收目标对象 private Person person; //接收目标对象 public MyMethodInterceptor(Person person) { this.person = person; } //创建返回CGLIB代理对象的方法 public Object createCGProxy() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(person.getClass()); enhancer.setCallback(this); Object CGProxy = enhancer.create(); return CGProxy; } //主要方法 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("方法加强前"); Object proxy = method.invoke(person, objects); System.out.println("方法加强后"); return proxy; } }
测试:
public class CGLIBMain { public static void main(String[] args) { //确认目标对象 Student student = new Student(); //获取调节器对象 MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor(student); //获取代理对象 Person proxy = (Person) myMethodInterceptor.createCGProxy(); //验证是否加强 proxy.add(); System.out.println("------分割线------"); proxy.delete(); System.out.println("------分割线------"); proxy.update(); System.out.println("------分割线------"); proxy.find(); } }
CGLIB依赖:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency>
对比:
JDK动态代理是JDK本身自带的,局限性是目标对象必须实现了接口。而Cglib动态代理是引入外来封装好的包来实现动态代理,目标对象可以不用实现接口。所以当目标对象实现了接口的情况下默认用JDK动态代理,但是CGLIB也可以,但是如果目标对象没有实现接口则只能用CGLIB动态代理。
JDK动态代理主要是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,而CGLIB动态代理是利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。