本文共 9809 字,大约阅读时间需要 32 分钟。
视频教程:
什么是设计模式?
- 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
- 1995年,GoF(Gang of Four,四人组)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了23种设计模式,人称 【GoF设计模式】
设计模式分类 具体模式 创建型模式:它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。 ⌛单例模式、⌛工厂模式、⌛抽象工厂模式、⌛建造者模式、⌛原型模式 结构型模式:结构型模式描述如何将类或对象按某种布局组成更大的结构。 ⌛适配器模式、⌛桥接模式、⌛装饰模式、⌛代理模式、组合模式、外观模式、享元模式、 行为型模式:这些设计模式特别关注对象之间的通信。 模板方法模、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理:
代理(Proxy)模式分为三种角色:
代理类在编译期就生成
通过 火车站卖票 的例子说明静态代理:
类图如下:
卖票方法(接口):
//卖票接口:抽象主题(Subject)类public interface SellTickets { void sell();}
火车站:
public class TrainStation implements SellTickets { @Override public void sell() { System.out.println("火车站卖票"); }}
代售点:
public class ProxyPoint implements SellTickets { //直接在此实例化火车站对象 private TrainStation station = new TrainStation(); @Override public void sell() { System.out.println("代理点收取一些服务费用..."); station.sell(); }}
顾客:
public class Client { public static void main(String[] args) { ProxyPoint proxyPoint = new ProxyPoint(); proxyPoint.sell(); }}
看起来是去代售点买票,实则仍是买火车站的票
代理点收取一些服务费用...火车站卖票
此时静态代理的作用:
静态代理是在代理类的一开始就声明了RealSubject对象:
动态代理则不用事先实例化对象,在运行途中由内存创建。此处先讲解JDK的动态代理:
在Java中提供了一个代理类:Proxy (JDK1.3提供)
Proxy 作为 动态代理类,提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象: 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler h)
newProxyInstance()方法参数说明: - ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可 - Class [] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口 - InvocationHandler h : 代理对象的调用处理程序
InvocationHandler中invoke方法参数说明:
//同时我们发现InvocationHandler是一个函数式接口,因此我们可以用lambda表达式来写:public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}
上代码:
由于【卖票方法(接口)】与【火车站类】与静态代理一致,不再赘述
代理工厂类:
//代理工厂,用来创建代理对象public class ProxyFactory { private TrainStation station = new TrainStation(); public SellTickets getProxyObject(){ //使用代理类获得代理对象: //1.获得其类加载器 //2.获得真实对象所实现的接口 //3.调用InvocationHandler处理真实对象 SellTickets sellTickets = (SellTickets)Proxy.newProxyInstance(station.getClass().getClassLoader(), station.getClass().getInterfaces(), (proxy,method,args)->{ System.out.println("(jdk动态代理)代售点收取了一定的手续费用..."); //执行真实对象: return method.invoke(station, args); }); return sellTickets; }}
特此注释下lambda表达式:
//lambda表达式注释:Proxy.newProxyInstance(station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理点收取一些服务费用(JDK动态代理方式)"); //执行真实对象 Object result = method.invoke(station, args); return result; } });
测试类:
public class Client { public static void main(String[] args) { //获取代理对象 //1.创建代理工厂对象 ProxyFactory factory = new ProxyFactory(); //2.使用factory对象的方法获取代理对象 SellTickets proxyObject = factory.getProxyObject(); //3.调用卖票的方法 proxyObject.sell(); }}
结果:
(jdk动态代理)代售点收取了一定的手续费用...火车站卖票
在代理工厂中,我们并没有直接通过实例去调用sell()方法,那是如何动态生成代理模式中的代理类呢?
通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:
Arthas
是Alibaba开源的Java诊断工具,深受开发者喜爱。当你遇到以下类似问题而束手无策时,
Arthas
可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
因动态代理是在 内存中动态生成代理类,因此需要将程序保持在运行中才能通过工具发现其代理类:
首先在client中加上一个死循环:
此时程序就会保持在运行状态,同时输出代理对象的类名,方便用工具查看:
在cmd/powershell使用Arthas工具
得到真正的动态代理:
代理类($Proxy0)实现了SellTickets。
这也就印证了我们之前说的真实类和代理类实现同样的接口。
代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。
筛出其重点代码可得到其执行过程:
//程序运行过程中动态生成的代理类public final class $Proxy0 extends Proxy implements SellTickets { private static Method m3; public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); } static { m3 = Class.forName("com.kuangstudy.design_pattern.proxy.jdk_proxy.SellTickets").getMethod("sell", new Class[0]); } public final void sell() { this.h.invoke(this, m3, null); }}
执行流程如下:
1. 在测试类中通过代理对象调用sell()方法2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
同样是通过去代理点买火车票的例子:
可以看到在newProxyInstance()
方法中需要由Class<?>[] interfaces
参数,也就是说必须要定义接口,才能对接口进行代理。
假如没有接口的情况下,这时就应该使用CGLib来实现动态代理:
CGLIB是一个功能强大,高性能的代码生成包。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib
它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
CGLIB是第三方提供的包,所以需要引入jar包的坐标:
cglib cglib 2.2.2
实现MethodInterceptor接口需要重写intercept()方法:
intercept方法参数说明: o : 代理对象 method : 真实对象中的方法的Method实例 args : 实际参数 methodProxy :代理对象中的方法的method实例
上代码:
火车站:
public class TrainStation { public void sell() { System.out.println("火车站卖票"); }}
代理工厂:
public class ProxyFactory implements MethodInterceptor { private TrainStation station = new TrainStation(); public TrainStation getProxyObject(){ //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数 Enhancer enhancer =new Enhancer(); //设置父类的字节码对象 enhancer.setSuperclass(station.getClass()); //设置回调函数 enhancer.setCallback(this); //创建代理对象 TrainStation obj = (TrainStation) enhancer.create(); return obj; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)"); TrainStation result = (TrainStation) methodProxy.invokeSuper(o, objects); return result; }}
顾客:
public class Client { public static void main(String[] args) { //创建代理工厂对象: ProxyFactory proxyFactory = new ProxyFactory(); //获取代理对象: TrainStation proxyObject = proxyFactory.getProxyObject(); //调用卖票方法: proxyObject.sell(); }}
运行结果:
代理点收取一些服务费用(CGLIB动态代理方式)火车站卖票
arthas再学习:
步骤方法与探究jdk动态代理方法一致:
好家伙,这也太长了吧这个类,挑了点重点代码看一看:
public class TrainStation$$EnhancerByCGLIB$$d50d37f9 extends TrainStation implements Factory { public TrainStation$$EnhancerByCGLIB$$d50d37f9() { TrainStation$$EnhancerByCGLIB$$d50d37f9 trainStation$$EnhancerByCGLIB$$d50d37f9 = this; TrainStation$$EnhancerByCGLIB$$d50d37f9.CGLIB$BIND_CALLBACKS(trainStation$$EnhancerByCGLIB$$d50d37f9); } static { TrainStation$$EnhancerByCGLIB$$d50d37f9.CGLIB$STATICHOOK1(); } //继承了TrainStation ,并调用super.sell()方法 public final void sell() { MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0; if (methodInterceptor == null) { TrainStation$$EnhancerByCGLIB$$d50d37f9.CGLIB$BIND_CALLBACKS(this); methodInterceptor = this.CGLIB$CALLBACK_0; } if (methodInterceptor != null) { Object object = methodInterceptor.intercept(this, CGLIB$sell$0$Method, CGLIB$emptyArgs, CGLIB$sell$0$Proxy); return; } super.sell(); }}
JDK代理和CGLIB代理
所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
动态代理和静态代理
优点:
缺点:
静态代理和装饰者模式的区别:
jdk动态代理在mybatis中的应用,主要通过 MapperProxyFactory
实现
如果使用过Mybatis,我们就会发现Mybatis的使用非常简单,首先定义一个dao接口,然后编写一个与dao接口的对应的配置文件,java对象与数据库字段的映射关系和dao接口对应的sql语句都是以配置的形式写在配置文件中,非常的简单清晰
但是笔者在使用的过程中就曾经有过这样的疑问,dao接口是怎么和mapper文件映射起来的呢?只有一个dao接口又是怎么以对象的形式来实现数据库的读写操作呢?相信有疑问的肯定不止我一个人,当然,在看了上面两节之后,应该很容易猜到可以通过代理模式来动态的创建dao接口的代理对象,并通过这个代理对象来实现数据库的操作。
Spring中的AOP实现
转载地址:http://yhzg.baihongyu.com/