Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理

 

前言

 

代理 代理 代理 代理 代理 代理 代理 代理 代理 代理
代理 代理 代理 代理 代理 代理 代理 代理 代理 代理

很多人至今都是看到 代理就懵, 静态代理、动态代理、JDK动态代理、CGLIB动态代理...... 

知道AOP,知道增强,但是还是对代理模式支支吾吾? 这是因为你没有用心去了解过它......

 

您这种症状持续多久了?

现在看这篇文章,还来得及。

该篇文章咱们将会一起通过手敲实例代码,去了解和使用代理模式,实现静态代理、JDK动态代理、CGLIB动态代理。

实现完再回头看看,应该就不会支支吾吾了。

 


进入正文

一、代理模式

代理模式分为:

静态代理  

动态代理

先不管什么静还是动的,先知道这个代理模式的使用,是干嘛的。

文字描述:

为其他对象提供一个代理以控制对某个对象的访问。
代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。

 

 

 

听我说:

其实就是 在你调用 某个业务方法时, 在调用前 和调用后,加点东西。

加的这些东西 随时改动,也不会影响到业务方法代码的改动。 这样就很 解耦 ,就很喜欢。

就像, 调用某个业务方法前, 我加个 日志记录(记录一下调用的方法、参数、IP来源等等);

又比如说,调用某个业务方法前,我加个 权限拦截 (看看能不能给调用,判断一下token、身份、角色、ip等等)

 

从代理模式的实现效果来说,就是做到了2点:

1、可以对 业务代码(需要被代理的类)  进行  增强(附属一些逻辑代码)

2、实现代理之后 ,主业务方法不用动, 需要新增的,修改调整的一些业务,可以都丢到代理方法里面去做,这样一定程度实现了代码防入侵。

 

Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理_第1张图片

 

 

 

 

二、实例讲解

 

该篇文章使用到的实例是一个 我自己临时模拟的场景:

业务-  点餐下单

然后 运用上代理模式,实现添加一些 点餐下单 前前后后的  代码,记录日志、权限判断等等。

 

1.静态代理

1.1 就像往常一样,我们先写个接口 OrderService:

import java.util.Map;

/**
 * @Author : JCccc
 * @CreateTime : 2020/4/16
 * @Description :
 **/
public interface OrderService {

    //下单点餐
    Map executeFoodOrder(String userName,Map foodMap);

}

1.2 就像往常一样,接口 OrderService的实现类 OrderServiceImpl:

import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author : JCccc
 * @CreateTime : 2020/4/16
 * @Description :
 **/
@Service
public class OrderServiceImpl implements OrderService {


    @Override
    public  Map executeFoodOrder(String userName,Map foodMap) {

        System.out.println("*********【下单主业务】*********");
        Map resultMap=new HashMap();

        for (Map.Entry m : foodMap.entrySet()) {
            String foodName=m.getKey();
            Integer foodCount= m.getValue();
            //模拟一些杂七杂八的业务
            System.out.println("【下单主业务】"+foodName+"---模拟这个菜的一些杂七杂八的业务");

            resultMap.put(foodName,"ok");
        }
        return resultMap;
    }


}

1.3 现在我们有个业务需求,就是记录客户点餐信息,根据传入的用户名,去数据库查出来,然后跟点的菜啥的简单做个日志记录。

那么静态代理的方式是这样实现(这里介绍的是通过实现业务接口方式,而其实还可以简单通过继承来实现):

 

静态代理类   OrderServiceLogProxy.java :

import com.ilas.testboot.proxy.OrderService;
import java.util.Map;

/**
 * @Author : JCccc
 * @CreateTime : 2020/4/21
 * @Description :
 **/
public class OrderServiceLogProxy implements OrderService {

    private OrderServiceImpl orderService;

    public OrderServiceLogProxy(OrderServiceImpl orderService) {
        this.orderService = orderService;
    }

