[000-01-010].第18节:代理模式

我的后端学习大纲

我Spring学习大纲


1.代理模式说明:

1.1.对代理模式的理解

a.生活场景1:

牛村的牛二看上了隔壁村小花,牛二不好意思直接找小花,于是牛二找来了媒婆王妈妈。这里面就有一个非常典型的代理模式。牛二不能和小花直接对接,只能找一个中间人。其中王妈妈是代理类,牛二是目标类。王妈妈代替牛二和小花先见个面。(现实生活中的婚介所)【在程序中,对象A和对象B无法直接交互时

b.生活场景2:

你刚到北京,要租房子,可以自己找,也可以找链家帮你找。其中链家是代理类,你是目标类。你们两个都有共同的行为:找房子。不过链家除了满足你找房子,另外会收取一些费用的。(现实生活中的房产中介)【在程序中,功能需要增强时

c.西游记场景:

八戒和高小姐的故事。八戒要强抢民女高翠兰。悟空得知此事之后怎么做的?悟空幻化成高小姐的模样。代替高小姐与八戒会面。其中八戒是客户端程序、悟空是代理类、高小姐是目标类。那天夜里,在八戒眼里,眼前的就是高小姐,对于八戒来说,他是不知道眼前的高小姐是悟空幻化的,在他内心里这就是高小姐。所以悟空代替高小姐和八戒亲了嘴儿。这是非常典型的代理模式实现的保护机制。代理模式中有一个非常重要的特点:对于客户端程序来说,使用代理对象时就像在使用目标对象一样。【在程序中,目标需要被保护时

d.业务场景:

系统中有A、B、C三个模块,使用这些模块的前提是需要用户登录,也就是说在A模块中要编写判断登录的代码,B模块中也要编写,C模块中还要编写,这些判断登录的代码反复出现,显然代码没有得到复用,可以为A、B、C三个模块提供一个代理,在代理当中写一次登录判断即可。代理的逻辑是:请求来了之后,判断用户是否登录了,如果已经登录了,则执行对应的目标,如果没有登录则跳转到登录页面。【在程序中,目标不但受到保护,并且代码也得到了复用


1.2.代理模式概述:

代理模式是GoF23种设计模式之一。属于结构型设计模式。

a.代理模式的作用是:

  • 1.访问控制:代理类不让你访问目标类;
    • 为其他对象提供一种代理以控制对这个对象的访问。
    • 在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。
  • 2.功能增强:在原有的功能上增加额外的功能:
    • 代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务
  • 3.连通两个对象交互时:
    • A对象和B对象无法直接交互的时候,可以使用代理对象

通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

b.代理模式中的角色:

  • 1.代理对象(代理主题)
  • 2.目标对象(真实主题)
  • 3.代理类和目标类的公共接口(抽象主题):客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。

c.代理模式的类图:

在这里插入图片描述

d.代理模式的分类:

  • 1.静态代理
  • 2.动态代理
    • Jdk动态代理
    • CGLib动态代理(子类代理)

2.静态代理:

2.1.静态代理的特点

  • 1.目标对象和代理对象必须实现同一个接口;
  • 2.目标对象必须实现接口;
  • 3.代理对象在程序运行前就必须已经存在;
  • 4.可以灵活的进行目标对象的切换,但是不可以灵活的进行功能的处理;

2.2.代码分析静态代理:

a.业务编码如下:

  • 1.定义OrderService接口:
package com.jianqun.mall.service;
public interface OrderService {
    /**
     * 生成订单
     */
    void generate();

    /**
     * 查看订单详情
     */
    void detail();

    /**
     * 修改订单
     */
    void modify();
}

  • 2.OrderService接口的实现类
package com.jianqun.mall.service.impl;
import com.powernode.mall.service.OrderService;
public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
}

  • 其中Thread.sleep()方法的调用是为了模拟操作耗时。
    项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。
  • 于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。该怎么做呢?

b.传统解决方案:

