精华内容
下载资源
问答
  • 深入理解代理模式静态代理与JDK动态代理

    万次阅读 多人点赞 2018-03-01 00:22:11
     代理模式为其他对象提供了一种代理以控制对这个对象的访问,具体实现包括两大类:静态代理和动态代理。Java动态代理机制的出现使得Java开发人员只需要简单地指定一组接口及委托类对象便能动态地获得代理类,并且其...

    摘要:
      
      代理模式为其他对象提供了一种代理以控制对这个对象的访问,具体实现包括两大类:静态代理和动态代理。Java动态代理机制的出现使得Java开发人员只需要简单地指定一组接口及委托类对象便能动态地获得代理类,并且其所生成的代理类在将所有的方法调用分派到委托对象上反射执行的同时,还可以对方法进行增强,这也正是Spring AOP的实现基础。通过阅读本文,读者将会对代理模式和Java动态代理机制有更加深入的理解。


    版权声明:

    本文原创作者:书呆子Rico
    作者博客地址:http://blog.csdn.net/justloveyou_/


    一. 代理模式

    1、简介

      代理模式是一种常用的设计模式,在AOP、RPC等诸多框架中均有它的身影。根据代理类的创建时机和创建方式的不同,可以将其分为静态代理和动态代理两种形式:在程序运行前就已经存在的编译好的代理类是为静态代理,在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能是为动态代理。代理模式的目的就是为真实业务对象提供一个代理对象以控制对真实业务对象的访问,代理对象的作用有:

    • 代理对象存在的价值主要用于拦截对真实业务对象的访问;

    • 代理对象具有和目标对象(真实业务对象)实现共同的接口或继承于同一个类;

    • 代理对象是对目标对象的增强,以便对消息进行预处理和后处理。


    2、定义与结构

      定义:为其他对象提供一种代理以控制对这个对象的访问。

                    这里写图片描述

      代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色,Proxied)以及代理类角色(Proxy),如上图所示:

    • 抽象主题角色:可以是接口,也可以是抽象类;
    • 委托类角色:真实主题角色,业务逻辑的具体执行者;
    • 代理类角色:内部含有对真实对象RealSubject的引用,负责对真实主题角色的调用,并在真实主题角色处理前后做预处理和后处理。

    3、静态代理实现

      静态代理是代理模式的实现方式之一,是相对于动态代理而言的。所谓静态代理是指,在程序运行前,由程序员创建或特定工具自动生成源代码并对其编译生成.class文件。静态代理的实现只需要三步:首先,定义业务接口;其次,实现业务接口;然后,定义代理类并实现业务接口;最后便可通过客户端进行调用。例如,

    • 抽象主题角色:HelloService 接口
    public interface HelloService {
    
        String hello(String name);
    
        String hi(String msg);
    }
    • 委托类角色: HelloServiceImpl类
    public class HelloServiceImpl implements HelloService{
        @Override
        public String hello(String name) {
            return "Hello " + name;
        }
    
        @Override
        public String hi(String msg) {
            return "Hi, " + msg;
        }
    }
    • 代理类角色: HelloServiceProxy类
    public class HelloServiceProxy implements HelloService {
    
        private HelloService helloService;
    
        public HelloServiceProxy(HelloService helloService) {
            this.helloService = helloService;
        }
    
        @Override
        public String hello(String name) {
            System.out.println("预处理...");
            String result = helloService.hello(name);
            System.out.println(result);
            System.out.println("后处理...");
            return result;
        }
    
        @Override
        public String hi(String msg) {
            System.out.println("预处理...");
            String result = helloService.hi(msg);
            System.out.println(result);
            System.out.println("后处理...");
            return result;
        }
    }
    • 客户端:
    public class Main {
        public static void main(String[] args){
            HelloService helloService = new HelloServiceImpl();
            HelloServiceProxy helloServiceProxy = new HelloServiceProxy(helloService);
            helloServiceProxy.hello("Panda");
            helloServiceProxy.hi("Panda");
        }
    }/** Output
     预处理...
    Hello Panda
    后处理...
    预处理...
    Hi, Panda
    后处理...
    **/

    4、代理模式与软件设计原则

      代理类不仅是一个隔离客户端和委托类的中介,还可以通过代理类在不修改原有代码的前提下增加一些新功能,是开闭原则(Open for Extension, Closed for Modification)最典型的实践。

      代理类可以为委托类预处理消息、过滤消息、把消息转发给委托类以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法来提供特定的服务。

      也就是说,真正的业务功能还是由委托类来实现,但是在实现业务功能前后可以增加一些公共逻辑,用于增强业务功能。例如,在项目前期开发中我们没有加入缓存、日志等这些功能,后期若想加入,我们就可以使用代理来实现,而且不必对原有代码进行改动。因此,代理模式是对开闭原则的典型实践,也是AOP理念的实现基础。


    二.JDK 动态代理

    1、动态代理引入

      对代理模式而言,一般来说,具体主题类与其代理类是一一对应的,这也是静态代理的特点。但是,也存在这样的情况:有N个主题类,但是代理类中的“预处理、后处理”都是相同的,仅仅是调用主题不同。那么,若采用静态代理,那么必然需要手动创建N个代理类,这显然让人相当不爽。动态代理则可以简单地为各个主题类分别生成代理类,共享“预处理,后处理”功能,这样可以大大减小程序规模,这也是动态代理的一大亮点。

      在动态代理中,代理类是在运行时期生成的。因此,相比静态代理,动态代理可以很方便地对委托类的相关方法进行统一增强处理,如添加方法调用次数、添加日志功能等等。动态代理主要分为JDK动态代理和cglib动态代理两大类,本文主要对JDK动态代理进行探讨。


    2、JDK动态代理机制的相关类/接口

      要想使用JDK动态代理,首先需要了解其相关的类或接口:

    • java.lang.reflect.Proxy:该类用于动态生成代理类,只需传入目标接口、目标接口的类加载器以及InvocationHandler便可为目标接口生成代理类及代理对象。
    // 方法 1: 该方法用于获取指定代理对象所关联的InvocationHandler
    static InvocationHandler getInvocationHandler(Object proxy) 
    
    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
    
    // 方法 3:该方法用于判断指定类是否是一个动态代理类
    static boolean isProxyClass(Class cl) 
    
    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
        InvocationHandler h)

    • java.lang.reflect.InvocationHandler:该接口包含一个invoke方法,通过该方法实现对委托类的代理的访问,是代理类完整逻辑的集中体现,包括要切入的增强逻辑和进行反射执行的真实业务逻辑。
    // 该方法代理类完整逻辑的集中体现。第一个参数既是代理类实例,第二个参数是被调用的方法对象,
    // 第三个方法是调用参数。通常通过反射完成对具体角色业务逻辑的调用,并对其进行增强。
    Object invoke(Object proxy, Method method, Object[] args)
    • java.lang.ClassLoader:类加载器类,负责将类的字节码装载到Java虚拟机中并为其定义类对象,然后该类才能被使用。Proxy静态方法生成动态代理类同样需要通过类加载器来进行加载才能使用,它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的而非预存在于任何一个.class 文件中。

    3、JDK动态代理使用步骤

      JDK动态代理的一般步骤如下:

    • 创建被代理的接口和类;

    • 实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;

    • 调用Proxy的静态方法,创建代理类并生成相应的代理对象;

    • 使用代理。


    (1).创建被代理的接口和类

    // 抽象主题角色
    public interface HelloService {
    
        String hello(String name);
    
        String hi(String msg);
    }
    
    // 具体(真实)主题角色
    public class HelloServiceImpl implements HelloService{
        @Override
        public String hello(String name) {
            return "Hello " + name;
        }
    
        @Override
        public String hi(String msg) {
            return "Hi, " + msg;
        }
    }

    (2).实现InvocationHandler接口

    public class MyInvocationHandler implements InvocationHandler{
    
        // 真实业务对象
        private Object target;
    
        public MyInvocationHandler(Object target){
            this.target = target;
        }
    
        /**
         * Processes a method invocation on a proxy instance and returns
         * the result.  This method will be invoked on an invocation handler
         * when a method is invoked on a proxy instance that it is
         * associated with.
         *
         * @param proxy  the proxy instance that the method was invoked on
         * @param method the {@code Method} instance corresponding to
         *               the interface method invoked on the proxy instance.  The declaring
         *               class of the {@code Method} object will be the interface that
         *               the method was declared in, which may be a superinterface of the
         *               proxy interface that the proxy class inherits the method through.
         * @param args   an array of objects containing the values of the
         *               arguments passed in the method invocation on the proxy instance,
         *               or {@code null} if interface method takes no arguments.
         *               Arguments of primitive types are wrapped in instances of the
         *               appropriate primitive wrapper class, such as
         *               {@code java.lang.Integer} or {@code java.lang.Boolean}.
         * @return the value to return from the method invocation on the
         * proxy instance.  If the declared return type of the interface
         * method is a primitive type, then the value returned by
         * this method must be an instance of the corresponding primitive
         * wrapper class; otherwise, it must be a type assignable to the
         * declared return type.  If the value returned by this method is
         * {@code null} and the interface method's return type is
         * primitive, then a {@code NullPointerException} will be
         * thrown by the method invocation on the proxy instance.  If the
         * value returned by this method is otherwise not compatible with
         * the interface method's declared return type as described above,
         * a {@code ClassCastException} will be thrown by the method
         * invocation on the proxy instance.
         * @throws Throwable the exception to throw from the method
         * invocation on the proxy instance.  The exception's type must be
         * assignable either to any of the exception types declared in the
         * {@code throws} clause of the interface method or to the
         * unchecked exception types {@code java.lang.RuntimeException}
         * or {@code java.lang.Error}.  If a checked exception is
         * thrown by this method that is not assignable to any of the
         * exception types declared in the {@code throws} clause of
         * the interface method, then an
         * {@link UndeclaredThrowableException} containing the
         * exception that was thrown by this method will be thrown by the
         * method invocation on the proxy instance.
         * @see UndeclaredThrowableException
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 增强逻辑
            System.out.println("PROXY : " + proxy.getClass().getName());
    
            // 反射调用,目标方法
            Object result = method.invoke(target, args);
    
            // 增强逻辑
            System.out.println(method.getName() + " : " + result);
    
            return result;
        }
    }

    (3). 创建代理类并生成相应的代理对象

    // 生成代理类的class对象
    Class<?> clazz = Proxy.getProxyClass(helloService.getClass().getClassLoader(), helloService
                .getClass().getInterfaces());
    // 创建InvocationHandler
    InvocationHandler myInvocationHandler = new MyInvocationHandler(helloService);
    // 获取代理类的构造器对象
    Constructor constructor = clazz.getConstructor(new Class[] {InvocationHandler.class});
    // 反射创建代理对象
    HelloService proxy = (HelloService)constructor.newInstance(myInvocationHandler);

    也可以一步到位,

    HelloService proxy = (HelloService)Proxy.newProxyInstance(HelloService.class.getClassLoader(),
    helloService.getClass().getInterfaces(), new MyInvocationHandler(helloService));
    

    (4).使用代理

    proxy.hello("rico");
    proxy.hi("panda");
    
    /** Output
    PROXY : com.sun.proxy.$Proxy0
    hello : Hello rico
    PROXY : com.sun.proxy.$Proxy0
    hi : Hi, panda
    **/

    三.JDK动态代理原理与源码

      代理类与代理对象的生成是由Proxy的newProxyInstance()方法来完成的。因此,我们先来看一下newProxyInstance()的实现:

    /**
         * Returns an instance of a proxy class for the specified interfaces
         * that dispatches method invocations to the specified invocation
         * handler.
         *
         * <p>{@code Proxy.newProxyInstance} throws
         * {@code IllegalArgumentException} for the same reasons that
         * {@code Proxy.getProxyClass} does.
         *
         * @param   loader the class loader to define the proxy class
         * @param   interfaces the list of interfaces for the proxy class
         *          to implement
         * @param   h the invocation handler to dispatch method invocations to
         * @return  a proxy instance with the specified invocation handler of a
         *          proxy class that is defined by the specified class loader
         *          and that implements the specified interfaces
         * @throws  IllegalArgumentException if any of the restrictions on the
         *          parameters that may be passed to {@code getProxyClass}
         *          are violated
         * @throws  SecurityException if a security manager, <em>s</em>, is present
         *          and any of the following conditions is met:
         *          <ul>
         *          <li> the given {@code loader} is {@code null} and
         *               the caller's class loader is not {@code null} and the
         *               invocation of {@link SecurityManager#checkPermission
         *               s.checkPermission} with
         *               {@code RuntimePermission("getClassLoader")} permission
         *               denies access;</li>
         *          <li> for each proxy interface, {@code intf},
         *               the caller's class loader is not the same as or an
         *               ancestor of the class loader for {@code intf} and
         *               invocation of {@link SecurityManager#checkPackageAccess
         *               s.checkPackageAccess()} denies access to {@code intf};</li>
         *          <li> any of the given proxy interfaces is non-public and the
         *               caller class is not in the same {@linkplain Package runtime package}
         *               as the non-public interface and the invocation of
         *               {@link SecurityManager#checkPermission s.checkPermission} with
         *               {@code ReflectPermission("newProxyInPackage.{package name}")}
         *               permission denies access.</li>
         *          </ul>
         * @throws  NullPointerException if the {@code interfaces} array
         *          argument or any of its elements are {@code null}, or
         *          if the invocation handler, {@code h}, is
         *          {@code null}
         */
        @CallerSensitive
        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException {
    
            // (Objects工具类)检查指定类型的对象引用不为空null。当参数为null时,抛出空指针异常。设计这个方法主要是为了在方法、构造函数中做参数校验。
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    
                // Class<?>[] constructorParams ={ InvocationHandler.class };
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                // 利用构造器对象反射生成代理对象
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }

      继续跟进getProxyClass0()方法,

    /**
         * Generate a proxy class.  Must call the checkProxyAccess method
         * to perform permission checks before calling this.
         */
        private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
    
            // 实现接口数应小于65535                                
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
    
            // If the proxy class defined by the given loader implementing
            // the given interfaces exists, this will simply return the cached copy;
            // otherwise, it will create the proxy class via the ProxyClassFactory
             如果代理类创建,则返回缓存中的该类的副本;否则通过ProxyClassFactory创建代理类 
            return proxyClassCache.get(loader, interfaces);
        }

      到此为止还是没有看到如何生成代理类的,只知道代理类是从proxyClassCache中取得的,这个变量是与缓存相关的一个对象,查看该变量的声明与初始化:

        /**
         * a cache of proxy classes
         */
        private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

      继续跟进ProxyClassFactory类,

        /**
         * A factory function that generates, defines and returns the proxy class given
         * the ClassLoader and array of interfaces.
         * 根据给定的类加载器和接口数组生成代理类的工厂类 
         */
        private static final class ProxyClassFactory
            implements BiFunction<ClassLoader, Class<?>[], Class<?>>
        {
            // prefix for all proxy class names
            private static final String proxyClassNamePrefix = "$Proxy";
    
            // next number to use for generation of unique proxy class names
            private static final AtomicLong nextUniqueNumber = new AtomicLong();
    
            @Override
            public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
                Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
                for (Class<?> intf : interfaces) {
                    /*
                     * Verify that the class loader resolves the name of this
                     * interface to the same Class object.
                     */
                    Class<?> interfaceClass = null;
                    try {
                        interfaceClass = Class.forName(intf.getName(), false, loader);
                    } catch (ClassNotFoundException e) {
                    }
                    if (interfaceClass != intf) {
                        throw new IllegalArgumentException(
                            intf + " is not visible from class loader");
                    }
                    /*
                     * Verify that the Class object actually represents an
                     * interface.
                     */
                    if (!interfaceClass.isInterface()) {
                        throw new IllegalArgumentException(
                            interfaceClass.getName() + " is not an interface");
                    }
                    /*
                     * Verify that this interface is not a duplicate.
                     */
                    if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                        throw new IllegalArgumentException(
                            "repeated interface: " + interfaceClass.getName());
                    }
                }
    
                String proxyPkg = null;     // package to define proxy class in
                int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    
                /*
                 * Record the package of a non-public proxy interface so that the
                 * proxy class will be defined in the same package.  Verify that
                 * all non-public proxy interfaces are in the same package.
                 */
                for (Class<?> intf : interfaces) {
                    int flags = intf.getModifiers();
                    if (!Modifier.isPublic(flags)) {
                        accessFlags = Modifier.FINAL;
                        String name = intf.getName();
                        int n = name.lastIndexOf('.');
                        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                        if (proxyPkg == null) {
                            proxyPkg = pkg;
                        } else if (!pkg.equals(proxyPkg)) {
                            throw new IllegalArgumentException(
                                "non-public interfaces from different packages");
                        }
                    }
                }
    
                if (proxyPkg == null) {
                    // if no non-public proxy interfaces, use com.sun.proxy package
                    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
                }
    
                /*
                 * Choose a name for the proxy class to generate.
                 */
                long num = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
                /*
                 * Generate the specified proxy class.
                 * 生成代理类字节码
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
                try {
    
                    // 加载生成class对象
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    /*
                     * A ClassFormatError here means that (barring bugs in the
                     * proxy class generation code) there was some other
                     * invalid aspect of the arguments supplied to the proxy
                     * class creation (such as virtual machine limitations
                     * exceeded).
                     */
                    throw new IllegalArgumentException(e.toString());
                }
            }
        }

      由ProxyClassFactory类可以知道产生代理类的具体逻辑大致上是,根据传递的被代理类及其实现的接口生成代理类的字节码,然后进行加载并生成对应的Class对象。


      对于JDK动态代理,在运行时我们可以设置JVM参数(-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true)来得到动态生成 的class文件,然后通过 Java Decompiler 反编译上述得到的class文件,有:

    package com.sun.proxy;
    
    import com.youku.zixu.api.HelloService;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    // JDK动态代理生成的代理类继承于父类Proxy
    public final class $Proxy0 extends Proxy
      implements HelloService {
    
      private static Method m1;
      private static Method m3;
      private static Method m2;
      private static Method m4;
      private static Method m0;
    
      // 构造函数
      public $Proxy0(InvocationHandler paramInvocationHandler){
        super(paramInvocationHandler);
      }
    
      public final boolean equals(Object paramObject){
        try{
          return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
        }catch (Error|RuntimeException localError){
          throw localError;
        }catch (Throwable localThrowable){
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      public final String hi(String paramString){
        try{
          return (String)this.h.invoke(this, m3, new Object[] { paramString });
        }catch (Error|RuntimeException localError){
          throw localError;
        }catch (Throwable localThrowable){
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      public final String toString(){
        try{
          return (String)this.h.invoke(this, m2, null);
        }catch (Error|RuntimeException localError){
          throw localError;
        }catch (Throwable localThrowable){
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      public final String hello(String paramString){
        try{
          return (String)this.h.invoke(this, m4, new Object[] { paramString });
        }catch (Error|RuntimeException localError){
          throw localError;
        }catch (Throwable localThrowable){
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      public final int hashCode(){
        try{
          return ((Integer)this.h.invoke(this, m0, null)).intValue();
        }catch (Error|RuntimeException localError){
          throw localError;
        }catch (Throwable localThrowable){
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      static{
        try{
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m3 = Class.forName("com.youku.zixu.api.HelloService").getMethod("hi", new Class[] { Class.forName("java.lang.String") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m4 = Class.forName("com.youku.zixu.api.HelloService").getMethod("hello", new Class[] { Class.forName("java.lang.String") });
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          return;
        }catch (NoSuchMethodException localNoSuchMethodException){
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }catch (ClassNotFoundException localClassNotFoundException){
          throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
      }
    }

      由上述反编译结果我们可以知道:

    • JDK动态代理生成的代理类继承了Proxy类,这正是JDK动态代理只能实现接口代理而不能实现类代理的原因,即Java不允许多继承,而动态代理生成的代理类本身就已经继承了Proxy类;

    • JDK动态代理生成的代理类也代理了三个Object类的方法:equals()方法、hashCode()方法和toString()方法;


    四、小结

      1、实现动态代理的关键技术是反射;

      2、代理对象是对目标对象的增强,以便对消息进行预处理和后处理;

      3、InvocationHandler中的invoke()方法是代理类完整逻辑的集中体现,包括要切入的增强逻辑和进行反射执行的真实业务逻辑;

      4、使用JDK动态代理机制为某一真实业务对象生成代理,只需要指定目标接口、目标接口的类加载器以及具体的InvocationHandler即可。

      5、JDK动态代理的典型应用包括但不仅限于AOP、RPC、Struts2、Spring等重要经典框架。


    引用:

    Java设计模式——代理模式实现及原理
    Java Decompiler
    Java 动态代理机制分析及扩展,第 1 部分

    展开全文
  • 本文主要从三个方面介绍代理模式,什么是代理模式,提供了什么好处;代理模式的三种实现方式;三种代理的区别 首先简单说明下为什么需要代理模式:为其他对象提供一种代理以控制对这个对象的访问,可以隔离客户端和...

    本文主要从三个方面介绍代理模式,什么是代理模式,提供了什么好处;代理模式的三种实现方式;三种代理的区别

    首先简单说明下为什么需要代理模式:为其他对象提供一种代理以控制对这个对象的访问,可以隔离客户端和委托类的中介。我们还可以借助代理来在增加一些功能,而不需要修改原有代码。

    重点是代理模式的三种实现方式:

    先给出简单的接口和实现类:

    public interface IHello {
        void sayHello();
    }
    public final class Hello implements IHello{
        @Override
        public void sayHello() {
            System.out.println("hello");
    
        }
    }

    1静态代理模式

    public class StaticProxy {
        IHello hello;
        public StaticProxy(IHello hello){
            this.hello=hello;
        }
        public void syaHello(){
            System.out.println("before");
            hello.sayHello();
            System.out.println("after");
        }
        public static void  main(String  args[]){
            new StaticProxy(new Hello()).syaHello();
        }
    }

    输出为:

    before
    hello
    after

    2java动态代理实现的动态代理

    //java动态代理实现的动态代理
    public class DynamicProxy implements InvocationHandler {
        Object target;
        public DynamicProxy(Object target){
            this.target=target;
        }
        // 此处生成接口的实现类,所以需要有接口,无法代理未实现接口的类
        public Object bind(){
            return Proxy.newProxyInstance(this.target.getClass().getClassLoader(),
                        this.target.getClass().getInterfaces(),this);
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before");
            method.invoke(this.target,args);
            System.out.println("after");
            return null;
        }
    
        public static void  main(String  args[]){
            //此处返回的必定是接口,不可以转为具体实体类,所以java反射要求被代理类必须实现了接口;但不要求final
           IHello hello = (IHello) new DynamicProxy(new Hello()).bind();
           hello.sayHello();
        }
    
    }

    java动态代理实现代理步骤:

    a定义被代理类:接口及接口实现类

    b定义代理类,代理类构造器传入被代理类,且需要实现InvocationHandler接口的类并重写invoke方法

    c生成被代理的类的实例:调用 Proxy.newProxyInstance(被代理的类.getClass().getClassLoader(),

                                                                                            被代理的类.getClass().getInterfaces(),

                                                                                             InvocationHandler的实现类);

                  注意:newProxyInstance返回的是接口类型,所以java动态代理要求被代理类实现接口。

    d被代理的类的实例调用需要执行的方法

    3cglib实现的动态代理

    maven先引入包:

    <dependency>

        <groupId>cglib</groupId>

        <artifactId>cglib</artifactId>

        <version>2.2.2</version>

    </dependency>

    public class CGLibProxy {
        public static void  main(String  args[]){
            Enhancer enhancer = new Enhancer();
            // 此处将目标类设置为父类,生成该类的子类来实现动态代理,所以如果此时将Hello类声明为final,则会报IllegalArgumentException;但不要求实现接口
            enhancer.setSuperclass(Hello.class);
            enhancer.setCallback(new MethodInterceptor(){
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    System.out.println("before");
                    Object result = methodProxy.invokeSuper(o,objects);
                    System.out.println("after");
                    return result;
                }
            });
            Hello hello = (Hello)enhancer.create();
            hello.sayHello();
        }
    }

    Ehancer介绍:

    Enhancer
    Enhancer可能是CGLIB中最常用的一个类,和Java1.3动态代理中引入的Proxy类差不多(如果对Proxy不懂,可以参考这里)。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。

    public class SampleClass {
        public String test(String input){
            return "hello world";
        }
    }

    下面我们将以这个类作为主要的测试类,来测试调用各种方法

    @Test
    public void testFixedValue(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallback(new FixedValue() {
            @Override
            public Object loadObject() throws Exception {
                return "Hello cglib";
            }
        });
        SampleClass proxy = (SampleClass) enhancer.create();
        System.out.println(proxy.test(null)); //拦截test,输出Hello cglib
        System.out.println(proxy.toString()); 
        System.out.println(proxy.getClass());
        System.out.println(proxy.hashCode());
    }

    程序的输出为:

    Hello cglib
    Hello cglib
    class com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7

    java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

        at com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7.hashCode(<generated>)
        ...

    上述代码中,FixedValue用来对所有拦截的方法返回相同的值,从输出我们可以看出来,Enhancer对非final方法test()、toString()、hashCode()进行了拦截,没有对getClass进行拦截。由于hashCode()方法需要返回一个Number,但是我们返回的是一个String,这解释了上面的程序中为什么会抛出异常。

    Enhancer.setSuperclass用来设置父类型,从toString方法可以看出,使用CGLIB生成的类为被代理类的一个子类,形如:SampleClass$$EnhancerByCGLIB$$e3ea9b7

    Enhancer.create(Object…)方法是用来创建增强对象的,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。(虽然类的构造放法只是Java字节码层面的函数,但是Enhancer却不能对其进行操作。Enhancer同样不能操作static或者final类)。我们也可以先使用Enhancer.createClass()来创建字节码(.class),然后用字节码动态的生成增强后的对象。
     

    原理上:

    Java动态代理通过创建接口的实现类来完成对目标对象的代理,使用Java原生的反射API进行操作,在生成类上比较高效;但不能代理未实现接口的类;

    CGLIB 在运行期间生成的是目标类扩展的子类,直接使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效;不能扩展final修饰的类或方法;

    使用上:

    Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为ProxyJava类继承机制不允许多重继承);CGLIB能够代理普通类,但是不能代理final修改时的类;

    java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

    1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 
    2
    、如果目标对象实现了接口,可以强制使用CGLIB实现AOP ,spring中配置proxy-target-class='true'
    3
    、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

    借鉴:https://blog.csdn.net/danchu/article/details/70238002?utm_source=copy 

    性能对比:

    关于两者之间的性能的话,JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。

    主要体现在如下的两个指标中:

    CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;

    但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;

    因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。
    原文链接:https://blog.csdn.net/qq_35190492/article/details/103795708


    什么时候应该使用动态代理?

    aop就是一个很好的例子,我执行所有方法都需要记录日志,是不是所有方法都需要修改增加记录日志的逻辑?使用动态代理就不需要,
    调用方法需要调用代理,代理除了调用方法会增加日志的逻辑,那么所有原方法就无须修改!!

    spring的动态代理是怎么实现的? 没有继承接口为什么也可以?
    若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
    优点:因为有接口,所以使系统更加松耦合
    缺点:为每一个目标类创建接口
    若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
    优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
    缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。

    展开全文
  • Java设计模式学习06——静态代理与动态代理

    万次阅读 多人点赞 2016-10-30 09:57:18
    一、代理模式为某个对象提供一个代理,从而控制这个代理的访问。代理类和委托类具有共同的父类或父接口,这样在任何使用委托类对象的地方都可以使用代理类对象替代。代理类负责请求的预处理、过滤、将请求分配给委托...

    一、代理模式


    为某个对象提供一个代理,从而控制这个代理的访问。代理类和委托类具有共同的父类或父接口,这样在任何使用委托类对象的地方都可以使用代理类对象替代。代理类负责请求的预处理、过滤、将请求分配给委托类处理、以及委托类处理完请求的后续处理。

    二、代理模式结构


    UML类图:

    这里写图片描述

    由上图代理模式的结构为:

    • 抽象角色: 真实对象和代理对象的共同接口。
    • 代理角色: 代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
    • 真实角色: 代理角色所代表的真实对象,是我们最终要引用的对象。

    根据代理类的生成时间不同可以将代理分为静态代理和动态代理两种。

    三、静态代理


    由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
    示例代码:
    1.抽象角色

    public interface AbstractSubject {
         void doSomething();
    }   
    

    2.代理角色

    public class ProxySubject implements AbstractSubject{
         private AbstractSubject  real ;
         public ProxySubject(AbstractSubject  real) {
             this.real=real ;
        }
         @Override
         public void doSomething() {
    
             real.doSomething();
        }
    
         public void doOtherthing() {
    
        }
    }   
    

    3.真实角色

    public class RealSubject implements AbstractSubject{
         @Override
         public void doSomething() {
            System.out.println( "真实角色被使用" );
        }
    }   
    

    4.客户端

    public class Client {
         public static void main(String[]  args ) {
            RealSubject real = new  RealSubject();
            ProxySubject proxy = new  ProxySubject( real );
             proxy.doSomething();
        }
    }   
    

    5.静态代理的优缺点

    优点: 业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
    缺点:

    • 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
    • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

    四、动态代理


    动态代理类的源码是程序在运行期间由JVM根据反射等机制动态生成的,所以不存在代理类的字节码文件。代理角色和真实角色的联系在程序运行时确定。
    1.首先看看和动态代理相关JavaAPI
    ① .java.lang.reflect.Proxy
    这是Java动态代理机制生成的所有代理类的父类,它提供了一组静态的方法来为一组接口动态的生成代理类及其对象。
    Proxy类的静态方法:

    //方法 1: 该方法用于获取指定代理对象所关联的调用处理器  
    static InvocationHandler getInvocationHandler(Object proxy )  
    
    //方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象  
    static Class getProxyClass(ClassLoader loader,Class[] interfaces)  
    
    //方法 3:该方法用于判断指定类对象是否是一个动态代理类  
    static boolean isProxyClass(Class cl )    
    
    
    //方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader,  Class[] interfaces, InvocationHandler  h )   
    

    ②.java.lang.reflect.InvocationHandler
    这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
    InvocationHandler核心方法

    //该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象  
    //第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行   
    Object invoke(Object proxy, Method  method,Object[] args )     

    ③ .java.lang.reflect.ClassLoader
      这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。每次生成动态代理类对象时都需要指定一个类装载器对象 。

    2.动态代理实现步骤

    • 实现InvocationHandler接口创建自己的调用处理器 。
    • 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类 。
    • 执行真实角色具体任务。

    示例代码

    1.抽象角色和真实角色代码与上述相同 。
    2. 创建自己的调用处理器:

    public class SubjectHandler implements InvocationHandler{
        AbstractSubject real;
        public SubjectHandler(AbstractSubject real){
            this.real=real;
        }
    
        @Override
        public Object invoke(Object obj, Method method, Object[] args)throws Throwable {
            System.out.println("代理类预处理任务");
            //利用反射机制将请求分派给委托类处理。Method的invoke返回Object对象作为方法执行结果。  
      //因为示例程序没有返回值,所以这里忽略了返回值处理
            method.invoke(real, args);
            System.out.println("代理类后续处理任务");
            return null;
        }
    
    }
    

    3.客户端 :

    public class Client {
    
        public static void main(String[] args) {
            RealSubject real=new RealSubject();
            SubjectHandler handler=new SubjectHandler(real);
            //生成代理类对象
            AbstractSubject proxy=(AbstractSubject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{AbstractSubject.class},handler);
            proxy.doSomething();
    
        }
    
    }

    4.动态代理的优缺点

    优点
      动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

    不足
      诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

    本文动态代理部分内容大量引自:http://layznet.iteye.com/blog/1182924

    展开全文
  • JAVA设计模式--代理模式静态

    万次阅读 2016-07-06 18:51:17
    二、静态代理模式的结构 三、静态代理模式应用举例 应用一 应用二 四、代理模式的应用场景 五、静态代理模式的特点 一、什么是代理模式 代理(Proxy)模式为其他对象提供一种代理以控制对这个对象的访问。...

    目录

    一、什么是代理模式

    二、静态代理模式的结构

    三、静态代理模式应用举例

    应用一

    应用二

    四、代理模式的应用场景

    五、静态代理模式的特点


    一、什么是代理模式

    代理(Proxy)模式为其他对象提供一种代理以控制对这个对象的访问。(原文:The Proxy Pattern Provide a surrogate or placeholder for another object to control access to it.)

    代理模式的作用:在客户端和被代理对象之间起到中介作用,通过代理可以有效地控制客户端对被代理对象的直接访问,进而可以很好地隐藏和保护被代理对象。

    代理模式按照其代理类生成方式的不同又可以分为静态代理和动态代理两种,本章先跟大家来聊聊静态代理。

    二、静态代理模式的结构

    代理模式是一种结构型设计模式,在代理模式中,代理与被代理的对象通常要实现相同的接口,以便在需要的时候可以使用代理来替代被代理对象,客户端必须通过代理与被代理对象进行交互,并且可以在交互的过程中(交互前后)添加一些额外的功能。

    静态代理模式的UML结构示意图如下。

    静态代理模式涉及的角色及其职责:

    抽象主题(Subject)角色:这是一个抽象角色,通常被定义为接口,真实主题角色和代理主题角色都需实现此接口,以便将来可以使用代理主题对象来替代真实主题对象。

    真实主题(RealSubject)角色:也叫被代理角色,是代理主题角色所代表的真实对象,是业务逻辑的实际执行者。

    代理主题(Proxy)角色:也叫代理角色,该角色内部含有对真实主题对象的引用,从而可以操作真实主题对象,同时代理对象提供与真实主题对象相同的接口以便在任何时刻都能代替真实主题对象。通常代理角色会在将客户端调用传递给真实主题对象之前或者之后执行某些操作,而不是单纯地将调用传递给真实主题对象。

    静态代理模式结构示意源代码如下:

    抽象主题(Subject)角色

    public interface Subject {
    
    	// 声明真实主题角色和代理主题角色都需实现的方法
    	public void operation();
    
    }

    真实主题(RealSubject)角色

    public class RealSubject implements Subject {
    
    	//实现了抽象主题角色定义的接口
    	@Override
    	public void operation() {
    		System.out.println("调用真实主题对象执行操作进行中...");
    	}
    
    }

    代理主题(Proxy)角色

    public class Proxy implements Subject {
    
    	//包含一个真实主题对象的引用
    	private Subject realSubject;
    
    	public Proxy(Subject realSubject) {
    		this.realSubject = realSubject;
    	}
    
    	public void before() {
    		System.out.println("调用真实主题对象之前进行的相关操作...");
    	}
    
    	public void after() {
    		System.out.println("调用真实主题对象之后进行的相关操作...");
    	}
    
    	//通过调用内部真实主题对象的引用实现了抽象主题角色定义的接口,并添加了一些额外处理功能
    	@Override
    	public void operation() {
    		before();
    		realSubject.operation();
    		after();
    	}
    
    }

    在MainClass中测一下:

    public class MainClass {
    
    	public static void main(String[] args) {
    		Subject realSubject=new RealSubject(); 
    		Proxy proxy= new Proxy(realSubject);
    		proxy.operation();
    	}
    
    }

    运行程序打印结果如下:

    调用真实主题对象之前进行的相关操作...
    调用真实主题对象执行操作进行中...
    调用真实主题对象之后进行的相关操作...

    从上面的例子可以看出代理对象将客户端的调用委派给被代理对象,并在调用被代理对象的方法之前和之后执行了一些特殊操作,此种模式与装饰模式有点相似,两者的区别在于:代理模式的目的在于控制访问,装饰模式的目的在于添加功能。

    PS:不理解代理模式与装饰模式之间区别?没关系,待我们讲完接下来数据库连接池的例子,相信你一定会有所领悟。

    三、静态代理模式应用举例

    在软件设计中,使用代理模式的意图很多,比如因为安全原因需要屏蔽客户端直接访问真实对象,或者在远程调用中需要使用代理类处理远程方法调用的技术细节 (如 RMI),也可能为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。

    应用一

    我们都知道,对于一个简单的数据库应用,由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。但是对于一个复杂的数据库应用,情况就完全不同了,频繁的建立、关闭数据库连接,会极大的降低系统的性能,此时,对于连接的使用就成了系统性能的瓶颈。

    由于存在以上弊病,我们一般都是通过使用数据库连接池来解决连接问题,即创造一堆等待被使用的连接,等到用的时候就从连接池里取一个,不用了再放回去,数据库连接在整个应用启动期间,几乎是不关闭的。

    但是在程序员编写程序的时候,会经常使用connection.close()这样的方法,去关闭数据库连接,而且这样做是对的,所以你并不能告诉程序员们说,你们使用连接都不要关了,去调用一个其他的类似方法将连接重新归还给连接池吧。这是不符合程序员的编程思维的,也很勉强,而且具有风险性,因为程序员会忘的。

    解决这一问题的办法就是使用代理模式,因为通过代理可以有效控制客户端对被代理对象的直接访问,客户端只能通过代理与被代理对象进行交互,这样代理可以很轻易对被代理对象的行为进行扩展或者修改,而我们在这里要做的就是替换掉connection的close()方法。

    为清晰起见,我在此重新创建了一个连接接口名为IConnection,它包含query()和close()两个方法。 

    public interface IConnection {
    
    	// 执行一条查询语句
    	void query();
    
    	// 关闭连接
    	void close();
    }

    接下来写一个实现的IConnection接口的ImpConnection类,它扮演了代理模式中的真实主题角色。

    public class ImpConnection implements IConnection {
    
    	@Override
    	public void query() {
    		System.out.println("执行一条查询语句...");
    	}
    
    	@Override
    	public void close() {
    		System.out.println("关闭连接...");
    	}
    }

    接下来我们要写代理类ConnectionProxy,代理类也要实现IConnection这个接口,由于通过继承方式实现代理模式不够灵活,推荐使用组合方式。

    public class ConnectionProxy implements IConnection {
    
    	private IConnection iConnection;
    
    	public ConnectionProxy(IConnection iConnection) {
    		super();
    		this.iConnection = iConnection;
    	}
    
    	@Override
    	public void query() {
                //对于我们不关心的方法,直接调用真实对象去处理
    		iConnection.query();
    	}
    
    	@Override
    	public void close() {
    		// 不真正关闭连接,而是将连接归还给连接池
    		ConnectionPool.getInstance().returnConnection(iConnection);
    	}
    
    }

    接下来是连接池的简单代码

    import java.util.LinkedList;
    
    public class ConnectionPool {
    
    	private static LinkedList<IConnection> connectionList = new LinkedList<IConnection>();
    	private static IConnection createNewConnection() {
    		return new ImpConnection();
    	}
    
    	private ConnectionPool() {
    		if (connectionList == null || connectionList.size() == 0) {
    			for (int i = 0; i < 10;) {
    				System.out.println("创建第 " + (++i) + " 个连接...");
    				connectionList.add(createNewConnection());
    			}
    			System.out.println("连接池初始化完成...");
    			showConnectionCount();
    		}
    	}
    
    	public static void showConnectionCount() {
    		System.out.println("当前连接池可用连接数为: " + connectionList.size());
    	}
    
    	public IConnection getConnection() {
    		if (connectionList.size() > 0) {
    			// return connectionList.remove();
    			// 这是原有的方式,直接返回连接,这样可能会出现程序员把连接给关闭掉的情况
    			// 下面是使用代理的方式,程序员再调用close时,就会调用代理的close()方法把连接归还到连接池
    			IConnection iConnection = null;
    			iConnection = connectionList.remove();
    			System.out.println("从连接池获取一条连接...");
    			showConnectionCount();
    			return new ConnectionProxy(iConnection);
    		}
    		return null;
    	}
    
    	public void returnConnection(IConnection connection) {
    		System.out.println("将连接归还给连接池。。。");
    		connectionList.add(connection);
    		showConnectionCount();
    	}
    
    	public static ConnectionPool getInstance() {
    		return ConnectionPoolInstance.connectionPool;
    	}
    
    	private static class ConnectionPoolInstance {
    		private static ConnectionPool connectionPool = new ConnectionPool();
    	}
    
    }

    在MainClass里面测试一下。

    public class MainClass {
    
    	public static void main(String[] args) {
    		// 此条语句用来测试单例ConnectionPool是否实现延迟加载
    		// 由于未使用连接,此条语句不会导致数据库连接池内连接初始化
    		ConnectionPool.showConnectionCount();
    		System.out.println("************接下来要从连接池取连接了****************");
    		IConnection connection = ConnectionPool.getInstance().getConnection();
    		System.out.println("************可以使用连接进行数据库操作了****************");
    		// 使用连接执行查询语句
    		connection.query();
    		connection.query();
    		System.out.println("************连接用完了之后,该归还连接了****************");
    		connection.close();
    	}
    }

    运行程序结果打印: 

    当前连接池可用连接数为: 0
    ************接下来要从连接池取连接了****************
    创建第 1 个连接...
    创建第 2 个连接...
    创建第 3 个连接...
    创建第 4 个连接...
    创建第 5 个连接...
    创建第 6 个连接...
    创建第 7 个连接...
    创建第 8 个连接...
    创建第 9 个连接...
    创建第 10 个连接...
    
    连接池初始化完成...
    当前连接池可用连接数为: 10
    
    从连接池获取一条连接...
    当前连接池可用连接数为: 9
    
    ************可以使用连接进行数据库操作了****************
    执行一条查询语句...
    执行一条查询语句...
    
    ************连接用完了之后,该归还连接了****************
    将连接归还给连接池。。。
    
    当前连接池可用连接数为: 10

     好了,这下我们的连接池返回的连接全是代理,就算程序员调用了close方法也只会把连接归还给连接池了。如果我们使用连接池getConnection()方法中被注释掉的语句 " return connectionList.remove(); " ,直接返回连接对象,那么以上MainClass中*连接用完了之后*连接池中的可用连接数将变为:9,即连接被真正关闭了。

    我们使用代理模式解决了上述问题,从静态代理的使用上来看,我们一般是这么做的。

    1,代理类一般要持有一个被代理的对象的引用。

    2,对于我们不关心的方法,全部委托给被代理的对象处理。

    3,我们只处理我们关心的方法。

    应用二

    以上示例使用单例模式实现了ConnectionPool对象的延迟加载,示例稍微有点复杂,接下来我们再以一个简单的示例来感受一下使用代理模式实现延迟加载的方法及其意义。

    延迟加载(lazy load)(也称为懒加载)是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才去执行数据加载操作。延迟加载的实际应用很多:比如使用浏览器打开网站,浏览器一般总是先加载文本,待文本显示出来以后再加载图片、视频等耗时较多的资源,这样做的好处是用户不必非要等到所有东西都下载完成以后才能看到页面,可以极大地减少用户的等待时间;又比如Windows操作系统的启动过程,若我们将系统的所有服务项都设置为开机启动,那么我们每次启动计算机都需要等待非常长的时间,所以我们一般都是将系统必须的服务和一些常用服务设置为开机启动,对于那些不常用的服务我们只在用到的时候再去开启,这样做可以减少每次开机时系统启动所需时间,提高系统的启动速度。

    接下来该引出我们的第二个例子了:

    假设某客户端软件有根据用户请求去数据库查询数据的功能,在查询数据前,需要获得数据库连接,如果我们在软件开启时就去初始化并获得数据库连接,会存在以下弊端:软件开启时需初始化系统的所有类,如果系统还有大量类似资源耗费较多的操作(比如 XML 解析,本地或远程文件资源加载)也需在此时进行初始化,所有这些初始化操作的叠加会使得系统的启动速度变得非常缓慢。为此,我们使用代理模式的代理类对数据库查询类的初始化操作进行封装,当系统启动时,初始化这个代理类,而非真实的数据库查询类,由于此时代理类什么都没有做,因此,它的构造是相当迅速的。在用户真正做查询操作时再由代理类单独去加载真实的数据库查询类,完成用户的请求,这样就使用代理模式实现了延迟加载。

    源代码如下:

    定义一个接口 IConnection 作为抽象主题角色,该接口用来定义代理类和真实主题类需要对外提供的服务,本例只在其中定义了一个用来进行数据库查询的 query()方法。

    public interface IConnection {
    	void query();
    }

    ImpConnection 是真实主题角色,负责实际的业务操作。

    public class ImpConnection implements IConnection {
    
    	ImpConnection() {
    		try {
    			for (int i = 0; i < 3; i++) {
    				System.out.println("ImpConnection对象创建中...");
    				Thread.sleep(1000);// 假设数据库连接等耗时操作
    			}
    			System.out.println("ImpConnection对象创建完成...");
    		} catch (InterruptedException ex) {
    			ex.printStackTrace();
    		}
    	}
    
    	@Override
    	public void query() {
    		System.out.println("执行一次数据库查询操作...");
    	}
    
    }

    ConnectionProxy 是 ImpConnection 的代理类。

    public class ConnectionProxy implements IConnection {
    
    	private ImpConnection connection = null;
    
    	@Override
    	public void query() {
    	    // 在真正需要的时候才能创建真实对象,创建过程可能很慢
    	    if (connection == null) {
    			connection = new ImpConnection();
    		}
    	    connection.query();
    	}
    }

    在MainClass中进行数据库查询测试。

    public class MainClass {
    
    	public static void main(String[] args) {
    		IConnection connection = new ConnectionProxy(); // 使用代理
    		connection.query(); // 在真正使用时才创建真实主题对象
    	}
    }

    运行程序结果打印: 

    ImpConnection对象创建中...
    ImpConnection对象创建中...
    ImpConnection对象创建中...
    ImpConnection对象创建完成...
    执行一次数据库查询操作...

     将MainClass中的 "connection.query();" 一行删除之后,将再没有任何结果打印到控制台,由此证明:若没有发生请求,将不会创建ImpConnection对象,使用代理模式实现了被代理对象延迟加载的功能。

    使用代理模式实现延迟加载的核心思想是:如果当前并没有使用这个组件,则不需要真正地初始化它,使用一个代理对象替代它的原有的位置,只在真正需要的时候才对它进行加载。使用代理模式的延迟加载是非常有意义的,首先,它可以在时间轴上分散系统压力,尤其在系统启动时,不必完成所有的初始化工作,从而加快启动速度;其次,对很多真实主题而言,在软件启动直到被关闭的整个过程中,可能根本不会被调用,初始化这些数据无疑是一种资源浪费。例如上面这个例子,若系统不使用代理模式,则在启动时就要初始化 ImpConnection 对象,而使用代理模式后,启动时只需要初始化一个轻量级的 ConnectionProxy对象。 

    四、代理模式的应用场景

    代理模式主要应用于以下几个场景:

    -远程代理,也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。比如说 WebService,当我们在应用程序的项目中加入一个 Web 引用,引用一个 WebService,此时会在项目中声称一个 WebReference 的文件夹和一些文件,这个就是起代理作用的,这样可以让那个客户端程序调用代理解决远程访问的问题;

    -虚拟代理,是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化,比如打开一个网页,这个网页里面包含了大量的文字和图片,但我们可以很快看到文字,但是图片却是一张一张地下载后才能看到,那些未打开的图片框,就是通过虚拟代理来替换了真实的图片,此时代理存储了真实图片的路径和尺寸;

    -安全代理,用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候;

    -指针引用,是指当调用真实对象时,代理处理另外一些事。比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它,或当第一次引用一个持久对象时,将它装入内存,或是在访问一个实际对象前,检查是否已经释放它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些事务处理;

    -延迟加载,用代理模式实现延迟加载的一个经典应用就在 Hibernate 框架里面。当 Hibernate 加载实体 bean 时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采取延迟加载的机制,以提高系统的性能。Hibernate 中的延迟加载主要分为属性的延迟加载和关联表的延时加载两类。实现原理是使用代理拦截原有的 getter 方法,在真正使用对象数据时才去数据库或者其他第三方组件加载实际的数据,从而提升系统性能。

    PS:此外还有防火墙代理,智能引用代理,缓存代理,同步代理,复杂隐藏代理,写入时复制代理等等,都有各自特殊的用途。

    五、静态代理模式的特点

    静态代理的优点:

    被代理对象只要和代理类实现了同一接口即可,代理类无须知道被代理对象具体是什么类、怎么做的,而客户端只需知道代理即可,实现了类之间的解耦合。

    静态代理的缺点:

    代理类和被代理类实现了相同的接口,代理类通过被代理类实现了相同的方法,这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。

    每个代理类只能为一个接口服务,如果程序开发中要使用代理的接口很多的话,必然会产生许多的代理类,造成类膨胀。

    参考文章

    《代理模式详解包含原理详解》

    《Java动态代理的实现》

    《代理模式原理及实例讲解》​​​​​​​

    展开全文
  • 代理模式静态代理和动态代理)

    千次阅读 2015-05-29 20:57:53
    一、代理模式  代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。...而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。   二、静态代理
  • 静态模式

    千次阅读 2014-05-11 21:51:07
    静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加地有弹性和灵活。我们还是先来看一下语法: : : ... targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。 target-pattern 是...
  • 代理模式静态代理+动态代理)——JAVA

    千次阅读 热门讨论 2014-08-05 17:25:30
    代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类...
  • 标题:Java代理模式详解及案例分析:静态代理/动态代理 我们以几个问题,来开始我们今天的学习,如果下面几个问题,你都能说出个一二,那么恭喜你,你已经掌握了这方面的知识。 1,什么是代理模式? 2,Java中,...
  • 设计模式之——静态代理模式

    万次阅读 2021-02-11 16:54:45
    设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。 正确使用设计模式具有以下优点: 可以提高程序员的思维能力、编程能力和设计能力。 使程序...
  • VMware虚拟机有三种网络模式,分别是Bridged(桥接模式)、NAT(网络地址转换模式)、Host-only(主机模式)。  宿主机在VMware workstation安装好之后会多出两个网络连接,分别是VMware Network Adapter VMnet1和VMware...
  • 程序设计之单例模式 VS 静态方法

    万次阅读 多人点赞 2013-09-24 11:57:48
    单例模式(Singleton); 2>静态方法. 但是, 对于这两种实现方式 , 那种更好呢? 在国内论坛上看了一下其他的一些看法 : http://hi.baidu.com/jiangzhong8715/item/c8b66e3d6afd2f677c034b07: ...
  • 代理模式 什么是代理模式 通过代理为原始类增加额外功能 好处:有利于目标类的维护,增强的更能是可插拔的。 名词解释 目标类:如UserServiceImpl 目标方法: login(String username,String password) 额外功能:...
  • iOS静态动态库基本使用

    千次阅读 2015-11-05 17:29:44
    静态库和动态库分别都有两种形式: 静态库:.a 和 .framework动态库:.dylib 和 .framework  在Xcode6之前我们只能在App中使用‘静态库’,如果使用到了‘动态库’那么App是无法上传AppStore的。但Xcode6开始...
  • 代理模式分为静态代理和动态代理。 原理 使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。 就像如果你们公司想...
  • 静态功耗与动态功耗

    万次阅读 多人点赞 2020-04-05 15:01:57
    功耗的本质是能量耗散。由能量守恒定律可知,能量只能从一种形式转成另一种形式,能量的总量不变。芯片耗散的电能主要转化成热能。如果一颗芯片的功耗过大,容易导致工作时...静态功耗以及动态功耗是两个主要的功耗源。
  • 代理模式静态代理的实现

    千次阅读 2017-03-30 19:09:13
    代理模式分为静态代理、动态代理。 静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  • 单例模式就是保证系统中这个对象只有一个实例 什么是单例模式?  单例模式确保某个类只有一个实例,而且自行...1)饿汉模式:饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这...
  • 轻松学,Java 中的代理模式动态代理

    万次阅读 多人点赞 2017-06-29 22:08:55
    前几天我写了《秒懂,Java 注解 (Annotation)你可以这样学》,因为注解其实算反射技术中的一部分,然后我想了一下,反射技术中还有个常见的概念就是动态代理,于是索性再写一篇关于动态代理的博文好了。...
  • 设计模式--静态代理模式详解

    千次阅读 2017-07-08 10:29:44
    代理模式的基本概念:   概念:为其他对象提供一种代理,以控制对这个对象的访问(例如火车票代售处,就是火车站的代理)。代理对象起到中介作用,可去掉功能服务(退票服务)或增加额外的服务(手续费)。 ...
  • 静态代理模式和装饰模式的区别

    千次阅读 2018-07-13 18:51:21
    学了代理模式之后发现静态代理模式和装饰模式非常的相似,代理类和被代理的客户实现同一个接口,装饰者和被装饰者也是实现的同一个接口,实现方式也类似(针对接口编程是一个主要设计原则)。搜索之,得到结论:代理...
  • 设计模式之代理模式静态代理)

    千次阅读 2016-08-30 15:01:19
    概要设计模式是一门艺术,如果真正了解这门艺术,你会发现,世界都将变得更加优美。定义为其它的对象提供一种代理,以控制这个对象的访问使用场景当不想直接访问某个对象的时候,就可以通过代理1.不想买午餐,同事...
  • Java设计模式-代理模式静态代理

    千次阅读 2015-06-26 11:25:36
    Java设计模式-代理模式静态代理 概念 为另一个对象提供一个替身或占位符以提供对这个对象的访问,使用代理模式创建代表对象,让代表对象控制某对象的访问,被代理对象可以是远程的对象、创建开销大的...
  • 关系,关系模式,关系模型区别和联系

    万次阅读 多人点赞 2019-12-18 09:40:11
    关系是关系模式在某一个时刻的状态或者内容,关系模式静态的,稳定的,而关系是动态的,随时间不断变化的,因为关系操作在不断地更新着数据库中的数据 类似于面向对象程序设计中”类“与”对象“的区别。”...
  • 单例模式静态

    千次阅读 2017-09-02 14:47:26
    单例模式还是静态类,这是一个老话题了,从我刚开始接触Java的时候就看到这样的讨论。在这里我总结一下,也添加一点点新东西。首先要澄清和区别一些概念,“静态类”和“所有方法皆为静态方法的类”。 严格说来,...
  • 本文部分参考自JAVA与模式一...1.类的创建模式使用继承关系,把类的创建延迟到子类,从而封装了客户端得到了哪些具体类的信息,隐藏这些类的实例是如何被创建和放在一起的2.对象的创建模式把对象的创建过程动态地委...
  • 关系模式,关系,关系数据库

    千次阅读 2020-03-11 22:13:06
    关系模式是型、关系是值,关系模型是对关系的描述(元组集合的结构、完整性约束条件)是静态的、稳定的;关系是关系模式在吗某一时刻的状态或内容,是动态的、随时间不断变化的;关系数据库是在给定应用领域中所有关系...
  • 关系模式造磨一时刻的状态或内容 动态的、随时间不断变化的关系模式和关系统称为关系,需根据上下文加以区分关系的完整性:实体完整性、参照完整性、用户定义的完整性 实体完整性和参照完整性是关系模型必须满足的...
  • linux下桥接模式设置静态IP实现上网

    万次阅读 多人点赞 2017-06-18 20:56:02
    桥接网络连接模式的虚拟机就当作主机所在以太网的一部分,虚拟系统和宿主机器的关系,就像连接在同一个Hub上的两台电脑,可以像主机一样可以访问以太网中的所有共享资源和网络连接,可以直接共享主机网络的互联网接...
  • 这三种方式分别为:使用静态工厂...静态工厂和实例工厂区别:静态工厂指的是工厂的服务是静态的,也就是工厂提供的方法是static的,那么这些方法仅有工厂提供以及管理更新等,跟客户端或者说调用端是没有关系的;...
  • 静态方法、单例模式区别

    千次阅读 2017-01-06 23:44:02
    单例模式静态方法有很多优势: 首先,单例可以继承类,实现接口,而静态类不能(可以集成类,但不能集成实例成员); 其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化; 再次,单例类可以被集成,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 306,313
精华内容 122,525
热门标签
关键字:

关系模式静态还是动态