    @Override
    public Map executeFoodOrder(String userName, Map foodMap) {
        System.out.println("+静态代理LogProxy");
        System.out.println("+下单前我们做点什么.");
        System.out.println("+正在获取用户:"+userName+"用户信息......");
        System.out.println("+正在记录用户:"+userName+"选择的菜品:"+foodMap.toString());
        System.out.println("+准备执行主业务");
        Map map = orderService.executeFoodOrder(userName, foodMap);
        System.out.println("+下单后我们做点什么.");
        System.out.println("+记录用户"+userName+"下单后的详情信息:"+map.toString());
        return map;

    }
}

 

简单分析下,我们新创建了一个实现下单点餐接口的类,在这类里面,重写下单方法executeFoodOrder,在调用下单方法时加上一些日志记录等等的代码:

Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理_第2张图片

OK,有可能这样写,你还是不能看出所为的代理结构,那么如果说我 写成这样呢?

是不是这样看起来就有感觉了。(该篇文章后面的JDK动态代理、CGLIB动态代理,我就不一一抽出来写成 before和 after了)

Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理_第3张图片

 

调用方式:

    public static void main(String[] args) {
        
        //静态代理调用方式
        OrderServiceImpl orderService=new OrderServiceImpl();
        OrderServiceLogProxy orderServiceLogProxy=new OrderServiceLogProxy(orderService);

        String userName="JCccc";
        Map orderMap=new HashMap<>();
        orderMap.put("白米饭",2);
        orderMap.put("红烧肉",1);
        orderMap.put("水煮鱼",1);
        orderMap.put("番茄炒蛋",1);
        Map resultMap = orderServiceLogProxy.executeFoodOrder(userName, orderMap);

        System.out.println("静态代理方法执行完毕,结果:"+resultMap);

    }

可以看下控制台的输出情况:

蓝色:静态代理类做的事情 (可以看到在主业务执行的前后)

红色:原本主业务做的事情

Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理_第4张图片

 

静态代理使用感觉:

实现代理的代码都是提前设计好,而且还是一一对应起来,OrderService 的代理是 OrderServiceLogProxy;

而且实现代理的方法也是一一对应。

那么如果 需要记录日志 的不止是OrderService ,再来一个 UserService呢? 再来一个 GameService呢?

这样一来,需要我们写死的代码就非常非常多。

 

这就是所谓的 静态 的不好之处,于是乎有了 动态代理(能够根据我们传入的 需要被代理类,实现代理)

 

 

2.动态代理

动态代理有两种:

JDK动态代理

CGLIB动态代理

 

2.1 JDK动态代理

基于前面创建好的 OrderService 和 OrderServiceImpl ,我们接下来采取 JDK动态代理的方式去实现我们的日志添加。

(已经有种解耦的意味了吧,我们根本不需要动原本的业务接口和业务实现类,代理模式的作用无形中已经慢慢崭露出来了)

创建咱们的日志动态代理类,DynamicsLogProxy.java :

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

/**
 * @Author : JCccc
 * @CreateTime : 2020/4/21
 * @Description :
 **/
public class DynamicsLogProxy implements InvocationHandler {

    Object obj;
    //绑定委托对象,并返回代理类
    public Object bind(Object obj)
    {
        this.obj = obj;
        //绑定该类实现的所有接口,取得代理类
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("+静态代理LogProxy");
        System.out.println("+下单前我们做点什么.");
        System.out.println("+正在获取目前传入的参数:"+Arrays.toString(args));
        System.out.println("+正在记录.....");
        System.out.println("+准备执行主业务");
        Object res = method.invoke(obj, args);
        System.out.println("+下单后我们做点什么.");
        System.out.println("+可得到业务方法执行后结果"+res.toString());
        System.out.println("+记录.......");

        return res;



    }
}

 

JDK动态代理的一些代码剖析:

 

1. JDK动态代理类 必须 实现 InvocationHandler 接口:

2.被代理的类 必须 存在 类 和实现的接口 (OrderService  和  OrderServiceImpl) 

3.需要把 被代理对象传过去,因为动态生成代理类时要使用到:

Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理_第5张图片

