Java静态代理和动态代理


什么是代理?通俗理解为代理就是中间层或者中间对象,有了代理就不需要客户类直接和委托类进行交互.那为何又要有代理呢,主要考虑下面两个因素:

  • 可以隐藏委托类的实现
  • 达到一种与委托类的解耦,在不修改委托类的情况下,实现一些功能的处理.

比如:我们平时购买商品,一般会到超市,而非是去生产商品的工厂去购买.我们并不关心商品在工厂中是如何生产的,只是要单纯的购买商品而已.这样的话,超市就是一个代理类,作为中间层供给我们商品.而工厂就是我们说说的委托类,我们就是属于客户类群体.

同样的,在编程语言中,有很多场景也需要使用代理类,因为委托类本身不想要让他的客户群体看到自己的内部实现,所以通过代理类来完成调用.比如在远程RPC服务调用的时候,我们就是通过代理类来完成,而非直接委托类,再有Spring中的AOP切面中也是为切面生成了一个代理类等等.

作为代理类,可分为:静态代理和动态代理,动态代理又有JDK动态代理和CGLIB动态代理,每一种代理都各有优势与不足,并不能单纯的拿来比较好坏,就像编程语言一样,没有绝对的好坏之分,合适的就是最好的.

静态代理

首先定义接口和接口的实现类,然后定义接口的代理对象,并将接口的实例注入到代理对象中,之后可以通过代理对象调用真正的实现类。静态代理的关系在编译期间就已经确定。鉴于其这种处理方式,所以静态代理只适用于代理类较少且确定的情况中。比如,商品购买的包装,实现客户与委托类的解耦。

  1. 定义接口

    public interface IService {
        /**
         * 定义接口中的方法
         * @param userName
         * @return
         */
        String sayHello(String userName);
    }
    
  2. 定义接口实现

    //委托类的实现
    public class IServiceImpl implements IService {
    
        @Override
        public String sayHello(String userName) {
            System.out.println("HelloService " + userName);
            return "HelloService " + userName;
        }
    }
    
    
  3. 定义代理类

    public class ProxyHello implements IService {
        /**
         * 注入接口实例
         */
        private IService iService = new IServiceImpl();
    
        /**
         * @param userName
         * @return
         */
        @Override
        public String sayHello(String userName) {
            //代理对象可以在此处进行包装处理
            System.out.println("代理对象进行相应处理");
            return iService.sayHello(userName);
        }
    }
    
  4. 代理类测试

    public class TestProxy {
        public static void main(String[] args) {
            ProxyHello proxyHello=new ProxyHello();
            proxyHello.sayHello("FEEL");
        }
    }
    
  5. 运行结果

    代理对象进行相应处理
    HelloService FEEL

动态代理

即程序在运行过程中创建的代理。

动态代理通常有两种做法:

  • 通过接口的JDK动态代理
  • 通过继承类的CGLIB动态代理
JDK动态代理

Java的动态代理中主要涉及java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler两个类。而我们只需要一个实现InvocationHandler接口的中间类。该接口中只有一个方法invoke,如下:

public interface InvocationHandler {
    /**
     * 调用处理
     * @param proxy 代理类对象
     * @param method 具体调用的代理类的方法
     * @param args 代理类方法的参数
     */
 	public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

对处理类中的所有方法的调用都会变成对 invoke 方法的调用,这样我们可以在 invoke 方法中添加统一的处理逻辑(也可以根据 method 参数判断是哪个方法)。中间类 (实现InvocationHandler的类) 有一个委托类对象引用, 在 invoke方法中调用委托类对象的相应方法,通过这种聚合的方式持有委托类对象引用,把外部对 invoke的调用最终都转为对委托类对象的调用。

实际上,中间类与委托类构成静态代理关系,在这个关系中,中间类是代理类,委托类是委托类。然后代理类与中间类也构成一个静态代理关系,在这个关系中,中间类是委托类,代理类是代理类。也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。

下面看一个JDK动态代理的例子:

  1. 接口定义
public interface IService {

    /**
     * @param userName
     * @return
     */
    String sayHello(String userName);

    /**
     * @param userName
     * @return
     */
    String sayLing(String userName);
}
  1. 接口实现
public class IServiceImpl implements IService {

    @Override
    public String sayHello(String userName) {
        System.out.println("HelloService " + userName);
        return "HelloService " + userName;
    }

    @Override
    public String sayLing(String userName) {
        System.out.println(userName+" Ling");
        return userName+ " Ling";
    }

}
  1. 代理执行类
public class JavaProxyInvocationHandler implements InvocationHandler {

    /**
     * 中间类持有委托类对象引用
     */
    private Object object;

    /**
     * 传入委托类对象
     *
     * @param o 委托类对象
     */
    public JavaProxyInvocationHandler(Object o) {
        this.object = o;
    }