第一种方案:直接修改Java源代码,在每个业务方法中添加统计逻辑,如下:

  • 1.需求可以满足,但显然是违背了OCP开闭原则。这种方案不可取。
package com.jianqun.mall.service.impl;

import com.jianqun.mall.service.OrderService;

public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }
}

第二种方案:编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:

package com.jianqun.mall.service.impl;

public class OrderServiceImplSub extends OrderServiceImpl{
    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        super.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        super.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        super.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }
}

c.两种解决方案分析:

  • 1.这种方式可以解决,但是存在两个问题:
    • 第一个问题:假设系统中有100个这样的业务类,需要提供100个子类,并且之前写好的创建Service对象的代码,都要修改为创建子类对象。
    • 第二个问题:由于采用了继承的方式,导致代码之间的耦合度较高。

d.使用静态代理解决:

代码恢复到原始状态,在这些代码中已经含有了:OrderService 接口和OrderServiceImpl 实现类。在这里我们后面会把OrderService 接口作为代理对象和目标对象的公共接口:、 把OrderServiceImpl 类作为目标类

  • 1.现在来定义个代理类OrderServiceProxy ,这个代理对象和目标对象一样,都要实现公共的接口:OrderService接口
  • 2.如下是新定义的代理类OrderServiceProxy ,代理类中的方法都称之为代理方法,客户端在使用代理对象的时候就像在使用目标对象一样
package com.jianqun.mall.service;

public class OrderServiceProxy implements OrderService{ // 代理对象

	// 将目标对象作为代理对象的一个属性,这种叫做关联关系,比上面继承关系的关联程度低。
    // 代理对象中含有目标对象的引用,关联关系,关联关系:has a
    
    // 注意:这里要写一个公共的接口类型。因为公共接口的耦合度低
    private OrderService orderService;

    // 通过构造方法,在代理对象创建的时候,会将目标对象传递给代理对象
    public OrderServiceProxy(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }
}

这种方式的优点:符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的。

  • 2.编写客户端程序:
package com.powernode.mall;

import com.powernode.mall.service.OrderService;
import com.powernode.mall.service.OrderServiceProxy;
import com.powernode.mall.service.impl.OrderServiceImpl;

/**
 * @author 动力节点
 * @version 1.0
 * @className Client
 * @since 1.0
 **/
public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        OrderService proxy = new OrderServiceProxy(target);
        // 调用代理对象的代理方法
        proxy.generate();
        proxy.modify();
        proxy.detail();
    }
}

以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口OrderServiceImpl是目标类OrderServiceProxy是代理类

e.反思思考:

  • 1.如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸
  • 2.动态代理可以解决类爆炸这个问题。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用。

3.动态代理:

3.1.动态代理概念:

  • 1.在程序运行阶段,在内存中动态生成代理类,被称为动态代理
  • 2.动态代理目的是为了减少代理类的数量。解决代码复用的问题。

3.2.在内存当中动态生成类的技术常见的包括:

  • 1.JDK动态代理技术:只能代理接口。
  • 2.CGLIB动态代理技术
    • CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口
    • 它既可以代理接口,又可以代理类(底层是通过继承的方式实现的)
    • 性能比JDK动态代理要好(底层有一个小而快的字节码处理框架ASM)
  • 3.Javassist动态代理技术
    • Javassist是一个开源的分析、编辑和创建Java字节码的类库。
    • 是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。
    • 它已加入了开放源代码JBoss 应用服务器项目
    • 通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

3.3.JDK动态代理:

.我们还是使用静态代理中的例子:一个接口和一个实现类

a.编码实现JDK动态代理:

  • 2.OrderService接口
package com.jianqun.mall.service;

public interface OrderService {
    /**
     * 生成订单
     */
    void generate();

    /**
     * 查看订单详情
     */
    void detail();

    /**
     * 修改订单
     */
    void modify();
}

  • 3.OrderService接口实现类