3.使用jdk的api 中的 java.lang.reflect.Proxy 帮我们即时创建 代理实例对象:

前面两个参数,可以理解为把需要被代理的类相关信息传过去,最后一个参数  InvocationHandler h,

这玩意讲究。

咱们的DynamicsLogProxy 实现了这玩意,那么Proxy帮我们创建出来的一个代理实例对象,自然是不清楚咱们要代理的具体方法。

而invoke就是帮我们根据传入的参数:

proxy表示动态代理类实例,method表示调用的方法,args表示调用方法的参数

实现 调用 目标对象(被代理的类)的 目标方法。

 

简单点理解:

Proxy会帮我们 动态创建一个代理类,  这个代理类 代理的谁, 是我们传入的。

而InvocationHandler 的invoke方法,随时待命从代理类中去调用 被代理类的对应方法。

 

调用方式:

 

    public static void main(String[] args) {

        //动态代理调用方式
        OrderService dynamicsOrderProxy = (OrderService)new DynamicsLogProxy().bind(new OrderServiceImpl());

        String userName="JCccc";
        Map orderMap=new HashMap<>();
        orderMap.put("白米饭",2);
        orderMap.put("红烧肉",1);
        orderMap.put("水煮鱼",1);
        orderMap.put("番茄炒蛋",1);
        //通过动态代理类去调用不同方法
        Map resultMap = dynamicsOrderProxy.executeFoodOrder(userName, orderMap);

        System.out.println("动态代理方法执行完毕,结果:"+resultMap);

    }

可以看下控制台的输出情况:

Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理_第6张图片

 

JDK的动态代理使用感觉:

 

挺好,我们只需要编写一个日志代理类之后;

想这个日志代理类与哪个 需要被代理的类绑定, 我们就动态传入;

想要在被代理的类哪个方法前后 去嵌入 一些东西, 我们就动态调用方法;

很灵活,不像静态代理那样死死的。

但是,显然我们在使用JDK动态代理的时候,我们发现了,Proxy在帮我们动态即时创建 代理类的时候,要求我们传入

Class[] interfaces  

那就是意味着,我们的需要被代理的类,必须实现接口:

因为JDK动态代理其实也是悄咪咪帮我们创建一份代理类代码,也帮我们实现同样的接口。

也就是说, 如果你的 被代理类, 没有实现接口,那么就没办法使用 JDK动态代理。

那怎么办?  CGLIB动态代理出来了,它说 我不关心你是否实现接口,只需要告诉我 哪个类需要被代理。

 

2.2 CGLIB动态代理

 

上面咱们已经说了,CGLIB动态代理只关心 哪个类需要被代理。

因为这个家伙其实是悄咪咪地帮我们 针对需要被代理的类 创建一个 代理子类。根据调用的方法,拦截调用到父类的方法。

 

看CGLIB动态代理实现,CglibLogProxy.java:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * @Author : JCccc
 * @CreateTime : 2020/4/21
 * @Description :
 **/

public class CglibLogProxy  implements MethodInterceptor {
    /**
     * 自定义方法:利用Enhancer类生成代理类
     *
     * @param clazz
     * @param 
     * @return
     */
    public  T getObjByEnhancer(Class clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        T res = (T) enhancer.create();
        return res;
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println(" +CGLIB日志代理CglibLogProxy");
        System.out.println("+CGLIB日志代理获取到执行的方法名" + method.getName());
        System.out.println("+做一些日志记录......");
        System.out.println("+做一些日志记录...");
        Object res = methodProxy.invokeSuper(o, objects);
        System.out.println("+主业务方法执行后我们做点什么.");
        System.out.println("+可得到业务方法执行后结果"+res.toString());
        System.out.println("+记录.......");
        return res;

    }
}

实现代码简单剖析:

1. implements MethodInterceptor    cglib动态代理类实现了这个方法拦截

2. intercept 方法,这个也就是 提供地方给咱们 添加 日志记录、权限判断等等的地方

3. Enhancer

Enhancer  这个玩意是CGLIB动态代理类的核心。

