本篇是 spi
的第四篇,本篇讲解的是 spi
中增加的 AOP
,还是和上一篇一样,我们先从大家熟悉的spring引出AOP.
AOP
是老生常谈的话题了,思想都不会是一蹴而就的.比如架构设计从 All in One
到 SOA
也是一个逐步演进的过程,所以本篇也讲讲这个AOP的思想演进过程.
AOP
,那你讲讲这用到了什么设计模式,dubbo又是如何做的. 假如我们就以 AOP
最常用的场景 事务
来说,我们最初的做法是怎么样的?
public class EmployeeServiceImpl implements IEmployeeService { private TransactionManager txManager; @Override public void save() { try { txManager.begin(); System.out.println("保存操作"); txManager.commit(); }catch (Exception e){ txManager.rollback(); e.printStackTrace(); } } @Override public void update() { try { txManager.begin(); System.out.println("更新操作"); txManager.commit(); }catch (Exception e){ txManager.rollback(); e.printStackTrace(); } } } 复制代码
这些代码存在的问题就很明显了,比如
优化代码我们第一个想到的是设计模式,那么我们进入如下的优化
public class APP { @Test public void testSave() throws Exception { IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(), new EmployeeServiceImpl()); service.save(); } @Test public void testUpdate() throws Exception { IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(), new EmployeeServiceImpl()); service.update(); } } 复制代码
通过装饰设计模式,我们解决了上面遇到的两个问题,但是同时也引出了新的问题,在客户端我们暴露了真实的对象 EmployeeServiceImpl
,这样就很不安全,那么我们可不可以把真实对象隐藏起来,让使用者看不到呢?那么我们进一步优化
通过这种方式,真实对象对使用者进行了一定的隐藏,但是又引出了新的问题
save
方法,万一有很多方法,则需要处理很多次 EmployeeServiceImpl
和 EmployeeServiceImplProxy
都要改动),增加了代码的维护难度 EmployeeServiceImplProxy
是只给 IEmployeeService
接口服务的,假如我新增了一个 IRoleService
,又要搞一个 RoleServiceImplProxy
,增加了维护难度 鉴于以上问题,我们能否再优化一下呢?答案是可以的
动态代理类是在程序运行期间由JVM通过反射等机制动态的生成的,所以不存在代理类的字节码文件.代理对象和真实对象的关系是在程序运行事情才确定的.
动态代理的方式和区别我们前面有讲过,这里就简单演示一下jdk动态代理
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class JDKProxyTest { @Autowired private TransactionManagerInvocationHandle handle; @Test public void testSave() throws Exception { IEmployeeService service = handle.getProxyObject(); service.save(); } @Test public void testUpdate() throws Exception { IEmployeeService service = handle.getProxyObject(); service.update(); } } 复制代码
public class TransactionManagerInvocationHandle implements InvocationHandler { @Setter private TransactionManager txManager; @Setter private Object target;//真实对象 //生成代理对象 //泛型只是为了调用时不用强转,如果用Object的话调用时需要强转 public <T> T getProxyObject() { return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),//类加载器 target.getClass().getInterfaces(),//为哪些接口做代理(拦截什么方法) this);//为哪个类监听增强操作的方法(把这些方法拦截到哪里处理) } //如何做增强操作(被拦截的方法在这里增强处理) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object obj = null; try { txManager.begin(); //原封不动调用之前的方法 obj = method.invoke(target, args); txManager.commit(); return obj; } catch (Exception e) { e.printStackTrace(); txManager.rollback(); } return obj; } } 复制代码
这样,对于使用者来说,就无需再关心事务的逻辑.当然这个还需要 getProxyObject
获取动态代理对象是不是还是太麻烦,那如何不调用 getProxyObject
就无声无息的注入动态代理对象呢?可以观看之前的 dubbo源码解析-简单原理、与spring融合
看了这么多演进的过程,是不是还是没有看到 dubbo
源码的影子?因为 dubbo
在做 spi
的设计的时候,也是有一个演进和优化的过程的.我们来看看dubbo是怎么做的
//dubbo spi中的aop instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 复制代码
下面引用文档介绍
ExtensionLoader 在加载扩展点时,如果加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper 类。
Wrapper类内容:
package com.alibaba.xxx; import com.alibaba.dubbo.rpc.Protocol; public class XxxProtocolWrapper implemenets Protocol { Protocol impl; public XxxProtocol(Protocol protocol) { impl = protocol; } // 接口方法做一个操作后,再调用extension的方法 public void refer() { //... 一些操作 impl.refer(); // ... 一些操作 } // ... } 复制代码
通过 Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在所有的扩展点上添加了逻辑,有些类似 AOP,即 Wrapper 代理了扩展点。
看到这里可能发现,dubbo里面的 spi
增加的 aop
,其实就是装饰者设计模式.但是从上面的演进中我们发现,装饰者设计模式还是有很多弊端的,后面是逐步演进,最后到达动态代理.那dubbo又是如何处理这个弊端逐步演进的?
dubbo里面有个概念叫 扩展点自适应
,也就是给接口注入拓展点是一个 Adaptive
实例,直到方法执行时,才决定调用的是哪一个拓展点的实现.这个在下一篇的 Adaptive
会详细介绍,本篇其实也是下一篇的启蒙篇.
既然本篇提到了spring的aop,那么这里插播一个小技巧,Spring的 AOP
增强方式一共有5种,分别为
增强类型 | 应用场景 |
---|---|
前置增强 | 权限控制、记录调用日志 |
后置增强 | 统计分析结果数据 |
异常增强 | 通过日志记录方法异常信息 |
最终增强 | 释放资源 |
环绕增强 | 缓存、性能、权限、事务管理 |
面试的时候也会问到5种增强方式,但是很多同学都是说,我每天都在加班,哪有时间记这些.但是其实如果你理解他的设计思想,那么就可以"理解性记忆",以后想忘都忘不掉.
//环绕 try { //前置 System.out.println("====="); //后置 }catch (Exception e){ //异常 }finally { //最终 } 复制代码
其实他这5种方式就是根据 try-catch-finally
的模型来设计的,只要你记住了这个设计的思想,自然不会忘记这5种方式,这也是我之前反复强调的,理解透原理和设计思想,很多东西都是一通百通的.
肥朝 是一个专注于 原理、源码、开发技巧的技术公众号,号内原创专题式源码解析、真实场景源码原理实战(重点)。 扫描下面二维码 关注肥朝,让本该造火箭的你,不再拧螺丝!