package com.jianqun.mall.service.impl;

import com.jianqun.mall.service.OrderService;

// 目标对象
public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
}

b.代码分析:

我们在静态代理的时候,除了以上一个接口和一个实现类之外,还要写一个代理类UserServiceProxy!!!但是在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可

  • 4.书写客户端测试程序:
package com.powernode.mall;

import com.jianqun.mall.service.OrderService;
import com.jianqun.mall.service.impl.OrderServiceImpl;

import java.lang.reflect.Proxy;
public class Client {
    public static void main(String[] args) {
        // 第一步:创建目标对象
        OrderService target = new OrderServiceImpl();
        // 第二步:创建代理对象
        OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
        // 第三步:调用代理对象的代理方法
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

b.客户端代码分析:

这行代码Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象)做了两件事:

  • 第一件事:在内存中生成了代理类的字节码class
  • 第二件事:new对象了。通过内存中生成的代理类这个代码,实例化了对象

Proxy类全名:java.lang.reflect.Proxy。这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。

Proxy.newProxyInstance()方法中的参数:

  • 1.newProxyInstance:
    • 翻译为 新建代理对象,也就是说调用这个方法就可以创建代理对象了,
  • 2.newProxyInstance()方法有三个参数:Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器)
    • 第一个参数:类加载器
      • 在内存中生成了字节码也是class文件,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
    • 第二个参数:接口类型
      • 代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
    • 第三个参数:调用处理器
      • 这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。在这个接口实现类中写的就是增强代码

c.编写:java.lang.reflect.InvocationHandler接口的实现类

所以接下来我们要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:

  • 1.TimerInvocationHandler.java
    • 这是专门负责记时的一个调用处理器对象
    • 在这个调用处理器当中编写记时相关的增强代码
    • 这个调用处理器只需要写一个就可以
package com.jianqun.mall.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TimerInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}
  • 2.InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:
    • invoke方法JDK负责调用的,所以调用这个方法的时候会自动的给我们传过来这三个参数,我们就可以在invoke方法的大括号内直接使用
    • 第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,就通过这个参数来使用。
    • 第二个参数:Method method。目标方法。
    • 第三个参数:Object[] args。目标方法调用时要传的参数

我们将来肯定是要调用“目标方法”的,但要调用目标方法的话,需要“目标对象”的存在,
“目标对象”从哪儿来呢?我们可以给TimerInvocationHandler提供一个构造方法,可以通过这个构造方法传过来“目标对象”,代码如下:

  • 3.建目标对象:
package com.jianqun.mall.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
    // 目标对象
    private Object target;

    // 通过构造方法来传目标对象
    public TimerInvocationHandler(Object target) {
        this.target = target;
    }

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

  • 4.通过目标对象在invoke()方法中调用目标方法了,代码如下:
package com.jianqun.mall.service;

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

public class TimerInvocationHandler implements InvocationHandler {
    // 目标对象
    private Object target;

    // 通过构造方法来传目标对象
    public TimerInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       // 这个地方就编写增强带啊就可以了
       
		// 目标执行之前增强。
        long begin = System.currentTimeMillis();
        // 调用目标对象的目标方法
        Object retValue = method.invoke(target, args);
        // 目标执行之后增强。
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
        // 一定要记得返回哦。
        return retValue;
    }
}

  • 4.到此为止,调用处理器就完成了。接下来,应该继续完善客户端程序
package com.jianqun.mall;
import com.powernode.mall.service.OrderService;
import com.powernode.mall.service.TimerInvocationHandler;
import com.powernode.mall.service.impl.OrderServiceImpl;

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                                                                                target.getClass().getInterfaces(),
                                                                                new TimerInvocationHandler(target));
        // 调用代理对象的代理方法
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

那个InvocationHandler接口中的invoke()方法没看见在哪里调用呀?

  • 5.注意:当调用代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法会被调用:
    在这里插入图片描述