它的职责是,创建出一个子类(代理类), 然后将我们传入的 被代理类  设置成 SuperClass 父类 。

这个子类(代理类) ’继承‘ 了父类( 被代理类)的 所有 可以公开的方法 ,然后设置拦截回调的类(父类),子类中拦截父类方法并织入方法增强逻辑。

ps:cglib动态代理不能代理声明为final类型的类和方法   

 

事不宜迟,我们贴出CGLIB动态代理调用方式:

    public static void main(String[] args) {


        CglibLogProxy  cglibLogProxy=new CglibLogProxy();
        OrderServiceImpl   orderProxy=   cglibLogProxy.getObjByEnhancer(OrderServiceImpl.class);

        String userName="JCccc";
        Map orderMap=new HashMap();
        orderMap.put("白米饭",2);
        orderMap.put("红烧肉",1);
        orderMap.put("水煮鱼",1);
        orderMap.put("番茄炒蛋",1);
        Map map = orderProxy.executeFoodOrder(userName, orderMap);
        System.out.println(map.toString());
    }

注意看, 调用方法的时候,用的是什么?

 

orderProxy.executeFoodOrder(userName, orderMap);

没错,跟JDK动态代理看似一样直接调用 我们执行的业务方法,

但是接下来就不一样了,再看看cglib动态代理类的实现代码:

Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理_第7张图片

没错,orderProxy是我们通过Enhancer去生成的,特意设置了回调拦截方法。从拦截的这个意思去看,就更有AOP的意味,更有代理的感觉了。

调用父类方法(被代理类)前,拦住, 先调用咱们自己写的 拦截方法, 啥时候完事了,再通过 methodProxy.invokeSuper 切回到主业务方法去。

可以看到控制台输出:

Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理_第8张图片

 

简单的介绍就到这里吧。

 


总结

静态代理 和 动态代理  对比 :

静态代理是 一开始咱们编码的时候,已经写死了,哪个代理类对应哪个被代理类。

而动态代理都是 运行时动态即时创建的。

疑问:

那么是不是静态代理就肯定不如动态代理?

那肯定不能这么说,人家静态代理一开始就码好了,运行调用的时候,肯定就快啊。

而动态代理时运行时才去开始,所以自然也会慢。

 

 

JDK动态代理 和 CGLIB动态代理:

JDK动态代理,代理的类 必须实现接口,没有实现接口的类 无法使用JDK动态代理。

CGLIB动态代理,代理的类必须能被子类继承使用(方法也得能被重写),所以不能代理声明为final类型的类和方法。

 

 

 

 

多级代理实现方式:

补充:

如果我们想实现, 在调用下单点餐这个方法时,  不单是 进行日志记录 ,我们还想进行 一个营业判断或者说权限判断。

(JDK动态代理-多层级代理)

这时就会引出一个, 多级代理的概念。

也就是说,

对应的动态代理切点不止一个:

我们需要的流程是, 当通过代理方式去调用主业务方法 executeFoodOrder ,先给我进行 日志记录, 再给我进行 营业时间判断(可以类比成一些权限判断、角色判断、身份判断等等),时间判断符合在营业时间时,最后再给我去调用主业务方法 executeFoodOrder 。

 

实现代码,两个动态代理类 

DynamicsLogProxy.java:

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

/**
 * @Author : JCccc
 * @CreateTime : 2020/4/21
 * @Description :
 **/
public class DynamicsLogProxy implements InvocationHandler {

    Object obj;
    //绑定委托对象,并返回代理类
    public Object bind(Object obj)
    {
        this.obj = obj;
        //绑定该类实现的所有接口,取得代理类
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("+JDK动态代理LogProxy");
        System.out.println("+下单前我们做点什么.");
        System.out.println("+正在获取目前传入的参数:"+Arrays.toString(args));
        System.out.println("+正在记录.....");
        System.out.println("+准备执行主业务");
        Object res = method.invoke(obj, args);
        System.out.println("+JDK动态代理LogProxy下单后我们做点什么.");
        System.out.println("+可得到业务方法执行后结果"+res.toString());
        System.out.println("+记录.......");

        return res;



    }
}

 

