Java动态代理模式深入学习

目录,更新ing,学习Java的点滴记录

  目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录

1 动态代理设计模式

1.1 动态代理意义

  • 动态代理的意义在于生成一个代理对象,来代理真实对象,从而控制真实对象的访问.

1.2 代理模式介绍

  • 说明动态代理之前,先聊聊代理模式,代理模式的原理实际是:使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理. 代理对象决定是否以及何时将方法调用转到原始对象上.
  • 比如,在一个软件公司中,有个软件工程师,客户带着需求去找公司显然不会直接和你谈,而是去找商务谈,此时客户就会认为商务就代表公司.
      在这里插入图片描述
  • 那么商务(代理对象)存在的意义是什么呢?商务可以跟客户进行谈判,商定项目的价格等,但是谈判也有可能不成功,这样客户与该公司的合作关系就终结了,这些都不需要软件工程师去处理.
  • 因此代理的作用可以理解为:在真实对象访问之前或者之后加入对应的逻辑,或者根据其他规则控制是否使用真实对象,该例子中商务控制了客户对软件工程师的访问.
  • 通过上面的描述,我们可以清楚明白,商务和软件工程师是代理和被代理的关系,客户是经过商务去访问软件工程师的.此时客户就是程序中的调用者,商务就是代理对象,软件工程师就是真实对象.我们需要在调用者调用对象之前产生一个代理对象,而这个代理对象需要和真实对象建立代理关系
  • 代理必须存在的两个步骤:
      1) 代理对象和真实对象的代理逻辑
      2) 实现代理对象的代理逻辑
  • 在Java中有多种动态代理技术,比如JDK,CGLIB,Javassist,ASM等,其中最常用的动态代理技术有两种:一种是JDK动态代理,这是JDK自带的功能;另一种是CGLIB,这是第三方提供的一个技术.目前Spring常用JDK和CGLIB,而MyBatis还使用了Javassist,它们的理念都是相似的
  • JDK和CGLIB应用场景
      如果真实对象实现了接口,则一般采用JDK的动态代理,如果真实独享没有实现接口,则只能采用CGLIB动态代理

1.3 动态代理技术之JDK动态代理

  • JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能生成代理对象.
  • 先定义一个接口,并提供其实现类
      在这里插入图片描述
  • 现在就可以进行动态代理了.按照之前提到的步骤,先要建立代理对象和真实对象的关系,然后实现代理对象的代理逻辑.共分为两个步骤.
  • 在JDK动态代理中,代理类必须实现java.lang.reflect.InvocationHandler接口,它里面定义了一个invoke方法.
      在这里插入图片描述
  • 第1步,建立代理对象和真实对象的关系,这里使用了bind方法去完成,方法里面首先用类的属性target保存了真实对象,然后通过下面代码建立并生成了代理对象
      在这里插入图片描述
  • 其中newProxyInstance方法包含3个参数:
      - 第一个是类加载器,采用了target本身的类加载器
      - 第二个是把生成的动态代理对象下挂在哪些接口下面,该写法就是将生成的代理对象放在target真实对象所实现的所有接口下.HelloWorldImpl对象的接口显然是HelloWorld,代理对象可以这样声明:HelloWorld proxy =xxxx;
      第三个是定义实现方法逻辑的代理类,this表示当前对象,它`必须实现InvocationHandler接口的invoke方法,它就是代理逻辑方法.
  • 第2步,实现代理逻辑方法.invoke方法可以实现代理逻辑,invoke方法的3个参数含义如下:
      proxy:代理对象,就是bind方法生成的对象
      method,当前调度的方法
      args:调度方法的参数
  • 当我们使用代理对象调度方法时,它就会进入到invoke方法里面,下面这行代理相当于调度真实对象的方法,方式是通过反射.(得到的method对象已经包含了要调用目标方法的信息,target表示调用方法的目标真实对象,args就是要调用方法的实参)
      在这里插入图片描述
  • 再次结合一开始给出的案例,proxy相当于商务对象,target相当于软件工程师对象,bind方法就是建立商务和软件工程师代理关系的方法.而invoke就是商务逻辑,它将控制软件工程师的访问.
  • 测试代码
      在这里插入图片描述
  • 此时,大家可以在代码中关注到,在调用sayHelloWorld()方法前后都可以自定义相关业务逻辑,甚至可以不调用sayHelloWolrd方法
  • 最后,我们通过打断点,详细看一下执行流程,断点如下:
      在这里插入图片描述
      在这里插入图片描述
  • 执行流程
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
  • 这就是JDK的动态代理,它是一种最常用的动态代理,非常重要.

1.4 动态代理技术之CGLIB动态代理

  • JDK动态代理必须提供接口才能使用,在一些不能提供接口的环境中,只能采用其他第三方技术,比如CGLIB动态代理.它的优势在于不需要接口,只要一个非抽象类就能实现动态代理
  • CGLib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口,它广泛的被许多AOP的框架使用.例如SpringAOP,实现方法拦截
  • 需要导入jar包(cglib,asm,后者是因为用到了Cglib的Enhancer类),链接:链接:https://pan.baidu.com/s/1fRyftNKs7_rwx3S6a8VKhg
    提取码:vch1
  • 创建一个非抽象类作为真实对象
      在这里插入图片描述
  • 创建CGLIB动态代理类
public class CglibProxy implements MethodInterceptor {

    /**
     * 生成CGLIB代理对象
     * @param cls  Class类
     * @return Class类的CGLIB代理对象
     */
    public Object getProxy(Class cls){
        // 1. CGLIB enhancer 增强类对象
        Enhancer enhancer = new Enhancer();
        //2. 设置增强类型
        enhancer.setSuperclass(cls);
        //3. 定义代理对象为当前对象,要求当前对象实现MethodInterceptor接口
        enhancer.setCallback(this);
        //4. 生成并返回代理对象
        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;
    }
}
  • 这里使用了CGLIB的加强这Enhancer,通过设置超类的方法(setSuperclass),然后通过setCallback方法设置哪个类为它的代理类.其中参数this就意味着当前对象为代理类,那么this就`必须实现接口MethodInterceptor,并重写其中intercept方法,最后返回代理对象.
  • 当前类的intercept方法就是代理逻辑方法.CGLIB就是通过如下代码实现的
      在这里插入图片描述
  • 测试效果
      在这里插入图片描述
  • 最后,我们通过打断点,详细看一下执行流程,断点如下:
      在这里插入图片描述
      在这里插入图片描述
  • 执行流程:
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
  • JDK动态代理和CGLIB动态代理是非常相似的,都通过某个方法生成代理对象,然后代理类分别要实现接口,而实现的接口中所定义的方法就是代理对象的逻辑方法,它可以控制对真实对象的访问.

