spring与设计模式之三代理模式
部分内容引用:
https://blog.csdn.net/shulianghan/article/details/119798155
一、定义
1.1定义
对于现实生活中的代理,大家非常好理解。我们需要代理,主要因为几个原因:
- 太忙-例如房产中介、代购
- 目前对象不是自身可以直接接触的-例如托人办事、例如掏钱购买某种服务都可以理解为代理
- 自己不方便出面的-例如找帮手干活
但在计算机中,这些不是太好理解。
因为我们设计程序主要满足性能和扩展、维护的要求,那么代理又可以给我们带来什么?
看看某些地方对这个定义:(来自于百度百科https://baike.baidu.com/item/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/8374046?fr=ge_ala)
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少存储器用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。
而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。 [1]
组成:
抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
也就是说在程序设计中,代理起到的作用和现实生活是类似的,使用的原因也是类似的:不方便或者不能,并且可以基于代理实现一些稍微复杂的功能。
1.2优点
(1).职责清晰
降低耦合 : 在一定程度上 , 降低了系统耦合性 , 扩展性好 ;
保护目标对象 : 代理类 代理目标对象的业务逻辑 , 客户端 直接与 代理类 进行交互 , 客户端 与 实际的目标对象之间没有关联 ;
增强目标对象 : 代理类 可以 在 目标对象基础上 , 添加新的功能 ;
1.3缺点
- 类个数增加 : 代理模式 会 造成 系统中 类的个数 增加 , 比不使用代理模式增加了代理类 , 系统的复杂度增加 ; ( 所有的设计模式都有这个缺点 )
- 性能降低 : 在 客户端 和 目标对象 之间 , 增加了一个代理对象 , 造成 请求处理速度变慢 ;
二、代码
2.1先来看经典代理的实现例子
通过接口和工厂实现
接口(抽象类)--销售代理接口(客户的需求)
package study.base.designPattern.proxy.normal; public interface Saler { public void sale(String thing); }
具体代理(实现客户需求/接口)--买书代理人
package study.base.designPattern.proxy.normal; public class BookSaler implements Saler { @Override public void sale(String thing) { System.out.println("........,嗯嗯,啊啊,汪汪!来一来,看一看["+thing+"],一次销售,终生保用"); } }
客户调用代理
package study.base.designPattern.proxy.normal; /** * 体力有限的销售代码 * @author lzfto * */ public class SalerProxy implements Saler { private Saler saler; private int power; public SalerProxy() { saler=new BookSaler(); power=100; } private void rest() { System.out.println("体力不济,累了。请下次再来!"); this.power+=5; if (power>100) { power=100; } } @Override public void sale(String thing) { if (power<30) { this.rest(); } else { saler.sale(thing); power--; } } public static void main(String[] args) { SalerProxy proxy=new SalerProxy(); for (int i=0;i<100;i++) { proxy.sale("大米"); } } }
这几个代码实现了代理的根本思路:客户呼叫一个特定代理,由代理实现具体的功能(卖书)。
这种实现起来其实很像适配器、装饰器模式。
关于这个问题,其实也是很多人的疑惑:https://zhuanlan.zhihu.com/p/296319765
但这个问题,这个链接说得好像也不是太清晰,但这个都不是重点。重点是实际应用的时候,再仔细分析下即可。
---
这个代码例子并不能说服我们一定要去使用代理模式去间接调用SalerProxy,我们肯定有疑问:为什么不能直接使用BookSaler了?
所以,这个经典的代码中只能得到这样的知识:代理代码编写方式,优点类似装饰器或者适配器模式。 暂时还没有看到它独有的优点。
2.2 java代理功能实现的例子
java本身提供了代理工具来帮助实现稍微复杂一些的功能,这个工具就是InvocationHandler+Proxy
package study.base.designPattern.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ItManageProxy implements InvocationHandler { private Object itManage; public ItManageProxy(Object itManage){ this.itManage=itManage; } public ItManageProxy(){ } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (itManage==null){ itManage=new ItManageImpl(); } Object obj=method.invoke(itManage, args); return obj; } }
package study.base.designPattern.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class ItManageTest { public static void main(String[] args) { ItManage itManage = new ItManageImpl(); InvocationHandler handler = new ItManageProxy(itManage); ItManage test = (ItManage) Proxy.newProxyInstance(handler.getClass().getClassLoader(), itManage.getClass().getInterfaces(), handler); test.encourage("新成员", "认真工作"); String poet = test.getName(); System.out.println(poet); } }
这个工具的好处在于,不要我们自己分析接口方法的参数类型,因为这个newProxyInstance可以自动分析。
这个例子只能看到一个好处:newProxy简化了代理编码。 依然没有看到什么独有的优点。
2.3 spring的Aop代理
网络上有非常棒的文章,关于aop实现部分的源码:
https://juejin.cn/post/7153214385236738055
所以本小节不再班门弄斧,仅仅是做个搬砖工。
aop工作流程:
这个过程和spring的bean工厂、服务器分发器的实现没有太大的区别。特别注意的是,必须和spring的benn工厂结合起来使用。
对于其它网文的关注到上图为止,以下是本人的需要强调的代理实现。
-----------------------------------------------------------------------------------------------------------------------------------------------
--
---------------------------------------------------------------------------
由于本文主要是讨论设计模式,所以这里只需要关注代理有关部分的代码即可。
如前,我们知道aop的实现根据目标对象的不同而有不同的实现:jdk代理实现和cglib创建实例实现。 而代理发生再jdk代理中。
无论哪一种aop被代理对象的实现,都是基于 org.springframework.aop.framework.AopProxy
以下是AopProxy的类层次图:
看下org.springframework.aop.framework.JdkDynamicAopProxy代码:
/* * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.aop.framework; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.AopInvocationException; import org.springframework.aop.RawTargetAccess; import org.springframework.aop.TargetSource; import org.springframework.aop.support.AopUtils; import org.springframework.core.DecoratingProxy; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * JDK-based {@link AopProxy} implementation for the Spring AOP framework, * based on JDK {@link java.lang.reflect.Proxy dynamic proxies}. * * <p>Creates a dynamic proxy, implementing the interfaces exposed by * the AopProxy. Dynamic proxies <i>cannot</i> be used to proxy methods * defined in classes, rather than interfaces. * * <p>Objects of this type should be obtained through proxy factories, * configured by an {@link AdvisedSupport} class. This class is internal * to Spring's AOP framework and need not be used directly by client code. * * <p>Proxies created using this class will be thread-safe if the * underlying (target) class is thread-safe. * * <p>Proxies are serializable so long as all Advisors (including Advices * and Pointcuts) and the TargetSource are serializable. * * @author Rod Johnson * @author Juergen Hoeller * @author Rob Harrop * @author Dave Syer * @author Sergey Tsypanov * @see java.lang.reflect.Proxy * @see AdvisedSupport * @see ProxyFactory */ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { /** use serialVersionUID from Spring 1.2 for interoperability. */ private static final long serialVersionUID = 5531744639992436476L; /* * NOTE: We could avoid the code duplication between this class and the CGLIB * proxies by refactoring "invoke" into a template method. However, this approach * adds at least 10% performance overhead versus a copy-paste solution, so we sacrifice * elegance for performance. (We have a good test suite to ensure that the different * proxies behave the same :-) * This way, we can also more easily take advantage of minor optimizations in each class. */ /** We use a static Log to avoid serialization issues. */ private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); /** Config used to configure this proxy. */ private final AdvisedSupport advised; private final Class<?>[] proxiedInterfaces; /** * Is the {@link #equals} method defined on the proxied interfaces? */ private boolean equalsDefined; /** * Is the {@link #hashCode} method defined on the proxied interfaces? */ private boolean hashCodeDefined; /** * Construct a new JdkDynamicAopProxy for the given AOP configuration. * @param config the AOP configuration as AdvisedSupport object * @throws AopConfigException if the config is invalid. We try to throw an informative * exception in this case, rather than let a mysterious failure happen later. */ public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { Assert.notNull(config, "AdvisedSupport must not be null"); if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { throw new AopConfigException("No advisors and no TargetSource specified"); } this.advised = config; this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces); } @Override public Object getProxy() { return getProxy(ClassUtils.getDefaultClassLoader()); } @Override public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); } return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this); } /** * Finds any {@link #equals} or {@link #hashCode} method that may be defined * on the supplied set of interfaces. * @param proxiedInterfaces the interfaces to introspect */ private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) { for (Class<?> proxiedInterface : proxiedInterfaces) { Method[] methods = proxiedInterface.getDeclaredMethods(); for (Method method : methods) { if (AopUtils.isEqualsMethod(method)) { this.equalsDefined = true; } if (AopUtils.isHashCodeMethod(method)) { this.hashCodeDefined = true; } if (this.equalsDefined && this.hashCodeDefined) { return; } } } } /** * Implementation of {@code InvocationHandler.invoke}. * <p>Callers will see exactly the exception thrown by the target, * unless a hook method throws an exception. */ @Override @Nullable public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Object target = null; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return equals(args[0]); } else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } else if (method.getDeclaringClass() == DecoratingProxy.class) { // There is only getDecoratedClass() declared -> dispatch to proxy config. return AopProxyUtils.ultimateTargetClass(this.advised); } else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // Get as late as possible to minimize the time we "own" the target, // in case it comes from a pool. target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null); // Get the interception chain for this method. List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct // reflective invocation of the target, and avoid creating a MethodInvocation. if (chain.isEmpty()) { // We can skip creating a MethodInvocation: just invoke the target directly // Note that the final invoker must be an InvokerInterceptor so we know it does // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { // We need to create a method invocation... MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); } // Massage return value if necessary. Class<?> returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned "this" and the return type of the method // is type-compatible. Note that we can't help if the target sets // a reference to itself in another returned object. 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()) { // Must have come from TargetSource. targetSource.releaseTarget(target); } if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } } /** * Equality means interfaces, advisors and TargetSource are equal. * <p>The compared object may be a JdkDynamicAopProxy instance itself * or a dynamic proxy wrapping a JdkDynamicAopProxy instance. */ @Override public boolean equals(@Nullable Object other) { if (other == this) { return true; } if (other == null) { return false; } JdkDynamicAopProxy otherProxy; if (other instanceof JdkDynamicAopProxy) { otherProxy = (JdkDynamicAopProxy) other; } else if (Proxy.isProxyClass(other.getClass())) { InvocationHandler ih = Proxy.getInvocationHandler(other); if (!(ih instanceof JdkDynamicAopProxy)) { return false; } otherProxy = (JdkDynamicAopProxy) ih; } else { // Not a valid comparison... return false; } // If we get here, otherProxy is the other AopProxy. return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised); } /** * Proxy uses the hash code of the TargetSource. */ @Override public int hashCode() { return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode(); } }
重点就是:
@Override public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); } return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this); }
重点语句:Proxy.newProxyInstance
再看看执行aop中原来的方法的有关代码:
以下是org.springframework.aop.framework.ReflectiveMethodInvocation的proceed()方法,执行目标对象原方法。
@Override @Nullable public Object proceed() throws Throwable { // We start with an index of -1 and increment early. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher dm) { // Evaluate dynamic method matcher here: static part will already have // been evaluated and found to match. Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass()); if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) { return dm.interceptor.invoke(this); } else { // Dynamic matching failed. // Skip this interceptor and invoke the next in the chain. return proceed(); } } else { // It's an interceptor, so we just invoke it: The pointcut will have // been evaluated statically before this object was constructed. return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } }
2.4 aop为什么用代理实现
前面我们提到设计的时候为什么要代理,这一般是因为:
不方便接触目标、保持灵活、增强
而代理可以实现aop的几个要求:
- aop通过代理可以动态访问成千上万的对象,而不需要写无数的if else之类的直接接触目标
- aop能够访问所有符合规范的目标对象,足够灵活,只要设计者按照规范来设计代码
- 增强-aop是典型的增强(通产是增强)
如果不用代理,能不能实现aop呢?可以的,例如Cglib,但这可以看作另外一种代理。
小结:spring的伟大是基于bean工厂,基于ioc。
在bean工厂的基础上,通过设置无数的门卡实现各种各样的功能。
基于java的注解,反射和设计模式。
话说回来,如果不会反射进行适当的提升,那么spring的aop的想能还是很一般般的。
所以,如果基于spring的设计程序,那么尽量不要用于需要太高性能的环节,此外应该尽量不要用aop。
想象下每辆在高速路商行驶的车都要停下来检查下,车能开得快吗?