JDK动态代理实现的两种方式(代理模式Proxy)

Java领域中,常用的动态代理实现方式有两种,一种是利用JDK反射机制生成代理,另外一种是使用CGLIB代理。

JDK代理必须要提供接口,而CGLIB则不需要,可以直接代理类。

定义

代理模式是对象的结构模式。代理模式给某一个对象提供代理对象,并由代理对象控制对源对象的引用。

代理模式的结构

所谓的代理,就是一个人或者一个机构代表另外一个人或者另外一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象中间起到中介的作用。

动态代理

动态代理主要有如下特点:

  • 代理对象不需要实现目标对象的接口。
  • 代理对象的生成,使用的是Java的API,动态的在内存中构件代理对象(这需要我们指定创建代理对象/目标对象的接口的类型)。
  • 动态代理也叫做JDK代理、接口代理。

   JDK中生成代理对象的API

       代理类所在的包为:java.lang.reflect.Proxy

       JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,源码中的方法定义为:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
    //......
}

  

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

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
  • Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方式确认类型。
  • InvocationHandler h:事件处理。执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

示例代码

  MyService:

public interface MyService {
    
    /**
     * 吃饭
     */
    public void eat();
    
    /**
     * 睡觉
     */
    public void sleep();
    
}

 

  MyServiceImpl:
public class MyServiceImpl implements MyService {

    @Override
    public void eat() {
        System.out.println("一日三餐");
    }

    @Override
    public void sleep() {
        System.out.println("每天八小时睡眠,不然会猝死");
    }

}

  

  MyProxy:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyProxy {

    //维护一个目标对象
    private Object target;
    
    //对象构造时,提供目标对象
    public MyProxy(Object target) {
        this.target = target;
    }
    //给目标对象生成代理对象
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), 
                new InvocationHandler() {
                    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("日志打印: before");
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("日志打印: after");
                        return returnValue;
                    }
                });
    }
    
    public static void main(String[] args) {
        MyService myService = new MyServiceImpl();
        MyService myProxy = (MyService)new MyProxy(myService).getProxyInstance();
        myProxy.eat();
        myProxy.sleep();
    }
    
}

 

 

Cglib代理

上面的静态代理和动态代理模式都需要目标对象是一个实现了接口的目标对象,但是有的时候,目标对象可能只是一个单独的对象,并没有实现任何的接口,这个时候,我们就可以使用目标对象子类的方式实现代理,这种代理方式就是:Cglib代理

定义

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

  • JDK的动态代理有个限制,就是使用动态代理的目标对象必须实现至少一个接口,由此,没有实现接口但是想要使用代理的目标对象,就可以使用Cglib代理。
  • Cglib是强大的高性能的代码生成包,它可以在运行期间拓展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)。
  • Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不鼓励直接只使用ASM,因为它要求你必须对JVM内部结构,包括class文件的格式和指令集都很熟悉。

Cglib子类代理的实现方法

  1. 需要引入Cglib的jar文件,在Maven中可以直接在POM.xml中添加下列引用即可。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>

  如果不是maven项目,除了到cglib包还需要导入asm.jar包,不然会报异常:

Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
    at net.sf.cglib.core.TypeUtils.parseType(TypeUtils.java:180)
    at net.sf.cglib.core.KeyFactory.<clinit>(KeyFactory.java:66)
    at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:69)
    at proxy.CGlibProxy.getProxy(CGlibProxy.java:13)
    at proxy.CGlibProxy.main(CGlibProxy.java:36)
Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    ... 5 more

代码示例:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyProxyByGclib implements MethodInterceptor {
    
    //维护目标对象
    private Object target;
    
   
    public MyProxyByGclib(Object target) {
        this.target = target;
    }
    
    public Object getProxyInstance() {
        //1. 实例化工具类
        Enhancer en = new Enhancer();
        //2. 设置父类对象
        en.setSuperclass(this.target.getClass());
        //3. 设置回调函数
        en.setCallback(this);
        //4. 创建子类,也就是代理对象
        return en.create();
    }

    @Override
    public Object intercept(Object arg0, Method method, Object[] objects, MethodProxy arg3) throws Throwable {
        System.out.println("Begin Transaction");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, objects);
        System.out.println("End Transaction");
        return returnValue;
    }
    
    public static void main(String[] args) {
        //目标对象
        MyServiceImpl myService = new MyServiceImpl();
        //生成代理对象
        MyServiceImpl myProxy = (MyServiceImpl)new MyProxyByGclib(myService).getProxyInstance();
        //调用对象方法
        myProxy.eat();
        myProxy.sleep();
    }

}

在Spring的AOP编程中:

  • 如果加入容器的目标对象有实现接口,就使用JDK代理
  • 如果目标对象没有实现接口,就使用Cglib代理。

推荐阅读:https://www.jianshu.com/p/305c8da4563d

      https://www.cnblogs.com/ygj0930/p/6542259.html

posted @ 2019-07-12 15:26  尘世间迷茫的小书童  阅读(2897)  评论(0编辑  收藏  举报