欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

常用动态代理介绍

程序员文章站 2022-05-25 17:31:33
...

大家好今天给大家介绍一下常用的几种动态代理,希望能从这篇文章中了解一些spring中动态代理的基本原理。

 

一、背景介绍

何为代理:

假设这样一个场景,你的公司是一家软件公司,你是一位软件工程师。客户带着需求来找公司显然不会直接找你谈,而是去找商务谈,此时客户会认为商务就代表公司

客户(调用者)——》商务(代理对象)——》软件工程师(真实对象)

显然客户是通过商务去访问软件工程师的,那么商务(代理对象)的意义在于什么呢?

商务可以进行谈判,比如项目启动前的商务谈判,软件的价格,交付,进度的时间节点等,或者项目完成后的商务追讨应收账款等。

商务也有可能在开发软件之前谈判失败,此时商务就会按照公司规则和客户去终止合作关系,这些都不用软件工程师来处理。

因此,代理的作用就是,在真实对象访问之前或者之后加入对应的逻辑。或者根据其他规则控制是否使用真实对象,先然在这个例子里商务对象控制了客户对软件工程师的访问。

从上面我们知道商务和软件工程师是代理和被代理的关系,客户是经过商务去访问软件工程师的。此时客户就是软件中的调用者,商务就是代理对象,软降城市就是真实对象。我们需要在调用者调用对象之前产生一个代理对象,而这个代理对象需要和真实对象建立代理关系,所以代理必须分为两个步骤:

1.代理对象和真实对象建立代理关系

2.实现代理对象的代理逻辑方法

在java中有多种动态代理技术,比如JDK,CGLIB,Javassist,ASM,其中最常用的动态代理技术有两种:一种是JDK动态代理,这是JDK自带的功能;另一种是CGLIB,这时第三方提供的一个技术,目前,Spring常用JDK和CGLIB,而Mybatis还是用了Javassist,无论使用哪种代理技术,它们的理念都是相似的。

二、知识刨析

在介绍两种动态代理之前,我们先看一下静态代理,了解一下代理和普通的直接使用多态让父类引用指向子类对象有什么区别。

静态代理通常用于对原有业务逻辑的扩充。比如持有二方包的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这些逻辑写入二方包的方法里。所以可以创建一个代理类实现和二方方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。

这其实也就是代理模式的一种实现,通过对真实对象的封装,来实现扩展性。

一个典型的代理模式通常有三个角色,这里称之为**代理三要素**

1.共同接口

//三要素之共同接口
public interface Hello {
    void say(String name);
}

2.真实对象

//三要素之真实对象
public class HelloImpl implements Hello {
    @Override
    public void say(String name) {
        System.out.println("Hello"+name);
    }
}

3.代理对象

//三要素之代理对象
public class HelloProxy implements Hello{
    private Hello hello;
     //不是动态代理,所以直接写死就可以绑定了
    //静态代理在使用之前就已经将代理类和真实对象确定了
    //如果想要修改就会很麻烦
    public HelloProxy(){
        hello=new HelloImpl();
    }

    public void before(){
        System.out.println("静态代理前逻辑");
    }

    public void after(){
        System.out.println("静态代理后逻辑");
    }

    @Override
    public void say(String name) {
         before();
         hello.say(name);
         after();
    }
}

测试代码如下

//实现代理
public class TestStaticProxy {
    public static void main(String[] args) {
        Hello hello=new HelloProxy();
        hello.say("Tom");
        System.out.println("我是分割线——————————");
        Hello hello1=new HelloImpl();
        hello1.say("Jerry");
    }
}

结果如下

常用动态代理介绍

从上面的demo我们可以看到静态代理的缺点,就是一个功能一个代理,如果功能多了,代码就会很臃肿,而且很多都是重复代码,这绝不是我们想要的

相比静态代理,动态代理的代理类是动态的,是通过绑定过程的类加载器来加载具体类,方法也是动态的,通过反射来加载指定方法,而不像静态代理那样直接写死

JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能产生代理对象,

public interface HelloWorld {
     void sayHelloWorld();
}

然后提供实现类来实现接口

public class HelloWorldImpl implements HelloWorld {
    @Override
    public void sayHelloWorld() {
        System.out.println("Hello World");
    }
}

这是最简单的Java接口和实现类的关系,此时可以开始动态代理了。

按照我们之前的分析,先要建立起代理对象和真是服务对象的关系,然后是实现代理逻辑,所以一共分为两个步骤。

在JDK动态代理中,要实现代理逻辑类必须去实现java.lang.reflect.InvovationHandler接口,它里面定义了一个invoke方法,并提供接口数组用于下挂代理对象。

/**
 * 必须实现InvocationHandler接口,实现此接口的类在执行时会先调用invoke方法
 */
public class JdkProxyExample implements InvocationHandler{
    //真实对象
    private Object target=null;