1.5 拦截器

  • 由于动态代理一般比较难理解,JDK中提供了一个拦截器接口供我们使用,我们只需要知道拦截器接口的方法,含义和作用即可,无须知道动态代理怎么实现的.
  • 现在用JDK动态代理实现一个拦截器的逻辑,先定义一个拦截器接口Interceptor
     &emspp;在这里插入图片描述
  • 该拦截器接口中定义了3个方法:before,around,after方法,分别对这些方法进行一些逻辑定义
      3个方法参数为:proxy代理对象,target真实对象,method方法,args运行方法参数
      before方法返回boolean值,它在真实对象前调用.当返回true时,则反射真实对象的方法,当返回false时,则调用around方法
      在before方法返回false的情况下,调用around方法
      在反射真实对象方法或者around方法执行结束后,调用after方法
  • 实现该接口的实现类–MyInterceptor
      在这里插入图片描述
  • 该实现类实现了所有的Interceptor接口方法,使用JDK动态代理,就可以去实现这些方法在适当的时候调用逻辑了.
  • 创建一个真实对象HelloWorld接口和HelloWorldImpl
      在这里插入图片描述
  • 创建JDK代理类
public class JdkProxyInterceptor implements InvocationHandler {

    private Object target;//真实对象
    private String interceptorClass =null;//拦截器全限定名

    // 构造方法
    public JdkProxyInterceptor(Object target, String interceptorClass) {
        this.target = target;
        this.interceptorClass = interceptorClass;
    }

    /**
     * 绑定委托对象,并返回一个代理对象
     * @param target    真实对象
     * @param interceptorClass  拦截器全类名
     * @return  返回代理对象
     */
    public static Object bind(Object target,String interceptorClass){
        //生成代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkProxyInterceptor(target, interceptorClass));
    }

    /**
     * 通过代理对象调用方法
     * @param proxy 代理对象
     * @param method    被调用方法
     * @param args 方法参数
     * @return  返回调用方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (interceptorClass==null){
            //没有设置拦截器则直接反射被调用方法
            return method.invoke(target, args);
        }
        Object result=null;
        //通过反射生成拦截器
        MyInterceptor interceptor = (MyInterceptor) Class.forName(interceptorClass).newInstance();
        //调用前置方法
        if (interceptor.before(proxy, target, method, args)){
            //反射被调用方法
            result = method.invoke(target, args);
        }else{
            interceptor.around(proxy, target, method, args);
        }
        //调用后置方法
        interceptor.after(proxy, target, method, args);
        return result;
    }
}
  • 代理类中的构造器有两个属性,一个是target表示真实对象,另一个是字符串interceptorClass表示拦截器实现类的全限定名.
  • 执行步骤
      第一步,在bind方法中用JDK动态代理绑定了一个对象,然后返回代理对象
      第二步,如果没有设置拦截器,则直接反射真实对象的方法,然后结束,否则进行第三步
      第三步,通过反射生成拦截器,并准备使用拦截器
      第四步,调用拦截器before方法,如果返回true,反射原来的方法,否则运行拦截器的around方法
      第五步,调用拦截器的after方法
      第六步,返回结果
  • 这样的话,作为开发者只需要熟悉拦截器作用,并编写好拦截器,剩下的动态代理实现交给精通的人员去做就可以了
  • 创建测试类
      在这里插入图片描述

你可能感兴趣的:(Java设计模式)