From 6951a5b6ce5889b13de3edb94f625c32af21bec3 Mon Sep 17 00:00:00 2001 From: AmyliaY <471816751@qq.com> Date: Mon, 3 Feb 2020 13:53:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=8ESpring=E5=8F=8AMybatis=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E6=BA=90=E7=A0=81=E4=B8=AD=E5=AD=A6=E4=B9=A0=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E6=A8=A1=E5=BC=8F(=E7=BB=93=E6=9E=84=E5=9E=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- ...源码中学习设计模式(创建型).md | 6 +- ...源码中学习设计模式(结构型).md | 1114 ++++++++++++++++- ...源码中学习设计模式(行为型).md | 2 +- ...架源码中学习设计模式的感悟.md | 6 + .../DesignPattern/设计模式.md | 13 - .../JDK动态代理的实现原理解析.md | 57 +- .../DesignPattern/装饰器模式类图.png | Bin 0 -> 42422 bytes 8 files changed, 1154 insertions(+), 50 deletions(-) create mode 100644 docs/LearningExperience/DesignPattern/从框架源码中学习设计模式的感悟.md delete mode 100644 docs/LearningExperience/DesignPattern/设计模式.md create mode 100644 images/DesignPattern/装饰器模式类图.png diff --git a/README.md b/README.md index 72e694a..b0e1ea5 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ - [SpringMVC 的设计与实现](/docs/Spring/SpringMVC/SpringMVC的设计与实现.md) - [SpringMVC 跨域解析](/docs/Spring/SpringMVC/SpringMVC-CROS.md) ### SpringJDBC - +努力编写中... ### Spring事务 - [Spring与事务处理](/docs/Spring/SpringTransaction/Spring与事务处理.md) @@ -87,10 +87,13 @@ - [把被说烂的BIO、NIO、AIO再从头到尾扯一遍](docs/Netty/IO/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md) ### 设计原理 +努力编写中... ## Redis +努力编写中... ## Tomcat +努力编写中... ## 学习心得 ### 个人经验 @@ -103,6 +106,7 @@ - [从Spring及Mybatis框架源码中学习设计模式(创建型)](docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md) - [从Spring及Mybatis框架源码中学习设计模式(行为型)](docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型).md) - [从Spring及Mybatis框架源码中学习设计模式(结构型)](docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型).md) +- [从框架源码中学习设计模式的感悟](docs/LearningExperience/DesignPattern/从框架源码中学习设计模式的感悟.md) ## 贡献者 感谢以下所有朋友对 [GitHub 技术社区 Doocs](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。 diff --git a/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md b/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md index efe4b74..b5e8fe2 100644 --- a/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md +++ b/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md @@ -3,8 +3,8 @@ 本篇博文主要看一下创建型的几个设计模式,即,单例模式、各种工厂模式 及 建造者模式。 ## 单例模式 -#### 目标 -确保某个类只有一个实例,并提供该实例的获取方法。 +#### 个人理解 +确保某个类只有一个实例,并提供该实例的获取方法。实际应用很多,不管是框架、JDK还是实际的项目开发,但大都会使用“饿汉式”或“枚举”来实现单例。“懒汉式”也有一些应用,但通过“双检锁机制”来保证单例的实现很少见。 #### 实现方式 最简单的就是 使用一个私有构造函数、一个私有静态变量,以及一个公共静态方法的方式来实现。懒汉式、饿汉式等简单实现就不多BB咯,这里强调一下双检锁懒汉式实现的坑,以及枚举方式的实现吧,最后再结合spring源码 扩展一下单例bean的实现原理。 @@ -62,7 +62,7 @@ public enum SqlCommandType { } ``` -#### 实际范例 +#### JDK中的范例 **1. java.lang.Runtime** ```java /** diff --git a/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型).md b/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型).md index 5439a4a..8e8aa84 100644 --- a/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型).md +++ b/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型).md @@ -1 +1,1113 @@ -编写中... \ No newline at end of file +设计模式是解决问题的方案,从大神的代码中学习对设计模式的使用,可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码(Spring系列、Mybatis)及JDK源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 + +本篇博文主要看一下结构型的几个设计模式,即,适配器模式、代理模式 及 装饰器模式。 + +## 适配器模式 +#### 个人理解 +从名字就很好理解,主要起到一个连接适配的作用。生活中也有很多这样的例子,比如我们给笔记本充电,不能直接使用国家标准电源,都需要一个“电源适配器”来适配电源输入的电流。使用适配器模式最大的好处就是复用现有组件。应用程序需要复用现有的类,但接口不能被该应用程序兼容,则无法直接使用。这种场景下就适合使用适配器模式实现接口的适配,从而完成组件的复用。 + +很明显,适配器模式通过提供 Adapter 的方式完成接口适配,实现了程序复用 Adaptee(被适配者) 的需求,避免了修改 Adaptee 实现接口,当有新的 Adaptee 需要被复用时,只要添加新的 Adapter 即可,这是符合“开放封闭”原则的。 + +本模式的应用也比较广泛,因为实际的开发中也有很多适配工作要做,所以 这些都可以考虑使用适配器模式。在spring及mybatis中也使用了本模式,分析如下。 + +#### Spring中的应用 +Spring 在 AOP 模块中,设计了一套 AdvisorAdapter 组件,将各种 Advice 对象适配成了相对应的 MethodInterceptor 对象。其中,AfterReturningAdviceAdapter、MethodBeforeAdviceAdapter 及 ThrowsAdviceAdapter 实现类扮演了“适配器”的角色,AfterReturningAdvice、MethodBeforeAdvice 及 ThrowsAdvice 扮演了“被适配者”角色,而AfterReturningAdviceInterceptor、MethodBeforeAdviceInterceptor 及 ThrowsAdviceInterceptor 则扮演了“适配目标”的角色。其源码实现如下。 +```java +/** + * Advice 适配器的顶级接口 + * @author Rod Johnson + */ +public interface AdvisorAdapter { + + /** + * 此适配器是否能适配 给定的 advice 对象 + */ + boolean supportsAdvice(Advice advice); + + /** + * 获取传入的 advisor 中的 Advice 对象,将其适配成 MethodInterceptor 对象 + */ + MethodInterceptor getInterceptor(Advisor advisor); +} + + +/** + * 将 AfterReturningAdvice 适配成 AfterReturningAdviceInterceptor 的适配器 + * @author Rod Johnson + * @author Juergen Hoeller + */ +@SuppressWarnings("serial") +class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable { + + public boolean supportsAdvice(Advice advice) { + return (advice instanceof AfterReturningAdvice); + } + + public MethodInterceptor getInterceptor(Advisor advisor) { + AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice(); + return new AfterReturningAdviceInterceptor(advice); + } +} + + +/** + * 将 MethodBeforeAdvice 适配成 MethodBeforeAdviceInterceptor 的适配器 + * @author Rod Johnson + * @author Juergen Hoeller + */ +@SuppressWarnings("serial") +class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { + + public boolean supportsAdvice(Advice advice) { + return (advice instanceof MethodBeforeAdvice); + } + + public MethodInterceptor getInterceptor(Advisor advisor) { + MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice(); + return new MethodBeforeAdviceInterceptor(advice); + } +} + + +/** + * 将 ThrowsAdvice 适配成 ThrowsAdviceInterceptor 的适配器 + * @author Rod Johnson + * @author Juergen Hoeller + */ +@SuppressWarnings("serial") +class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable { + + public boolean supportsAdvice(Advice advice) { + return (advice instanceof ThrowsAdvice); + } + + public MethodInterceptor getInterceptor(Advisor advisor) { + return new ThrowsAdviceInterceptor(advisor.getAdvice()); + } +} + + +/** + * 下面这三个接口的实现类 均为 “被适配者” + */ +public interface AfterReturningAdvice extends AfterAdvice { + + /** + * 目标方法method执行后,AOP会回调此方法,注意,它还传入了method的返回值 + */ + void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable; +} + +public interface MethodBeforeAdvice extends BeforeAdvice { + + /** + * 目标方法method要开始执行时,AOP会回调此方法 + */ + void before(Method method, Object[] args, Object target) throws Throwable; +} + +public interface ThrowsAdvice extends AfterAdvice { + +} + + +/** + * 下面这三个类均为“适配目标” + */ +public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable { + + private final AfterReturningAdvice advice; + + /** + * 为给定的 advice 创建一个 AfterReturningAdviceInterceptor 对象 + */ + public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + } + + public Object invoke(MethodInvocation mi) throws Throwable { + Object retVal = mi.proceed(); + this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); + return retVal; + } +} + +public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { + + private MethodBeforeAdvice advice; + + /** + * 为指定的advice创建对应的MethodBeforeAdviceInterceptor对象 + */ + public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + } + + /** + * 这个invoke方法是拦截器的回调方法,会在代理对象的方法被调用时触发回调 + */ + public Object invoke(MethodInvocation mi) throws Throwable { + // 首先触发了advice的before()方法的回调 + // 然后才是MethodInvocation的process()方法回调 + this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() ); + return mi.proceed(); + } +} + +public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice { + + private static final String AFTER_THROWING = "afterThrowing"; + + private static final Log logger = LogFactory.getLog(ThrowsAdviceInterceptor.class); + + private final Object throwsAdvice; + + private final Map exceptionHandlerMap = new HashMap(); + + public ThrowsAdviceInterceptor(Object throwsAdvice) { + Assert.notNull(throwsAdvice, "Advice must not be null"); + this.throwsAdvice = throwsAdvice; + + // 配置 throwsAdvice 的回调 + Method[] methods = throwsAdvice.getClass().getMethods(); + for (Method method : methods) { + if (method.getName().equals(AFTER_THROWING) && + (method.getParameterTypes().length == 1 || method.getParameterTypes().length == 4) && + Throwable.class.isAssignableFrom(method.getParameterTypes()[method.getParameterTypes().length - 1]) + ) { + // 配置异常处理 + this.exceptionHandlerMap.put(method.getParameterTypes()[method.getParameterTypes().length - 1], method); + if (logger.isDebugEnabled()) { + logger.debug("Found exception handler method: " + method); + } + } + } + + if (this.exceptionHandlerMap.isEmpty()) { + throw new IllegalArgumentException( + "At least one handler method must be found in class [" + throwsAdvice.getClass() + "]"); + } + } + + public Object invoke(MethodInvocation mi) throws Throwable { + // 把对目标对象的方法调用放入 try/catch 中,并在 catch 中触发 + // throwsAdvice 的回调,把异常接着向外抛,不做过多处理 + try { + return mi.proceed(); + } + catch (Throwable ex) { + Method handlerMethod = getExceptionHandler(ex); + if (handlerMethod != null) { + invokeHandlerMethod(mi, ex, handlerMethod); + } + throw ex; + } + } +} + + +/** + * 本类的 getInterceptors() 方法使用上述 适配器组件,完成了 + * 从 Advice 到 MethodInterceptor 的适配工作 + */ +public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable { + + /** + * 持有AdvisorAdapter的list,这个list中的AdvisorAdapter与 + * 实现 spring AOP 的 Advice 增强功能相对应 + */ + private final List adapters = new ArrayList(3); + + /** + * 将已实现的 AdviceAdapter 加入 list + */ + public DefaultAdvisorAdapterRegistry() { + registerAdvisorAdapter(new MethodBeforeAdviceAdapter()); + registerAdvisorAdapter(new AfterReturningAdviceAdapter()); + registerAdvisorAdapter(new ThrowsAdviceAdapter()); + } + + public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { + List interceptors = new ArrayList(3); + + // 从Advisor通知器中获取配置的Advice + Advice advice = advisor.getAdvice(); + + // 如果advice是MethodInterceptor类型的,直接加进interceptors,不用适配 + if (advice instanceof MethodInterceptor) { + interceptors.add((MethodInterceptor) advice); + } + + // 如果advice不是MethodInterceptor类型的,就将其适配成MethodInterceptor, + // 当前的DefaultAdvisorAdapterRegistry对象 在初始化时就已经为 adapters 添加了 + // 三种 AdvisorAdapter 的实例 + for (AdvisorAdapter adapter : this.adapters) { + // 依次使用 adapters集合中的 adapter 对 advice 进行适配 + // 将其适配成 MethodInterceptor 对象 + if (adapter.supportsAdvice(advice)) { + interceptors.add(adapter.getInterceptor(advisor)); + } + } + if (interceptors.isEmpty()) { + throw new UnknownAdviceTypeException(advisor.getAdvice()); + } + return interceptors.toArray(new MethodInterceptor[interceptors.size()]); + } + + public void registerAdvisorAdapter(AdvisorAdapter adapter) { + this.adapters.add(adapter); + } + + /** + * 如果adviceObject是Advisor的实例,则将adviceObject转换成Advisor类型并返回 + */ + public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException { + if (adviceObject instanceof Advisor) { + return (Advisor) adviceObject; + } + if (!(adviceObject instanceof Advice)) { + throw new UnknownAdviceTypeException(adviceObject); + } + Advice advice = (Advice) adviceObject; + if (advice instanceof MethodInterceptor) { + return new DefaultPointcutAdvisor(advice); + } + for (AdvisorAdapter adapter : this.adapters) { + if (adapter.supportsAdvice(advice)) { + return new DefaultPointcutAdvisor(advice); + } + } + throw new UnknownAdviceTypeException(advice); + } +} +``` +像这样整理出来以后,其类结构及层次设计还是比较清晰明了的,比起很多书上范例的浅尝辄止,结合这些实际场景及源码去理解这些设计模式,要让人更加印象深刻。 + +#### Mybatis中的应用 +MyBatis 的日志模块中使用了适配器模式,MyBatis 内部调用其日志模块时,使用了其内部接口(org.apache.ibatis.logging.Log)。但是 Log4j、Slf4j 等第三方日志框架对外提供的接口各不相同,MyBatis 为了集成和复用这些第三方日志框架,在其日志模块中提供了多种 Adapter 实现 如:Log4jImpl、Slf4jImpl 等等,它们将这些 “第三方日志框架对外的接口方法” 适配成 “Log 接口方法”,这样 MyBatis 内部就可以统一通过该 Log 接口调用第三方日志框架的功能了。 + +其中,Log 接口定义了日志模块的功能,日志适配器 Log4jImpl、Slf4jImpl 等通过实现此接口,将对应框架中的日志类 (Logger) 里的方法 适配成Log接口中定义的方法。 +```java +/** + * mybatis的日志接口,统一了不同日志框架的 日志操作, + * 由各实现类 对各日志框架进行具体的适配 + */ +public interface Log { + + boolean isDebugEnabled(); + + boolean isTraceEnabled(); + + void error(String s, Throwable e); + + void error(String s); + + void debug(String s); + + void trace(String s); + + void warn(String s); +} + + +/** + * Log4j 日志框架适配器 + */ +public class Log4jImpl implements Log { + + /** + * 注意!!!!! + * 下面的 log 对象是 Log4j框架的 org.apache.log4j.Logger + * 本适配器完成了 “org.apache.log4j.Logger中的方法” 到 + * “org.apache.ibatis.logging.Log中的方法” 的适配 + * 从下面的代码中可以很轻易地看出来 + */ + private final Logger log; + + private static final String FQCN = Log4jImpl.class.getName(); + + public Log4jImpl(String clazz) { + log = Logger.getLogger(clazz); + } + + /** + * !!!!!!!!!!!!!!! + * 具体适配过程如下: + * !!!!!!!!!!!!!!! + */ + @Override + public boolean isDebugEnabled() { + return log.isDebugEnabled(); + } + + @Override + public boolean isTraceEnabled() { + return log.isTraceEnabled(); + } + + @Override + public void error(String s, Throwable e) { + log.log(FQCN, Level.ERROR, s, e); + } + + @Override + public void error(String s) { + log.log(FQCN, Level.ERROR, s, null); + } + + @Override + public void debug(String s) { + log.log(FQCN, Level.DEBUG, s, null); + } + + @Override + public void trace(String s) { + log.log(FQCN, Level.TRACE, s, null); + } + + @Override + public void warn(String s) { + log.log(FQCN, Level.WARN, s, null); + } +} + + +/** + * JDK 日志组件适配器 + */ +public class Jdk14LoggingImpl implements Log { + + /** + * 使用了JDK中的日志类 java.util.logging.Logger + */ + private final Logger log; + + public Jdk14LoggingImpl(String clazz) { + log = Logger.getLogger(clazz); + } + + @Override + public boolean isDebugEnabled() { + return log.isLoggable(Level.FINE); + } + + @Override + public boolean isTraceEnabled() { + return log.isLoggable(Level.FINER); + } + + @Override + public void error(String s, Throwable e) { + log.log(Level.SEVERE, s, e); + } + + @Override + public void error(String s) { + log.log(Level.SEVERE, s); + } + + @Override + public void debug(String s) { + log.log(Level.FINE, s); + } + + @Override + public void trace(String s) { + log.log(Level.FINER, s); + } + + @Override + public void warn(String s) { + log.log(Level.WARNING, s); + } +} +``` + +## 代理模式 +#### 个人理解 +代理模式的实际应用 主要体现在框架开发中,日常业务上的开发工作中很少有场景需要使用该模式。而代理模式中 动态代理尤为重要,不管是自己公司的内部框架 还是 一些知名的开源框架,很多重要的实现都用到了该模式。比如,有些 CS架构中,Client端的远程方法调用 就使用了动态代理,在invoke()方法中 为被代理对象调用的方法 织入远程调用处理,然后将远程处理的结果返回给调用者;Spring的AOP也是优先使用JDK动态代理来完成;Mybatis为JDBC操作织入日志处理,等等。下面我们结合源码来深入理解一下这个模式。 + +#### 动态代理原理 +静态代理没什么好讲的,很少见用到,功能也比较薄弱,本篇重点讲解动态代理。首先了解一下JDK动态代理的原理,这对理解 Spring AOP 部分的源码及实现原理也很有帮助。 + +JDK 动态代理的实现原理是,动态创建代理类井通过指定类加载器加载,然后在创建代理对象时将 InvokerHandler 对象作为构造参数传入。当调用代理对象的方法时,会调用 InvokerHandler 的 invoke() 方法,并最终调用真正业务对象的相应方法。 JDK 动态代理不仅在 Spring 及 MyBatis 的多个模块中都有所涉及, 在其它很多开源框架中也能看到其身影。 +```java +/** + * 一般会使用实现了 InvocationHandler 的类作为代理对象的生产工厂, + * 并且通过持有被代理对象target,来在invoke()方法中对被代理对象的目标方法进行调用和增强, + * 这些我们都能通过下面这段代码看懂,但代理对象是如何生成的?invoke()方法又是如何被调用的呢? + */ +public class ProxyFactory implements InvocationHandler{ + + private Object target = null; + + public Object getInstanse(Object target){ + + this.target = target; + return Proxy.newProxyInstance(target.getClass().getClassLoader(), + target.getClass().getInterfaces(), this); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + + Object ret = null; + System.out.println("前置增强"); + ret = method.invoke(target, args); + System.out.println("后置增强"); + return ret; + } +} + + +/** + * 实现了接口MyInterface和接口的play()方法,可以作为被代理类 + */ +public class TargetObject implements MyInterface { + + @Override + public void play() { + System.out.println("妲己,陪你玩 ~"); + + } +} + + +/** + * 测试类 + */ +public class ProxyTest { + + public static void main(String[] args) { + TargetObject target = new TargetObject(); + // ProxyFactory 实现了 InvocationHandler接口,其中的 getInstanse() 方法利用 Proxy 类 + // 生成了target目标对象的代理对象,并返回;且ProxyFactory持有对target的引用,可以在 + // invoke() 中完成对 target 相应方法的调用,以及目标方法前置后置的增强处理 + ProxyFactory proxyFactory = new ProxyFactory(); + // 这个mi就是JDK的 Proxy 类动态生成的代理类 $Proxy0 的实例,该实例中的方法都持有对 + // invoke() 方法的回调,所以当调用其方法时,就能够执行 invoke() 中的增强处理 + MyInterface mi = (MyInterface)proxyFactory.getInstanse(target); + // 这样可以看到 mi 的 Class 到底是什么 + System.out.println(mi.getClass()); + // 这里实际上调用的就是 $Proxy0代理类中对 play() 方法的实现,结合下面的代码可以看到 + // play() 方法通过 super.h.invoke() 完成了对 InvocationHandler对象(proxyFactory)中 + // invoke()方法的回调,所以我们才能够通过 invoke() 方法实现对 target 对象方法的 + // 前置后置增强处理 + mi.play(); + // 总的来说,就是在invoke()方法中完成target目标方法的调用,及前置后置增强, + // JDK动态生成的代理类中对 invoke() 方法进行了回调 + } + + /** + * 将ProxyGenerator生成的动态代理类的输出到文件中,利用反编译工具luyten等就可 + * 以看到生成的代理类的源码咯,下面给出了其反编译好的代码实现 + */ + @Test + public void generatorSrc(){ + byte[] bytesFile = ProxyGenerator.generateProxyClass("$Proxy0", TargetObject.class.getInterfaces()); + FileOutputStream fos = null; + try{ + String path = System.getProperty("user.dir") + "\\$Proxy0.class"; + File file = new File(path); + fos = new FileOutputStream(file); + fos.write(bytesFile); + fos.flush(); + } catch (Exception e){ + e.printStackTrace(); + } finally{ + try { + fos.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } +} + + +/** + * Proxy生成的代理类,可以看到,其继承了Proxy,并且实现了被代理类的接口 + */ +public final class $Proxy0 extends Proxy implements MyInterface { + private static Method m1; + private static Method m0; + private static Method m3; + private static Method m2; + + static { + try { + $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); + $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class[])new Class[0]); + //实例化MyInterface的play方法 + $Proxy0.m3 = Class.forName("com.shuitu.test.MyInterface").getMethod("play", (Class[])new Class[0]); + $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class[])new Class[0]); + } + catch (NoSuchMethodException ex) { + throw new NoSuchMethodError(ex.getMessage()); + } + catch (ClassNotFoundException ex2) { + throw new NoClassDefFoundError(ex2.getMessage()); + } + } + + public $Proxy0(final InvocationHandler invocationHandler) { + super(invocationHandler); + } + + public final void play() { + try { + // 这个 h 其实就是我们调用 Proxy.newProxyInstance() 方法时传进去的ProxyFactory(InvocationHandler对象), + // 该对象的 invoke() 方法中实现了对目标对象的目标方法的增强。看到这里,利用动态代理实现方法增强的 + // 实现原理就全部理清咯 + super.h.invoke(this, $Proxy0.m3, null); + } + catch (Error | RuntimeException error) { + throw new RuntimeException(); + } + catch (Throwable t) { + throw new UndeclaredThrowableException(t); + } + } + + public final boolean equals(final Object o) { + try { + return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o }); + } + catch (Error | RuntimeException error) { + throw new RuntimeException(); + } + catch (Throwable t) { + throw new UndeclaredThrowableException(t); + } + } + + public final int hashCode() { + try { + return (int)super.h.invoke(this, $Proxy0.m0, null); + } + catch (Error | RuntimeException error) { + throw new RuntimeException(); + } + catch (Throwable t) { + throw new UndeclaredThrowableException(t); + } + } + + public final String toString() { + try { + return (String)super.h.invoke(this, $Proxy0.m2, null); + } + catch (Error | RuntimeException error) { + throw new RuntimeException(); + } + catch (Throwable t) { + throw new UndeclaredThrowableException(t); + } + } +} +``` + +#### Spring 中的应用 +Spring 在生成动态代理类时,会优先选择使用JDK动态代理,除非被代理类没有实现接口。 +```java +/** + * 可以看到,其实现了 InvocationHandler 接口,所以肯定也定义了一个 使用 java.lang.reflect.Proxy + * 动态生成代理对象的方法,并在实现的 invoke() 方法中为代理对象织入增强方法 + */ +final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { + + public Object getProxy() { + return getProxy(ClassUtils.getDefaultClassLoader()); + } + + /** + * 获取 JVM 动态生成的代理对象 + */ + public Object getProxy(ClassLoader classLoader) { + if (logger.isDebugEnabled()) { + logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); + } + + // 获取代理类要实现的接口 + Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); + findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); + + // 通过 Proxy 生成代理对象 + return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); + } + + /** + * 本类所生成的代理对象中,所有方法的调用 都会回调本方法。 + * 根据用户的配置,对指定的切面进行相应的增强 + */ + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + MethodInvocation invocation; + Object oldProxy = null; + boolean setProxyContext = false; + + // 通过 targetSource 可以获取被代理对象 + TargetSource targetSource = this.advised.targetSource; + Class targetClass = null; + Object target = null; + + try { + // 如果目标对象调用的是 Obejct 类中的基本方法,如:equals、hashCode 则进行相应的处理 + if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { + // 如果目标对象没有重写 Object 类的基本方法:equals(Object other) + return equals(args[0]); + } + if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { + // 如果目标对象没有重写 Object类的基本方法:hashCode() + return hashCode(); + } + if (!this.advised.opaque && method.getDeclaringClass().isInterface() && + method.getDeclaringClass().isAssignableFrom(Advised.class)) { + // 使用代理配置对 ProxyConfig 进行服务调用 + return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); + } + + Object retVal; + + if (this.advised.exposeProxy) { + // 如果有必要,可以援引 + oldProxy = AopContext.setCurrentProxy(proxy); + setProxyContext = true; + } + + // 获取目标对象,为目标方法的调用做准备 + target = targetSource.getTarget(); + if (target != null) { + targetClass = target.getClass(); + } + + // 获取定义好的拦截器链 + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + + // 如果没有配置拦截器,就直接调用目标对象target的method方法,并获取返回值 + if (chain.isEmpty()) { + retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); + } + else { + // 如果有拦截器链,则需要先调用拦截器链中的拦截器,再调用目标的对应方法 + // 这里通过构造 ReflectiveMethodInvocation 来实现 + invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); + // 沿着拦截器链继续向下处理 + retVal = invocation.proceed(); + } + + // 获取 method 返回值的类型 + Class returnType = method.getReturnType(); + if (retVal != null && retVal == target && returnType.isInstance(proxy) && + !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { + // 特殊提醒:它返回“this”,方法的返回类型与类型兼容。 + // 注意,如果 target 在另一个返回的对象中设置了对自身的引用,spring 将无法处理 + retVal = proxy; + } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { + throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); + } + return retVal; + } + finally { + if (target != null && !targetSource.isStatic()) { + // 必须来自 TargetSource. + targetSource.releaseTarget(target); + } + if (setProxyContext) { + // 存储旧的 proxy. + AopContext.setCurrentProxy(oldProxy); + } + } + } +} +``` + +#### Mybatis中的应用 +Mybatis 的 PooledConnection 类中封装了数据库连接的代理对象,对数据库连接的操作大都会通过该代理对象完成。 +```java +/** + * Mybatis 封装的数据库连接类,它实现了 InvocationHandler 接口,封装了真正的 + * 数据库连接对象 (java.sql.Connection) 及其代理对象,该代理对象是通过 + * JDK 动态代理类 Proxy 产生的 + * @author Clinton Begin + */ +class PooledConnection implements InvocationHandler { + + private static final String CLOSE = "close"; + private static final Class[] IFACES = new Class[] { Connection.class }; + + private final int hashCode; + + /** + * 记录当前 PooledConnection对象 是从哪个 PooledDataSource(数据库连接池)对象获取的。 + * 当调用 close() 方法时会将 PooledConnection 放回该 dataSource 连接池 + */ + private final PooledDataSource dataSource; + /** 真正的 数据库连接对象 */ + private final Connection realConnection; + /** 数据库连接的 代理对象 */ + private final Connection proxyConnection; + /** 从连接池中取出该连接的时间戳 */ + private long checkoutTimestamp; + /** 该连接创建的时间戳 */ + private long createdTimestamp; + /** 最后一次被使用的时间戳 */ + private long lastUsedTimestamp; + /** 由数据库 URL、用户名 和 密码 计算出来的 hash值,可用于标识该连接所在的连接池 */ + private int connectionTypeCode; + /** + * 检测当前 PooledConnection 是否有效,主要是为了防止程序通过 close() 方法 + * 将连接归还给连接池之后,依然通过该连接操作数据库 + */ + private boolean valid; + + /** + * 注意该构造方法中对 proxyConnection 的初始化 + */ + public PooledConnection(Connection connection, PooledDataSource dataSource) { + this.hashCode = connection.hashCode(); + this.realConnection = connection; + this.dataSource = dataSource; + this.createdTimestamp = System.currentTimeMillis(); + this.lastUsedTimestamp = System.currentTimeMillis(); + this.valid = true; + // 这里使用了 JDK 的 Proxy 为数据库连接创建了一个代理对象,对该代理对象的所有操作 + // 都会回调 本类中的 invoke() 方法 + this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); + } + + /** + * 实现了 InvocationHandler 接口中的方法 + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + // 如果调用的是 close() 方法,则将其放入连接池,而不是真正关闭数据库连接 + if (CLOSE.equals(methodName)) { + dataSource.pushConnection(this); + return null; + } + try { + if (!Object.class.equals(method.getDeclaringClass())) { + // 通过 valid 字段检测连接是否有效 + checkConnection(); + } + // 调用真正数据库连接对象的对应方法 + return method.invoke(realConnection, args); + } catch (Throwable t) { + throw ExceptionUtil.unwrapThrowable(t); + } + } +} +``` + +## 装饰器模式 +#### 个人理解 +在实际生产中,新需求在软件的整个生命过程中总是不断出现的。当有新需求出现时,就需要为某些组件添加新的功能来满足这些需求。 添加新功能的方式有很多,我们可以直接修改已有组件的代码井添加相应的新功能,但这样会破坏己有组件的稳定性,修改完成后,整个组件需要重新进行测试才能上线使用。 这种方式显然违反了 “开放封闭” 原则。 + +另一种方式是使用继承,我们可以创建子类并在子类中添加新功能实现扩展。 这种方法是静态的,用户不能控制增加行为的方式和时机。 而且有些情况下继承是不可行的,例如 己有组件是被 final 修饰的类。 另外,如果待添加的新功能存在多种组合,使用继承方式可能会导致大量子类的出现。 例如,有 4 个待添加的新功能,系统需要动态使用任意多个功能的组合, 则需要添加 15 个子类才能满足全部需求。 + +装饰器模式能够帮助我们解决上述问题,装饰器可以动态地为对象添加功能,它是基于组合的方式实现该功能的。在实践中,我们应该尽量使用组合的方式来扩展系统的功能,而非使用继承的方式。通过装饰器模式的介绍,可以帮助读者更好地理解设计模式中常见的一句话:组合优于继承。下面先来看一下装饰器模式的类图,及其核心角色。 + +![avatar](/images/DesignPattern/装饰器模式类图.png) + +- Component (组件):组件接口定义了全部 “组件实现类” 以及所有 “装饰器实现” 的行为。 +- ConcreteComponent (具体组件实现类):通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了 Component 接口中定义的最基本的功能,其他高级功能或后续添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。 +- Decorator (装饰器):所有装饰器的父类,它是一个实现了 Component 接口的抽象类,并持有一个 Component 被装饰对象,这就实现了装饰器的嵌套组合和复用。 +- ConcreteDecorator (具体的装饰器实现类):该实现类要向被装饰对象添加某些功能,被装饰的对象只要是 Component 类型即可。 + +#### Mybatis中的应用 +在 MyBatis 的缓存模块中,使用了装饰器模式的变体,其中将 Decorator 接口和 Component 接口合并为一个 Component 接口,即,去掉了 Decorator 这个中间层,ConcreteDecorator 直接实现了Component 接口。 + +MyBatis 中缓存模块相关的代码位于 cache 包下, 其中 Cache 接口是缓存模块的核心接口,它定义了所有缓存的基本行为,扮演了 Component 的角色。实现类 PerpetualCache 扮演了 ConcreteComponent 的角色,其实现比较简单,底层使用 HashMap 记录缓存项,也是通过该 HashMap 对象的方法实现了 Cache 接口中定义的相应方法。而 cache 包下的 decorators 包中,则定义了一系列 ConcreteDecorator 的实现,如 BlockingCache、FifoCache 及 LruCache 等等,它们都持有一个 Cache 类型的对象,通过嵌套组合的方式为该 Cache对象 装饰相应的功能。其源码实现如下。 +```java +public interface Cache { + + /** 该缓存对象的 id */ + String getId(); + + /** 向缓存中添加数据,一般 key 是 CacheKey,value 是查询结果 */ + void putObject(Object key, Object value); + + /** 根据指定的 key,在缓存中查找对应的结果对象 */ + Object getObject(Object key); + + /** 删除 key 对应的缓存项 */ + Object removeObject(Object key); + + /** 清空缓存 */ + void clear(); + + /** 缓存项的个数,该方法不会被 MyBatis 核心代码使用,所以可提供空实现 */ + int getSize(); + + /** + * 获取读写锁,该方法不会被 MyBatis 核心代码使用,所以可提供空实现。 + * 这里在接口中为此方法提供了默认实现,也是 JDK8 的新特性 + */ + default ReadWriteLock getReadWriteLock() { + return null; + } +} + + +public class PerpetualCache implements Cache { + + /** Cache 对象的唯一标识 */ + private final String id; + /** 用于记录缓存项的 Map 对象 */ + private final Map cache = new HashMap<>(); + + public PerpetualCache(String id) { + this.id = id; + } + + @Override + public String getId() { + return id; + } + + /** + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * 下面所有的方法都是通过 cache 这个 HashMap对象 的相应方法实现的 + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ + @Override + public int getSize() { + return cache.size(); + } + + @Override + public void putObject(Object key, Object value) { + cache.put(key, value); + } + + @Override + public Object getObject(Object key) { + return cache.get(key); + } + + @Override + public Object removeObject(Object key) { + return cache.remove(key); + } + + @Override + public void clear() { + cache.clear(); + } + + /** + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * 重写了 equals() 和 hashCode() 方法,两者都只关心 id 字段,并不关心 cache 字段 + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ + @Override + public boolean equals(Object o) { + if (getId() == null) { + throw new CacheException("Cache instances require an ID."); + } + if (this == o) { + return true; + } + if (!(o instanceof Cache)) { + return false; + } + + Cache otherCache = (Cache) o; + return getId().equals(otherCache.getId()); + } + + @Override + public int hashCode() { + if (getId() == null) { + throw new CacheException("Cache instances require an ID."); + } + return getId().hashCode(); + } +} + + +/** + * 阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据。 + * 假设线程 A 在 BlockingCache 中未查找到 keyA 对应的缓存项时,线程 A 会获取 keyA 对应的锁, + * 这样后续线程在查找 keyA 时会被阻塞 + */ +public class BlockingCache implements Cache { + + /** 阻塞超时时长 */ + private long timeout; + /** 被装饰的底层 Cache 对象 */ + private final Cache delegate; + /** 每个 key 都有对应的 ReentrantLock 对象 */ + private final ConcurrentHashMap locks; + + public BlockingCache(Cache delegate) { + this.delegate = delegate; + this.locks = new ConcurrentHashMap<>(); + } + + @Override + public Object getObject(Object key) { + // 获取该 key 对应的锁 + acquireLock(key); + // 查询 key + Object value = delegate.getObject(key); + // 缓存中有 key 对应的缓存项,则释放锁,否则继续持有锁 + if (value != null) { + releaseLock(key); + } + return value; + } + + private void acquireLock(Object key) { + // 获取 ReentrantLock 对象 + Lock lock = getLockForKey(key); + // 获取锁,带超时时长 + if (timeout > 0) { + try { + boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS); + // 超时,则抛出异常 + if (!acquired) { + throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId()); + } + } catch (InterruptedException e) { + throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e); + } + } else { + // 获取锁,不带起时时长 + lock.lock(); + } + } + + private ReentrantLock getLockForKey(Object key) { + // 创建 ReentrantLock 对象,尝试添加到 locks 集合中,若 locks 集合中已经有了 + // 相应的 ReentrantLock 对象,则使用 locks 集合中的 ReentrantLock 对象 + return locks.computeIfAbsent(key, k -> new ReentrantLock()); + } + + @Override + public void putObject(Object key, Object value) { + try { + // 向缓存中添加缓存项 + delegate.putObject(key, value); + } finally { + // 释放锁 + releaseLock(key); + } + } + + private void releaseLock(Object key) { + // 获取锁 + ReentrantLock lock = locks.get(key); + // 锁是否被当前线程持有 + if (lock.isHeldByCurrentThread()) { + // 释放锁 + lock.unlock(); + } + } + + @Override + public String getId() { + return delegate.getId(); + } + + @Override + public int getSize() { + return delegate.getSize(); + } + + @Override + public Object removeObject(Object key) { + // despite of its name, this method is called only to release locks + releaseLock(key); + return null; + } + + @Override + public void clear() { + delegate.clear(); + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } +} + + +/** + * 在很多场景中,为了控制缓存的大小,系统需要按照一定的规则清理缓存。 + * FifoCache 是先入先出版本的装饰器,当向缓存添加数据时,如果缓存项的个数已经达到上限, + * 则会将缓存中最老(即最早进入缓存)的缓存项删除 + */ +public class FifoCache implements Cache { + + /** 底层被装饰的底层 Cache 对象 */ + private final Cache delegate; + /** 用于记录 key 进入缓存的先后顺序,使用的是 LinkedList 类型的集合对象 */ + private final Deque keyList; + /** 记录了缓存项的上限,超过该值,则需要清理最老的缓存项 */ + private int size; + + public FifoCache(Cache delegate) { + this.delegate = delegate; + this.keyList = new LinkedList<>(); + this.size = 1024; + } + + @Override + public void putObject(Object key, Object value) { + // 检测并清理缓存 + cycleKeyList(key); + // 添加缓存项 + delegate.putObject(key, value); + } + + private void cycleKeyList(Object key) { + // 记录 key + keyList.addLast(key); + // 如果达到缓存上限,则清理最老的缓存项 + if (keyList.size() > size) { + Object oldestKey = keyList.removeFirst(); + delegate.removeObject(oldestKey); + } + } + + @Override + public String getId() { + return delegate.getId(); + } + + @Override + public int getSize() { + return delegate.getSize(); + } + + public void setSize(int size) { + this.size = size; + } + + @Override + public Object getObject(Object key) { + return delegate.getObject(key); + } + + @Override + public Object removeObject(Object key) { + return delegate.removeObject(key); + } + + @Override + public void clear() { + delegate.clear(); + keyList.clear(); + } +} +``` \ No newline at end of file diff --git a/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型).md b/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型).md index 3979bec..0c8fe2f 100644 --- a/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型).md +++ b/docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型).md @@ -1,6 +1,6 @@ 设计模式是解决问题的方案,从大神的代码中学习对设计模式的使用,可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码(Spring系列、Mybatis)及JDK源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 -本篇博文主要看一下结构型的几个设计模式,即,策略模式、模板方法模式、迭代器模式 及 观察者模式。 +本篇博文主要看一下行为型的几个设计模式,即,策略模式、模板方法模式、迭代器模式 及 观察者模式。 ## 策略模式 #### 个人理解 diff --git a/docs/LearningExperience/DesignPattern/从框架源码中学习设计模式的感悟.md b/docs/LearningExperience/DesignPattern/从框架源码中学习设计模式的感悟.md new file mode 100644 index 0000000..4d6dbf0 --- /dev/null +++ b/docs/LearningExperience/DesignPattern/从框架源码中学习设计模式的感悟.md @@ -0,0 +1,6 @@ +努力编写中... + + + + + diff --git a/docs/LearningExperience/DesignPattern/设计模式.md b/docs/LearningExperience/DesignPattern/设计模式.md deleted file mode 100644 index 46c08ac..0000000 --- a/docs/LearningExperience/DesignPattern/设计模式.md +++ /dev/null @@ -1,13 +0,0 @@ -## 六大原则 - 1. 单一职责:一个类只负责唯一一项职责 - 2. 依赖倒置:即面向接口编程,系统的高层模块(顶层接口、顶层抽象类等)不应该依赖底层模块(具体实现类),当需求发生变化时,对外接口不变,只要提供新的实现类即可。 - 3. 接口隔离:尽量设计出功能单一的接口,避免实现类实现很多不必要的接口方法 - 4. **开放-封闭:对扩展开放,对修改关闭**,本原则是设计模式的终极目标 - 5. 迪米特法则:尽量减少类之间的耦合性 - 6. 里氏替换:继承体系的设计要合理 - ## 装饰器模式 - - - - - diff --git a/docs/Spring/AOP/JDK动态代理的实现原理解析.md b/docs/Spring/AOP/JDK动态代理的实现原理解析.md index 8097ff9..2729d0a 100644 --- a/docs/Spring/AOP/JDK动态代理的实现原理解析.md +++ b/docs/Spring/AOP/JDK动态代理的实现原理解析.md @@ -1,10 +1,9 @@ -最近在看 SpringAOP 部分的源码,所以对 JDK 动态代理具体是如何实现的这件事产生了很高的兴趣,而且能从源码上了解这个原理的话,也有助于对 spring-aop 模块的理解。话不多说,上代码。 - +最近在看spring AOP部分的源码,所以对JDK动态代理具体是如何实现的这件事产生了很高的兴趣,而且能从源码上了解这个原理的话,也有助于对spring-aop模块的理解。话不多说,上代码。 ```java /** - * 一般会使用实现了 InvocationHandler接口的类 作为代理对象生成工厂, - * 并且通过持有被代理对象 target,在 invoke() 方法中对被代理对象的目标方法进行调用和增强, - * 这些我们都能通过下面这段代码看懂,但代理对象是如何生成的?invoke() 方法又是如何被调用的呢? + * 一般会使用实现了 InvocationHandler 的类作为代理对象的生产工厂, + * 并且通过持有被代理对象target,来在invoke()方法中对被代理对象的目标方法进行调用和增强, + * 这些我们都能通过下面这段代码看懂,但代理对象是如何生成的?invoke()方法又是如何被调用的呢? */ public class ProxyFactory implements InvocationHandler{ @@ -30,13 +29,14 @@ public class ProxyFactory implements InvocationHandler{ } /** - * 实现了接口 MyInterface 和接口的 play() 方法,可以作为被代理类 + * 实现了接口MyInterface和接口的play()方法,可以作为被代理类 */ public class TargetObject implements MyInterface { @Override public void play() { - System.out.println("妲己,陪你玩 ~"); + System.out.println("妲己,陪你玩 ~"); + } } @@ -47,25 +47,26 @@ public class ProxyTest { public static void main(String[] args) { TargetObject target = new TargetObject(); - // ProxyFactory 实现了 InvocationHandler 接口,其中的 getInstanse() 方法利用 Proxy 类帮助生成了 - // target 目标对象的代理对象,并返回;且 ProxyFactory 持有对 target 的引用,可以在 invoke() 中完成对 target 相应方法 - // 的调用,以及目标方法前置后置的增强处理 + // ProxyFactory 实现了 InvocationHandler接口,其中的 getInstanse() 方法利用 Proxy 类 + // 生成了target目标对象的代理对象,并返回;且ProxyFactory持有对target的引用,可以在 + // invoke() 中完成对 target 相应方法的调用,以及目标方法前置后置的增强处理 ProxyFactory proxyFactory = new ProxyFactory(); - // 这个 mi 就是 JDK 的 Proxy 类生成的代理类$Proxy0 的对象,这个对象中的方法都持有对 invoke() 方法的回调 - // 所以当调用其方法时,就能够执行 invoke() 中的增强处理 - MyInterface mi = (MyInterface) proxyFactory.getInstanse(target); + // 这个mi就是JDK的 Proxy 类动态生成的代理类 $Proxy0 的实例,该实例中的方法都持有对 + // invoke() 方法的回调,所以当调用其方法时,就能够执行 invoke() 中的增强处理 + MyInterface mi = (MyInterface)proxyFactory.getInstanse(target); // 这样可以看到 mi 的 Class 到底是什么 System.out.println(mi.getClass()); - // 这里实际上调用的就是$Proxy0 中对 play() 方法的实现,可以看到 play 方法通过 super.h.invoke() - // 完成了对 InvocationHandler 对象 proxyFactory 的 invoke() 方法的回调 - // 所以我才能够通过 invoke() 方法实现对 target 对象方法的前置后置增强处理 + // 这里实际上调用的就是 $Proxy0代理类中对 play() 方法的实现,结合下面的代码可以看到 + // play() 方法通过 super.h.invoke() 完成了对 InvocationHandler对象(proxyFactory)中 + // invoke()方法的回调,所以我们才能够通过 invoke() 方法实现对 target 对象方法的 + // 前置后置增强处理 mi.play(); - // 总的来说,就是在 invoke() 方法中完成 target 目标方法的调用,及前置后置增强 - // 然后通过生成的代理类完成对对 invoke() 的回调 + // 总的来说,就是在invoke()方法中完成target目标方法的调用,及前置后置增强, + // JDK动态生成的代理类中对 invoke() 方法进行了回调 } /** - * 将 ProxyGenerator 生成的动态代理类的输出到文件中,利用反编译工具 luyten 等就可 + * 将ProxyGenerator生成的动态代理类的输出到文件中,利用反编译工具luyten等就可 * 以看到生成的代理类的源码咯,下面给出了其反编译好的代码实现 */ @Test @@ -92,7 +93,7 @@ public class ProxyTest { } /** - * Proxy 生成的代理类,可以看到,其继承了 Proxy,并且实现了被代理类的接口 + * Proxy生成的代理类,可以看到,其继承了Proxy,并且实现了被代理类的接口 */ public final class $Proxy0 extends Proxy implements MyInterface { @@ -105,7 +106,7 @@ public final class $Proxy0 extends Proxy implements MyInterface try { $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class[])new Class[0]); - //实例化 MyInterface 的 play 方法 + //实例化MyInterface的play方法 $Proxy0.m3 = Class.forName("com.shuitu.test.MyInterface").getMethod("play", (Class[])new Class[0]); $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class[])new Class[0]); } @@ -123,9 +124,9 @@ public final class $Proxy0 extends Proxy implements MyInterface public final void play() { try { - //这个 h 其实就是我们调用 Proxy.newProxyInstance() 方法时传进去的 ProxyFactory 对象, - //该对象的 invoke() 方法中实现了对目标对象的目标方法的增强。看到这里,利用动态代理实现方法增强的 - //调用原理就全部理清咯 + // 这个 h 其实就是我们调用 Proxy.newProxyInstance() 方法时传进去的ProxyFactory(InvocationHandler对象), + // 该对象的 invoke() 方法中实现了对目标对象的目标方法的增强。看到这里,利用动态代理实现方法增强的 + // 实现原理就全部理清咯 super.h.invoke(this, $Proxy0.m3, null); } catch (Error | RuntimeException error) { @@ -171,11 +172,5 @@ public final class $Proxy0 extends Proxy implements MyInterface throw new UndeclaredThrowableException(t); } } - } -``` -#### 动态代理原理图 - -![avatar](/images/动态代理原理图1.png) - -![avatar](/images/动态代理原理图2.png) +``` \ No newline at end of file diff --git a/images/DesignPattern/装饰器模式类图.png b/images/DesignPattern/装饰器模式类图.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb096cce96f0d90b2c8102baae5c191370be20d GIT binary patch literal 42422 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�P(UuAODiMch8BUh%_F{kIN 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