zoukankan      html  css  js  c++  java
  • 代理模式

    代理模式

    在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

    在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

    介绍

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

    主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

    何时使用:想在访问一个类时做一些控制。

    如何解决:增加中间层。

    关键代码:实现与被代理类组合。

    应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。

    优点: 1、职责清晰。 2、高扩展性。 3、智能化。

    缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

    使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

    注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

    角色

    Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;

    RealSubject(真实主题角色):真正实现业务逻辑的类;

    Proxy(代理主题角色):用来代理和封装真实主题;

    如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:

    • 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
    • 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件

    示例

    静态代理

    编写一个接口 UserService 

    public interface UserService {
        public void select();   
        public void update();
    }

    该接口的一个实现类 UserServiceImpl

    public class UserServiceImpl implements UserService {  
        public void select() {  
            System.out.println("查询 selectById");
        }
        public void update() {
            System.out.println("更新 update");
        }
    }

    我们将通过静态代理对 UserServiceImpl 进行功能增强,在调用 select 和 update 之前记录一些日志。写一个代理类 UserServiceProxy,代理类需要实现 UserService

    public class UserServiceProxy implements UserService {
        private UserService target; // 被代理的对象
    
        public UserServiceProxy(UserService target) {
            this.target = target;
        }
        public void select() {
            before();
            target.select();    // 这里才实际调用真实主题角色的方法
            after();
        }
        public void update() {
            before();
            target.update();    // 这里才实际调用真实主题角色的方法
            after();
        }
    
        private void before() {     // 在执行方法之前执行
            System.out.println(String.format("log start time [%s] ", new Date()));
        }
        private void after() {      // 在执行方法之后执行
            System.out.println(String.format("log end time [%s] ", new Date()));
        }
    }

    测试

    public class Client1 {
        public static void main(String[] args) {
            UserService userServiceImpl = new UserServiceImpl();
            UserService proxy = new UserServiceProxy(userServiceImpl);
    
            proxy.select();
            proxy.update();
        }
    }

    输出

    log start time [Mon Jul 01 18:24:52 CST 2019] 
    查询 selectById
    log end time [Mon Jul 01 18:24:52 CST 2019] 
    log start time [Mon Jul 01 18:24:52 CST 2019] 
    更新 update
    log end time [Mon Jul 01 18:24:52 CST 2019]

    通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。

    静态代理的缺点

    虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

    1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:

    • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
    • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类

    2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

    如何改进?

    当然是让代理类动态的生成啦,也就是动态代理。

    为什么类可以动态的生成?

    这就涉及到Java虚拟机的类加载机制了。

    ava虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

    1. 通过一个类的全限定名来获取定义此类的二进制字节流
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

    由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:

    • 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
    • 从网络中获取,典型的应用是 Applet
    • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
    • 由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类
    • 从数据库中获取等等

    所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。

    实现动态代理的思考方向

    为了让生成的代理类与目标对象(真实主题角色)保持一致性,从现在开始将介绍以下两种最常见的方式:

    1. 通过实现接口的方式 -> JDK动态代理
    2. 通过继承类的方式 -> CGLIB动态代理

    JDK动态代理

    JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。

    每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,

    这个方法的调用就会被转发为由InvocationHandler这个接口的invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

    Object invoke(Object proxy, Method method, Object[] args) throws Throwable

    我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?

    Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    
    proxy:  指代我们所代理的那个真实对象
    method:  指代的是我们所要调用真实对象的某个方法的Method对象
    args:  指代的是调用真实对象某个方法时接受的参数

    接下来我们来看看Proxy这个类:

    Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

    这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
    
    loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
    
    interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
    
    h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

    好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:

    编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke 方法中编写方法调用的逻辑处理

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.util.Date;
    
    public class LogHandler implements InvocationHandler {
        Object target;  // 被代理的对象,实际的方法执行者
    
        public LogHandler(Object target) {
            this.target = target;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            before();
            Object result = method.invoke(target, args);  // 调用 target 的 method 方法
            after();
            return result;  // 返回方法的执行结果
        }
        // 调用invoke方法之前执行
        private void before() {
            System.out.println(String.format("log start time [%s] ", new Date()));
        }
        // 调用invoke方法之后执行
        private void after() {
            System.out.println(String.format("log end time [%s] ", new Date()));
        }
    }

    编写客户端,获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法,具体步骤可见代码和注释

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class Client2 {
        public static void main(String[] args) throws IllegalAccessException, InstantiationException {
            // 设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名
            // System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            // 1. 创建被代理的对象,UserService接口的实现类
            UserServiceImpl userServiceImpl = new UserServiceImpl();
            // 2. 获取对应的 ClassLoader
            ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
            // 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,
            Class[] interfaces = userServiceImpl.getClass().getInterfaces();
            // 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
            //     这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
            InvocationHandler logHandler = new LogHandler(userServiceImpl);
            /*
               5.根据上面提供的信息,创建代理对象 在这个过程中,
                   a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
                   b.然后根据相应的字节码转换成对应的class,
                   c.然后调用newInstance()创建代理实例
             */
            UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
            // 调用代理的方法
            proxy.select();
            proxy.update();
    
            // 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
            // ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
    
        }
    }

    运行结果

    log start time [Mon Jul 01 20:56:32 CST 2019] 
    查询 selectById
    log end time [Mon Jul 01 20:56:32 CST 2019] 
    log start time [Mon Jul 01 20:56:32 CST 2019] 
    更新 update
    log end time [Mon Jul 01 20:56:32 CST 2019] 

    InvocationHandler 和 Proxy 的主要方法介绍如下:

    java.lang.reflect.InvocationHandler

    Object invoke(Object proxy, Method method, Object[] args) 定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用

    java.lang.reflect.Proxy

    static InvocationHandler getInvocationHandler(Object proxy) 用于获取指定代理对象所关联的调用处理器

    static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 返回指定接口的代理类

    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法

    static boolean isProxyClass(Class<?> cl) 返回 cl 是否为一个代理类

    代理类的调用过程

    生成的代理类到底长什么样子呢?借助下面的工具类,把代理类保存下来再探个究竟

    import sun.misc.ProxyGenerator;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    public class ProxyUtils {
        /**
         * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
         * params: clazz 需要生成动态代理类的类
         * proxyName: 为动态生成的代理类的名称
         */
        public static void generateClassFile(Class clazz, String proxyName) {
            // 根据类信息和提供的代理类名称,生成字节码
            byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
            String paths = clazz.getResource(".").getPath();
            System.out.println(paths);
            FileOutputStream out = null;
            try {
                //保留到硬盘中
                out = new FileOutputStream(paths + proxyName + ".class");
                out.write(classFile);
                out.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    然后在 Client2 测试类的main的最后面加入一行代码

    // 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
    ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");

    IDEA 再次运行之后就可以在 out 的类路径下找到 UserServiceProxy.class,双击后IDEA的反编译插件会将该二进制class文件

    UserServiceProxy 的代码如下所示:

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    import proxy.UserService;
    
    public final class UserServiceProxy extends Proxy implements UserService {
        private static Method m1;
        private static Method m2;
        private static Method m4;
        private static Method m0;
        private static Method m3;
    
        public UserServiceProxy(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void select() throws  {
            try {
                super.h.invoke(this, m4, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void update() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m4 = Class.forName("proxy.UserService").getMethod("select");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
                m3 = Class.forName("proxy.UserService").getMethod("update");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }

    从 UserServiceProxy 的代码中我们可以发现:

    • UserServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
    • 由于 UserServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
    • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承
    • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名
    • 调用方法的时候通过 super.h.invoke(this, m1, (Object[])null); 调用,其中的 super.h.invoke 实际上是在创建代理的时候传递给 Proxy.newProxyInstance 的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑

    而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法

     @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            before();
            Object result = method.invoke(target, args);       // 调用 target 的 method 方法
            after();
            return result;  // 返回方法的执行结果
        }

    JDK动态代理执行方法调用的过程简图如下:

    代理类的调用过程相信大家都明了了,而关于Proxy的源码解析,还请大家另外查阅其他文章或者直接看源码

    CGLIB动态代理

    maven引入CGLIB包

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.2.2</version>
    </dependency>

    然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()

    public class UserDao {
        public void select() {
            System.out.println("UserDao 查询 selectById");
        }
        public void update() {
            System.out.println("UserDao 更新 update");
        }
    }

    编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调

    import java.lang.reflect.Method;
    import java.util.Date;
    
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class LogInterceptor implements MethodInterceptor {
        /**
         * @param object 表示要进行增强的对象
         * @param method 表示拦截的方法
         * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
         * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
         * @return 执行结果
         * @throws Throwable
         */
        @Override
        public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            before();
            Object result = methodProxy.invokeSuper(object, objects);   // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
            after();
            return result;
        }
        private void before() {
            System.out.println(String.format("log start time [%s] ", new Date()));
        }
        private void after() {
            System.out.println(String.format("log end time [%s] ", new Date()));
        }
    }

    测试

    import net.sf.cglib.proxy.Enhancer;
    
    public class CglibTest {
        public static void main(String[] args) {
            LogInterceptor daoProxy = new LogInterceptor(); 
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(UserDao.class);  // 设置超类,cglib是通过继承来实现的
            enhancer.setCallback(daoProxy);
    
            UserDao dao = (UserDao)enhancer.create();   // 创建代理类
            dao.update();
            dao.select();
        }
    }

    运行结果

    log start time [Mon Jul 01 23:41:15 CST 2019] 
    UserDao 更新 update
    log end time [Mon Jul 01 23:41:15 CST 2019] 
    log start time [Mon Jul 01 23:41:15 CST 2019] 
    UserDao 查询 selectById
    log end time [Mon Jul 01 23:41:15 CST 2019] 

    还可以进一步多个 MethodInterceptor 进行过滤筛选

    import java.lang.reflect.Method;
    import java.util.Date;
    
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class LogInterceptor2 implements MethodInterceptor {
        @Override
        public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            before();
            Object result = methodProxy.invokeSuper(object, objects);
            after();
            return result;
        }
        private void before() {
            System.out.println(String.format("log2 start time [%s] ", new Date()));
        }
        private void after() {
            System.out.println(String.format("log2 end time [%s] ", new Date()));
        }
    }

    再次测试

    import net.sf.cglib.proxy.Callback;
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.NoOp;
    
    public class CglibTest2 {
        public static void main(String[] args) {
            LogInterceptor logInterceptor = new LogInterceptor();
            LogInterceptor2 logInterceptor2 = new LogInterceptor2();
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(UserDao.class); // 设置超类,cglib是通过继承来实现的
            enhancer.setCallbacks(new Callback[] { logInterceptor, logInterceptor2, NoOp.INSTANCE }); // 设置多个拦截器,NoOp.INSTANCE是一个空拦截器,不做任何处理
            enhancer.setCallbackFilter(new DaoFilter());
    
            UserDao proxy = (UserDao) enhancer.create(); // 创建代理类
            proxy.select();
            proxy.update();
        }
    }

    运行结果

    log start time [Tues Jul 02 00:49:22 CST 2019] 
    UserDao 查询 selectById
    log end time [Tues Jul 02 00:49:22 CST 2019] 
    log2 start time [Tues Jul 00 20:49:22 CST 2019] 
    UserDao 更新 update
    log2 end time [Tues Jul 02 00:49:22 CST 2019] 

    CGLIB 创建动态代理类的模式是:

    1. 查找目标类上的所有非final 的public类型的方法定义;
    2. 将这些方法的定义转换成字节码;
    3. 将组成的字节码转换成相应的代理的class对象;
    4. 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求

    JDK动态代理与CGLIB动态代理对比

    JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。

    cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

    JDK Proxy 的优势:

    • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
    • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
    • 代码实现简单。

    基于类似 cglib 框架的优势:

    • 无需实现接口,达到代理类无侵入
    • 只操作我们关心的类,而不必为其他相关类增加工作量。
    • 高性能

    代理模式总结

    代理可以分为 “静态代理” 和 “动态代理”,动态代理又分为 “JDK动态代理” 和 “CGLIB动态代理” 实现。

    静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object

    • 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
    • 缺点:不同的接口要有不同的代理类实现,会很冗余

    JDK 动态代理:

    • 为了解决静态代理中,生成大量的代理类造成的冗余;
    • JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
    • jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
    • jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
    • 优点:解决了静态代理中冗余的代理实现类问题。
    • 缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

    CGLIB 代理:

    • 由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
    • CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
    • 实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
    • 但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
    • 同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
    • 优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
    • 缺点:技术实现相对难理解些。

    CGlib 对接口实现代理?

    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    import proxy.UserService;
    import java.lang.reflect.Method;
    
    /**
     * 创建代理类的工厂 该类要实现 MethodInterceptor 接口。
     * 该类中完成三样工作:
     * (1)声明目标类的成员变量,并创建以目标类对象为参数的构造器。用于接收目标对象
     * (2)定义代理的生成方法,用于创建代理对象。方法名是任意的。代理对象即目标类的子类
     * (3)定义回调接口方法。对目标类的增强这在这里完成
     */
    public class CGLibFactory implements MethodInterceptor {
        // 声明目标类的成员变量
        private UserService target;
    
        public CGLibFactory(UserService target) {
            this.target = target;
        }
        // 定义代理的生成方法,用于创建代理对象
        public UserService myCGLibCreator() {
            Enhancer enhancer = new Enhancer();
            // 为代理对象设置父类,即指定目标类
            enhancer.setSuperclass(UserService.class);
            /**
             * 设置回调接口对象 注意,只所以在setCallback()方法中可以写上this,
             * 是因为MethodIntecepter接口继承自Callback,是其子接口
             */
            enhancer.setCallback(this);
            return (UserService) enhancer.create();// create用以生成CGLib代理对象
        }
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("start invoke " + method.getName());
            Object result = method.invoke(target, args);
            System.out.println("end invoke " + method.getName());
            return result;
        }
    }
  • 相关阅读:
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1063:最大跨度值
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1063:最大跨度值
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1062:最高的分数
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1057:简单计算器
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1057:简单计算器
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1062:最高的分数
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1062:最高的分数
    C#怎么给新建的winform程序添加资源文件夹Resources
    C#怎么给新建的winform程序添加资源文件夹Resources
    C#Win32API编程之PostMessage
  • 原文地址:https://www.cnblogs.com/xiaojiesir/p/11133494.html
Copyright © 2011-2022 走看看