首页 > 其他分享 >spring与设计模式之三代理模式

spring与设计模式之三代理模式

时间:2024-01-13 20:57:33浏览次数:28  
标签:return spring 之三 Object 代理 proxy aop 设计模式 method

部分内容引用:

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)

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

著名的代理模式例子为引用计数(英语:reference counting)指针对象。

当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少存储器用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。

而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。 [1]

组成:

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用  

也就是说在程序设计中,代理起到的作用和现实生活是类似的,使用的原因也是类似的:不方便或者不能,并且可以基于代理实现一些稍微复杂的功能。

 

1.2优点

 (1).职责清晰

真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。 (2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。 (3).高扩展性 [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。

想象下每辆在高速路商行驶的车都要停下来检查下,车能开得快吗?

 

 

 

 

  

 

标签:return,spring,之三,Object,代理,proxy,aop,设计模式,method
From: https://www.cnblogs.com/lzfhope/p/17959289

相关文章

  • Spring-Boot框架的组件注册和条件注解------Spring-Boot框架
    packagecom.example.boot3.Config;//importcom.alibaba.druid.FastsqlException;importcom.example.boot3.bean.User;importorg.springframework.boot.SpringBootConfiguration;importorg.springframework.context.annotation.Bean;importorg.springframework.co......
  • TCP之三次握手四次挥手与UDP区别
    目录1TCP三次握手四次挥手1.1数据包说明1.1.1TCP数据包1.1.2UDP数据包1.1.3TCP和UDP差异1.1.4TCP可靠性传输机制1.2三次握手1.2.1三次握手定义1.2.2三次握手问题1.2.2.1问题引入分析1.2.2.2历史连接1.2.2.3同步双方初始序列号1.2.2.4避免资源浪费1.3四次挥手1TCP......
  • 设计模式之中介者模式
    1.定义多个对象之间通过一个中介者对象进行通信和协作,而不是直接相互交互2.口语化表述中介,这在生活中很常见,比如租房中介通常,有住房出租的房东有很多,需要租房的租客也很多,但是租客难以直接联系房东,这个时候租房中介这个职业就出现了房东将房屋登记到中介这里,租客来中介这里......
  • SpringCache教程
    前言极大地简化了程序员对于cache的操作流程,可以使用拒接方便的实现,支持redis,Caffeine,Memcached等不同的缓存框架。相关注解名称解释@Cacheable主要针对方法配置,能够根据方法的请求参数对其进行缓存@CacheEvict清空缓存@CachePut保证方法被调用,又希望结果......
  • springboot mybatis postgres 对于json类型的字段转换
    在SpringBoot与MyBatis结合使用时,处理PostgreSQL中的JSON类型字段的转换可以分为以下步骤:自定义TypeHandler:为了在Java实体类与数据库的JSON类型字段之间进行转换,需要创建一个自定义的 TypeHandler。例如,针对JSONObject类型的转换器可以这样实现:importorg.apache.ibatis.type.B......
  • 第二章 Spring Boot 整合 Kafka消息队列 生产者
    ​ 系列文章目录第一章Kafka配置部署及SASL_PLAINTEXT安全认证第二章  SpringBoot整合Kafka消息队列 生产者第三章  SpringBoot整合Kafka消息队列 消息者(待续) 前言        Kafka是一个消息队列产品,基于Topicpartitions的设计,能达到非常高的消息......
  • Spring表达式语言(SPEL)学习(03)
    rootObject在表达式中直接写name和getName(),这时候Expression是无法解析的,因为其不知道name和getName()对应什么意思@Testpublicvoidtest06(){ ExpressionParserparser=newSpelExpressionParser(); parser.parseExpression("name").getValue(); parser.parseExpre......
  • Spring事务状态处理
    Spring事务提交后执行:深入理解和实践在Java开发中,Spring框架的事务管理是一个核心概念,尤其是在企业级应用中。理解和正确使用Spring事务对于保证应用的数据一致性和稳定性至关重要。本文将深入探讨在Spring中事务提交后的执行逻辑,这是一个经常被忽视但又极为重要的部分。事务的......
  • Spring踩坑
    测试默认测java对应层级下的东西如果test和java层级不在对应位置可以手动写Test的路径来更改@Component=@Repository@Service@Controller对于WebMvcConfigurer加不加EnableWebMvc都可以对于WebMvcConfigurationSupport不能加EnableWebMvc总结:以后都不加EnableWe......
  • 手拉手Springboot获取yml配置文件信息
    环境介绍技术栈springboot3软件版本mysql8IDEAIntelliJIDEA2022.2.1JDK17SpringBoot3.1.7配置文件说明:启动配置文件优先级:properties高于yml配置文件application.ymlyml是JSON的超集,简洁而强大,是一种专门用来书写配置文件的语言,可以替代application.properties。在创建一个S......