Java动态代理设计模式

本文主要介绍Java中两种常见的动态代理方式:JDK原生动态代理CGLIB动态代理java

什么是代理模式

就是为其余对象提供一种代理以控制对这个对象的访问。代理能够在不改动目标对象的基础上,增长其余额外的功能(扩展功能)。编程

代理模式角色分为 3 种:dom

  • Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
  • RealSubject(真实主题角色):真正实现业务逻辑的类;
  • Proxy(代理主题角色):用来代理和封装真实主题;

若是根据字节码的建立时机来分类,能够分为静态代理和动态代理:ide

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

静态代理

学习动态代理前,有必要来学习一下静态代理。函数

静态代理在使用时,须要定义接口或者父类,被代理对象(目标对象)与代理对象(Proxy)一块儿实现相同的接口或者是继承相同父类。性能

来看一个例子,模拟小猫走路的时间。学习

// 接口
public interface Walkable {
    void walk();
}

// 实现类
public class Cat implements Walkable {

    @Override
    public void walk() {
        System.out.println("cat is walking...");
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

若是我想知道走路的时间怎么办?能够将实现类Cat修改成:测试

public class Cat implements Walkable {

    @Override
    public void walk() {
        long start = System.currentTimeMillis();
        System.out.println("cat is walking...");
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("walk time = " + (end - start));
    }
}

这里已经侵入了源代码,若是源代码是不能改动的,这样写显然是不行的,这里能够引入时间代理类CatTimeProxyui

public class CatTimeProxy implements Walkable {
    private Walkable walkable;

    public CatTimeProxy(Walkable walkable) {
        this.walkable = walkable;
    }

    @Override
    public void walk() {
        long start = System.currentTimeMillis();

        walkable.walk();

        long end = System.currentTimeMillis();
        System.out.println("Walk time = " + (end - start));
    }
}

若是这时候还要加上常见的日志功能,咱们还须要建立一个日志代理类CatLogProxythis

public class CatLogProxy implements Walkable {
    private Walkable walkable;

    public CatLogProxy(Walkable walkable) {
        this.walkable = walkable;
    }

    @Override
    public void walk() {
        System.out.println("Cat walk start...");

        walkable.walk();

        System.out.println("Cat walk end...");
        
    }
}

若是咱们须要先记录日志,再获取行走时间,能够在调用的地方这么作:

public static void main(String[] args) {
    Cat cat = new Cat();
    CatLogProxy p1 = new CatLogProxy(cat);
    CatTimeProxy p2 = new CatTimeProxy(p1);

    p2.walk();
}

这样的话,计时是包括打日志的时间的。

静态代理的问题

若是咱们须要计算SDK中100个方法的运行时间,一样的代码至少须要重复100次,而且建立至少100个代理类。往小了说,若是Cat类有多个方法,咱们须要知道其余方法的运行时间,一样的代码也至少须要重复屡次。所以,静态代理至少有如下两个局限性问题:

  • 若是同时代理多个类,依然会致使类无限制扩展
  • 若是类中有多个方法,一样的逻辑须要反复实现

因此,咱们须要一个通用的代理类来代理全部的类的全部方法,这就须要用到动态代理技术。

动态代理

学习任何一门技术,必定要问一问本身,这到底有什么用。其实,在这篇文章的讲解过程当中,咱们已经说出了它的主要用途。你发现没,使用动态代理咱们竟然能够在不改变源码的状况下,直接在方法中插入自定义逻辑。这有点不太符合咱们的一条线走到底的编程逻辑,这种编程模型有一个专业名称叫AOP。所谓的AOP,就像刀同样,抓住时机,趁机插入。

Jdk动态代理

JDK实现代理只须要使用newProxyInstance方法,可是该方法须要接收三个参数:

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        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);
            }

            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);
        }
    }

方法是在Proxy类中是静态方法,且接收的三个参数依次为:

  • ClassLoader loader //指定当前目标对象使用类加载器
  • Class<?>[] interfaces //目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler h //事件处理器