虽然写一个接口的实现类,但是我们可以看到,不管你有多少个Service接口,多少个业务类,这个TimerInvocationHandler接口只需要写一次就行了,代码得到复用了!!!!而且最重要的是,以后程序员只需要关注核心业务的编写了,像这种统计时间的代码根本不需要关注。因为这种统计时间的代码只需要在调用处理器中编写一次即可。到这里,JDK动态代理的原理就结束了。

不过我们看以下这个代码确实有点繁琐,对于客户端来说,用起来不方便:

在这里插入图片描述

我们可以提供一个工具类:ProxyUtil,封装一个方法

package com.jianqun.mall.util;

import com.jianqun.mall.service.TimerInvocationHandler;

import java.lang.reflect.Proxy;

public class ProxyUtil {
    public static Object newProxyInstance(Object target) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), 
                new TimerInvocationHandler(target));
    }
}

这样客户端代码就不需要写那么繁琐了:

package com.powernode.mall;

import com.powernode.mall.service.OrderService;
import com.powernode.mall.service.TimerInvocationHandler;
import com.powernode.mall.service.impl.OrderServiceImpl;
import com.powernode.mall.util.ProxyUtil;

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);
        // 调用代理对象的代理方法
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

3.4.CGLIB动态代理:

CGLIB既可以代理接口又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰
使用CGLIB,需要引入它的依赖:

  • 1.引入依赖:
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
  • 2.准备一个没有实现接口的类,作为目标类,如下:
package com.powernode.mall.service;
public class UserService {
    public void login(){
        System.out.println("用户正在登录系统....");
    }
    public void logout(){
        System.out.println("用户正在退出系统....");
    }
}
  • 3.使用CGLIB在内存中为UserService类生成代理类,并创建对象:
package com.powernode.mall;
import com.powernode.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;

public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        // 这个对象是CGLB库当中的核心对象,就是依靠它来生成代理类
        Enhancer enhancer = new Enhancer();
        // 告诉cglib父类是谁,就是获取目标类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口,等同于JDK动态代理中调用处理器:InvocationHandler
        enhancer.setCallback(方法拦截器对象);
        // 创建代理对象
        // 这一步会做两件事:
        // 第一件事:在内存中生成UserService 类的子类,其实就是代理类的字节码
        // 第二件事:创建代理对象
        
        UserService userServiceProxy = (UserService)enhancer.create();	//生成源码,编译class,加载到JVM,并创建代理对象

        userServiceProxy.login();
        userServiceProxy.logout();
    }
}

与JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是net.sf.cglib.proxy.MethodInterceptor

  • 1.编写MethodInterceptor接口实现类:
package com.powernode.mall.service;

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

import java.lang.reflect.Method;

public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return null;
    }
}
  • 2.MethodInterceptor接口中有一个方法intercept(),该方法有4个参数, 在MethodInterceptor的intercept()方法中调用目标以及添加增强
    • 第一个参数:目标对象
    • 第二个参数:目标方法
    • 第三个参数:目标方法调用时的实参
    • 第四个参数:代理方法
package com.powernode.mall.service;

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

import java.lang.reflect.Method;

public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前增强
        long begin = System.currentTimeMillis();
        // 调用目标对象的目标方法
        Object retValue = methodProxy.invokeSuper(target, objects);
        // 后增强
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "毫秒");
        // 一定要返回
        return retValue;
    }
}
  • 3.回调已经写完了,可以修改客户端程序了:
package com.powernode.mall;

import com.powernode.mall.service.TimerMethodInterceptor;
import com.powernode.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;
public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        enhancer.setCallback(new TimerMethodInterceptor());
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();

        userServiceProxy.login();
        userServiceProxy.logout();
    }
}
  • 4.对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
    在这里插入图片描述

● --add-opens java.base/java.lang=ALL-UNNAMED
● --add-opens java.base/sun.net.util=ALL-UNNAMED

  • 5.测试结果:
    在这里插入图片描述
  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值