    /**
     * 动态生成代理类对象,Proxy.newProxyInstance
     *
     * @return 返回代理类实例
     */
    public Object newProxyInstance() {
        return Proxy.newProxyInstance(
                //指定代理对象的类加载器
                object.getClass().getClassLoader(),
                //代理类对象要实现的接口
                object.getClass().getInterfaces(),
                //方法调用的实际处理者,代理对象的方法调用都会转发到此处
                this);
    }

    /**
     * @param proxy  代理对象
     * @param method 代理方法
     * @param args   方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Invoke before");
        Object result = method.invoke(object, args);
        System.out.println("Invoke after");
        return result;
    }
}
  1. 测试类
public class TestJavaProxy {
    public static void main(String[] args) {
        JavaProxyInvocationHandler proxyInvocationHandler
                = new JavaProxyInvocationHandler(new IServiceImpl());

        IService iService = (IService) proxyInvocationHandler.newProxyInstance();
        iService.sayHello("Gutter");
        iService.sayLing("Lin");
    }
}
  1. 输出结果

Invoke before
HelloService Gutter
Invoke after
Invoke before
Lin Ling
Invoke after

简要说明:

调用Proxy类的newProxyInstance方法获取一个代理类实例。该代理类实现了指定的接口并且将方法调用分发到指定的调用处理器。

然后通过代理类的实例调用代理类的方法,对代理类的方法调用都会调用中间类(实现了invocationHandler接口的类)的invoke方法,在该方法中调用委托类的对应方法。

该形式的动态代理最大特点就是:

动态生成的代理类和委托类实现同一个接口。其内部其实就是通过反射机制实现,即一个已知的对象,在运行时动态调用它的方法,并且调用的时候加上自己的处理。

下面是Proxy.newProxyInstance的源码

Proxy.newProxyInstance()通过反射机制来动态生成代理类对象,为接口创建一个代理类,而代理类实现接口。

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        //检查空指针
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
		*获取所有构造器
		*/
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    }
 
private static Object newProxyInstance(Class<?> caller, 
                  						 // null if no SecurityManager
                                         Constructor<?> cons,
                                         InvocationHandler h) {
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (caller != null) {
                checkNewProxyPermission(caller, cons.getDeclaringClass());
            }
			//返回创建的代理类Class的实例对象
            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.g(), t);
            }
        }
    }
CGLIB动态代理

JDK动态代理需要依赖于接口实现,而当没有接口只有类的时候,则需使用另一种动态代理技术即CGLIB动态代理。由于其是第三方框架实现,所以在maven工程中需要引入其pom文件

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

CGLIB代理是针对类实现代理,其原理就是对指定的委托类生成一个子类并重写其中方法来实现代理。代理类对象由Enhancer类来创建。其创建动态代理类的模式如下:

  1. 查找目标类的所有非final的public类型的方法
  2. 将方法定义转换为字节码
  3. 将字节码转换为相应的代理的Class对象,并通过反射获取代理类的实例对象
  4. 实现MethodInterceptor接口,用来处理对代理类上的方法
总结

静态代理容易理解, 需要被代理的类和代理类实现自同一个接口, 然后在代理类中调用真正实现类, 并且静态代理的关系在编译期间就已经确定。而动态代理的关系是在运行期间确定的。静态代理实现简单,适合于代理类较少且确定的情况,而动态代理则给提供了更大的灵活性。

JDK 动态代理所用到的代理类在程序调用到代理类对象时才由 JVM 真正创建,JVM 根据传进来的 业务实现类对象 以及 方法名 ,动态地创建一个代理类的 class 文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们要做的,就是指定代理类前置和后置操作即可。

静态代理和动态代理都是基于接口实现的, 而对于那些没有提供接口只是提供了实现类的而言, 就只能选择 CGLIB 动态代理了

JDK动态代理和CGLIB动态代理的区别
  • JDK 动态代理基于 Java 反射机制实现, 必须要实现了接口的业务类才能用这种方法生成代理对象。
  • CGLIB 动态代理基于 ASM 框架通过生成业务类的子类来实现。
  • JDK 动态代理的优势是最小化依赖关系,这意味着简化开发和维护。并且可以随 JDK 版本升级,代码实现简单。基于 CGLIB 框架的优势是无须实现接口,达到代理类无侵入,我们只需操作我们关系的类,不必为其它相关类增加工作量,性能比较高。
各自的优缺点
  • JDK动态代理解决了静态代理中冗余的代理实现类问题,但是其必须基于接口实现,若没有接口,则会抛出异常。
  • CGLIB动态代理无需接口,其采用字节码增强技术,拥有不错的性能,但是相对难于理解。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一倾而尽

你的鼓励将是我最大的动力,谢谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值