主要是完成InvocationHandler h的编写工做。

接口类UserService

public interface UserService {

    public void select();

    public void update();
}

接口实现类,即要代理的类UserServiceImpl

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

    @Override
    public void update() {
        System.out.println("更新 update");
    }
}

代理类UserServiceProxy

public class UserServiceProxy implements UserService {
    private UserService target;

    public UserServiceProxy(UserService target){
        this.target = target;
    }

    @Override
    public void select() {
        before();
        target.select();
        after();
    }

    @Override
    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 UserServiceProxyJDKMain {
    public static void main(String[] args) {

        // 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()建立代理实例
		 */

        // 会动态生成UserServiceProxy代理类,而且用代理对象实例化LogHandler,调用代理对象的.invoke()方法便可
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);

        // 调用代理的方法
        proxy.select();
        proxy.update();
        
        // 生成class文件的名称
        ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceJDKProxy");
    }
}

这里能够保存下来代理生成的实现了接口的代理对象:

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();
            }
        }
    }

}

动态代理实现过程

  1. 经过getProxyClass0()生成代理类。JDK生成的最终真正的代理类,它继承自Proxy并实现了咱们定义的接口.
  2. 经过Proxy.newProxyInstance()生成代理类的实例对象,建立对象时传入InvocationHandler类型的实例。
  3. 调用新实例的方法,即原InvocationHandler类中的invoke()方法。

代理对象不须要实现接口,可是目标对象必定要实现接口,不然不能用动态代理

Cglib动态代理

JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现加强,但由于采用的是继承,因此不能对final修饰的类进行代理。

Cglib代理,也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

Cglib子类代理实现方法:

  1. 须要引入cglibjar文件,可是Spring的核心包中已经包括了Cglib功能,因此直接引入Spring-core.jar便可.
  2. 引入功能包后,就能够在内存中动态构建子类
  3. 代理的类不能为final,不然报错
  4. 目标对象的方法若是为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

基本使用

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2</version>
</dependency>

方法拦截器

public class LogInterceptor implements MethodInterceptor{

    /*
     * @param o 要进行加强的对象
     * @param method 要拦截的方法
     * @param objects 参数列表,基本数据类型须要传入其包装类
     * @param methodProxy 对方法的代理,
     * @return 执行结果
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);
        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()));
    }
}

测试用例

这里保存了代理类的.class文件

public class CglibMain {

    public static void main(String[] args) {
        // 建立Enhancer对象,相似于JDK动态代理的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置目标类的字节码文件
        enhancer.setSuperclass(UserDao.class);
        // 设置回调函数
        enhancer.setCallback(new LogInterceptor());
        // create会建立代理类
        UserDao userDao = (UserDao)enhancer.create();
        userDao.update();
        userDao.select();
    }
}

结果

log start time [Mon Nov 30 17:26:39 CST 2020] 
UserDao 更新 update
log end time [Mon Nov 30 17:26:39 CST 2020] 
log start time [Mon Nov 30 17:26:39 CST 2020] 
UserDao 查询 selectById
log end time [Mon Nov 30 17:26:39 CST 2020]

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

JDK 动态代理

  • 为了解决静态代理中,生成大量的代理类形成的冗余;
  • JDK 动态代理只须要实现 InvocationHandler 接口,重写 invoke 方法即可以完成代理的实现,
  • jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
  • jdk动态代理之因此只能代理接口是由于代理类自己已经extendsProxy,而java是不容许多重继承的,可是容许实现多个接口

优势:解决了静态代理中冗余的代理实现类问题。

缺点JDK 动态代理是基于接口设计实现的,若是没有接口,会抛异常。

CGLIB 代理

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

优势:没有接口也能实现动态代理,并且采用字节码加强技术,性能也不错。

缺点:技术实现相对难理解些。

相关文章
相关标签/搜索