DynamicsOperateProxy.java:

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


/**
 * @Author : JCccc
 * @CreateTime : 2020/4/21
 * @Description :
 **/
public class DynamicsOperateProxy implements InvocationHandler {

    Object obj;

    //绑定委托对象,并返回代理类
    public Object bind(Object obj)
    {
        this.obj = obj;
        //绑定该类实现的所有接口,取得代理类
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                this);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("-----JDK动态代理 OperateProxy 执行主方法前-----");

        Object res =null;
        if (method.getName().equals("executeFoodOrder")){

            System.out.println("-----检测当前时需要下单点餐......");
            System.out.println("-----执行相关判断......");
            System.out.println("-----查看该店是否营业......");
            System.out.println("-----检测XXXXX......");
            System.out.println("-----可以调用主业务");

            // 如果不符合要求, 不执行 method.invoke(obj, args);
            //做拦截处理即可,抛出异常或者重定向等等
            res=   method.invoke(obj, args);
            System.out.println("-----JDK动态代理OperateProxy 执行主方法后----");

        }
        
        return res;


    }
}

 

注意此时 ,多级代理的 调用方式:

    public static void main(String[] args) {


        OrderService operateProxy= (OrderService) new DynamicsOperateProxy().bind(new OrderServiceImpl());

        OrderService operateAndLogProxy = (OrderService)new DynamicsLogProxy().bind(operateProxy);

        Map orderMap=new HashMap();
        orderMap.put("白米饭",2);

        orderMap.put("红烧肉",1);
        orderMap.put("水煮鱼",1);
        orderMap.put("番茄炒蛋",1);
        Map result = operateAndLogProxy.executeFoodOrder("JCccc",orderMap);

        System.out.println(result.toString());

    }

细看:

 

我们先是让 需要被代理的类 绑定到  DynamicsOperateProxy 上,

然后绑定完创建出来的类,我们继续 绑定到 DynamicsLogProxy 上,然后创建我们最后的多级动态代理类。

 

看控制台输出:

注意到此时的调用执行顺序,

第一步,进入到了log动态代理,执行 调用主业务方法 前 的 代码;

第二步,即将切回到主业务方法时,发现还有一级代理,operate动态代理,那么进入 operate动态代理 ,执行相关的 执行 调用主业务方法 前 的 代码;

第三步,切入主业务方法,拿到主业务方法的返回结果,回到 operate动态代理,执行 主业务方法调用后的 代码;

第四步,把 ‘拿到主业务方法的返回结果’ 往外面一层传递,也就是传递给 log动态代理,  log动态代理执行 调用主业务方法 后 的 代码。

第五步,最后把最终结果返回到最外面去。

Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理_第9张图片

所以多层级代理使用的时候,需要自己多注意动态代理实现的顺序,记得多测试看看流程。

 

那么可以看到这种多层级代理的实现方式是使用的JDK动态代理,那么如果我们想用CGLIB动态代理方式去实现这种多层级代理呢?

对于这个我也思索了很久,还未能找到一些实现起来很舒服的方式。

如果是基于Spirng框架或者是Springboot,那么实现多层级代理,直接去使用  注解AOP方式去,多重拦截判断算了。 

因为SpringBoot默认的AOP实现就是使用的CGLIB动态代理代理,当然你想切换成JDK动态方式也是可以的,改下配置项就像,但是如果你真的改成JDK动态代理方式,记得,你所编写的需要被代理的类,必须实现接口。

springboot使用注解简单实例介绍:

https://blog.csdn.net/qq_35387940/article/details/85261279

 

 


就到此吧,看完这篇,手动去运用JDK动态代理、CGLIB动态代理,应该不是问题了。下次带看到什么代理代理的,你就不用再懵了。

 

你可能感兴趣的:(JDK动态代理,CGLIB动态代理,静态代理,JAVA)