    /**
     * 建立代理对象和真实对象的代理关系,并返回代理对象
     * @param target 真实对象
     * @return 代理对象
     */
    public Object bind(Object target){
        //首先使用类的属性target保存真实对象
        this.target=target;
        //三个参数,分别是
        //类加载器,
        //把生成的动态代理对象下挂在哪些接口下,因此没有接口的类就无法实现jdk动态代理
        //定义实现方法逻辑的代理类,this表示当前对象,它必须实现InvocationHandler的invoke方法,是代理逻辑方法的现实方法
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    /**
     * 代理方法逻辑
     * @param proxy 代理对象,上面的bind方法生成的对象
     * @param method 当前调度方法
     * @param args 当前方法参数
     * @return 代理结果返回
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("进入代理逻辑方法");
        System.out.println("在调度真实对象之前的服务");
        Object obj=method.invoke(target,args);//这一步相当于使用target去调用这个方法,参实就是后面的这个参数,如果得到了结果,就是obj
        System.out.println("在调度真实对象之后的服务");
        return obj;
    }
}

第一步,建立代理对象和真实对象的关系,这里是用了bind方法去完成的,名字可以自己取,方法里面首先用类的属性target保存了真实对象的,然后通过如下代码建立并生成代理对象。

Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);

第一个是类加载器,我们采用target本身的类加载器

第二个是把生成的动态代理对象下挂在哪些接口下,这样写就是放在target实现的接口下。

第三个是定义实现方法逻辑的代理类,this标识当前对象,它必须实现InvocationHandler接口的invoke方法,它就是代理逻辑方法的显示方法。一般谁调用此bind方法,this 就是指谁

三个参数中的前两个均未指定任何固定的类和接口,而是根据传入不同的target来指定。这就是相比静态代理的动态二字的含义

第二步,实现代理逻辑,invoke方法可以实现代理逻辑,invoke方法的3个参数含义如下

proxy,代理对象,就是bind方法生成的对象。

method,当前调度的方法

args,调度方法的参数

当我们使用了代理对象调度方法后,它就会进入到invoke方法里面,我们可以通过debug来看其过程。

Object obj=method.invoke(target,args);

这行代码相当于调度真实对象的方法,只是通过反射实现而已。

类比前面的例子,proxy相当于商务对象,target相当于软件工程师对象,bind方法就是建立商务和软件工程师代理的方法,而invoke就是商务逻辑,它将控制软件工程师的访问,

public class TestJdkProxy {

    public static void main(String[] args) {
        //因为是非静态方法所以需要有一个实例来调用绑定方法
        JdkProxyExample jdk=new JdkProxyExample();
        //绑定关系,接口是代理对象,实现类是真实对象,bind之后向上转型
        HelloWorld proxy=(HelloWorld)jdk.bind(new HelloWorldImpl());
        //当使用代理对象调用方法时
        //就会通过代理对象进入代理的逻辑方法invoke中
        //在invoke中利用了反射,使用真实对象去调用此方法
        proxy.sayHelloWorld();

    }

运行结果如下

常用动态代理介绍

可以看到其是实现了动态代理

DK动态代理必须提供接口才能使用,在一些不能提供接口的环境中,只能采用其他第三方技术,比如CGLIB动态代理。

它的优势在于不需要提供接口,只要一个非抽象类来实现动态代理。

public class ReflectServiceImpl {
    public void sayHello(String name){
        System.out.println("Hello"+name);
    }
}

这里用了CGLIB的加强者Enhancer,通过设置超类的方法(setSuperclass),然后通过setCallback方法设置哪个类为它的代理类。其中,参数为this就意味着是当前对象,那就要求用this这个对象实现接口MethodInterceptor的方法——intercept,然后返回代理对象。

public class CglibProxyExample implements MethodInterceptor{
    /**
     * 生成CGLIB代理对象
     * @param cls——Class类
     * @return Class类的CGLIB代理对象
     */
    public Object getProxy(Class cls){
        //CGLIB enhancer增强类对象
        Enhancer enhancer=new Enhancer();
        //设置增强类型,将cls作为超类,生成子类
        enhancer.setSuperclass(cls);
        //定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor
        //也就是说通过CglibProxyExample的对象来调用getProxy方法
        enhancer.setCallback(this);
        //生成并返回代理对象
        return enhancer.create();
    }

    /**
     * 代理逻辑方法
     * @param proxy 代理对象
     * @param method 方法
     * @param args 参数
     * @param methodProxy 方法代理
     * @return 代理逻辑返回
     * @throws Throwable 异常
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用真实对象前");
        //CGLIB反射调用真实对象方法
        Object result=methodProxy.invokeSuper(proxy,args);
        System.out.println("调用真实对象后");
        return result;
    }
}

那么此时当前类的intercept方法就是其代理逻辑方法

测试一下

public class TestCglibProxy {
    public static void main(String[] args) {
        CglibProxyExample cpe=new CglibProxyExample();
        //实际上这个obj指向的是Impl的子类,其继承自ReflectServiceImpl
        ReflectServiceImpl obj=(ReflectServiceImpl) cpe.getProxy(ReflectServiceImpl.class);
        obj.sayHello("JAVA");
        System.out.println("_____________________________");
        ReflectServiceImpl rsi=new ReflectServiceImpl();
        rsi.sayHello("JAVA");
    }
}

结果如下

常用动态代理介绍

掌握了JDK动态代理就很容易掌握CGLIB的动态代理,因为二者是相似的。他们都是用getProxy方法生成代理对象,制定代理的逻辑类,而代理逻辑类要实现一个接口的一个方法,那么这个接口定义的方法就是代理对象的逻辑方法,它可以控制真实对象的方法

三、常见问题

1.两种动态代理原理有什么区别?

2.如何强制使用CGLIB实现AOP?

四、解决方案

1.两种动态代理原理有什么区别?

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

a、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 

b、如果目标对象实现了接口,可以强制使用CGLIB实现AOP 

c、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

2.如何强制使用CGLIB实现AOP?

(1)添加CGLIB库,SPRING_HOME/cglib/*.jar

 (2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

五、参考文献

https://blog.csdn.net/wangqyoho/article/details/77584832

《互联网轻量级框架整合开发》

 

今天的分享就到这了,大家有什么问题可以评论里留言,觉得不错也可以点个赞哦~

相关标签: t da