Java-代理设计模式

代理设计模式

  1. 通过对目标对象(target)进行封装, 对程序进行加强处理。例如:获得目标对象方法的运行总时长、Spring的面向切面编程。
  2. 实现原理:创建一个代理类(proxy),并持有一个目标对象,代理类不会自己实现真正服务,通过调用目标对象的相关方法来提供服务。类似于现实中介。
  3. 通过一个代理类间接访问目标对象。

静态代理

直接编写一个代理类,把源码直接编写好,编译后生成一个.class文件

简单实现

  1. 创建一个Person接口
java 复制代码
public interface Person{
    // 租房
    public void rentRoom();
}
  1. 创建租客类
java 复制代码
public class Renter implements Person{
    @Override
    public void rentRoom(){
		租房子
    }
}
  1. 创建代理类,即租客通过中间找房子
java 复制代码
public class RenterProxy implements Person{
	private Renter renter;
    @Override
    public void rentRoom(){
        中介找房子,转租给租客....
        租房子
        中介给租客钥匙,租客入住...
    }
}

租客想租房子,但是自己没有实力,就通过找一个代理(中介)来替他找房子,并租下。实际上来说最终还是租客租房子,只不过通过其他人来代替自己完成该任务,自己领包入住即可。

弊端

通过编写源码来实现代理模式,1000个类就会有一千个代理类,会产生类爆炸。

动态代理

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

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

JDK动态代理

  • 只能代理接口。

  • 在程序运行过程中创建代理类。

  • 在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

代码实现

  1. 创建一个Person接口,提供方法
java 复制代码
/**
 * 被实现的接口
 * @author hly
 * @version 1.0
 */
public interface Person {
    /**
     * 相亲方法
     *
     * */
     void blindDate();
}
  1. 创建Person的Impl实现类,并重写方法。
java 复制代码
/**
 * 一位男士要去相亲
 * @author hly
 * @version 1.0
 */
public class Man implements Person {
    /**
     * 相亲
     */
    @Override
    public void blindDate() {
        System.out.println("开始相亲.....");
    }
}
  1. 创建InvocationHandle实现类
    • 代理对象的所有方法调用都会被转发到 InvocationHandler 接口的 invoke() 方法中
java 复制代码
/**
 * @author hly
 * @version 1.0
 */
public class MyInvocationHandler implements InvocationHandler {
    // 持有目标对象(被代理的对象)
    private Object target;
    // 有参构造器,给target赋值
    public MyInvocationHandler(Object target){
        this.target = target;
    }

    /**
     * 代理类底层执行的方法。即当代理类调用目标方法时,jvm底层转发到InvocationHandle接口实现类的invoke方法
     * 再通过该方法,利用反射机制调用被代理对象的目标方法。
     * 当代理类调用该方法时就可以做一些增强操作。
     * 即代理对象就是媒婆,帮你约好人,相亲还是你自己去
     * @param proxy 代理对象,后期需要用时,直接用
     * @param method 被代理对象(目标对象)的目标方法
     * @param args 目标方法的参数列表
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用目标对象方法前的操作
        System.out.println("媒婆帮你找了一个相亲对象");
        // 将反射机制调用目标对象的目标方法(相亲方法)
        Object result = method.invoke(target, args);
        // 调用目标对象方法后的操作
        return result;
    }
}
  1. 通过java.lang.reflect.Proxy创建代理对象
java 复制代码
/**
 * @author hly
 * @version 1.0
 */
public class TestPerson {
    @Test
    public void test(){
        // 创建目标对象
        Person target = new Man();
        /*
         * 男人条件有点差,没能约到相亲对象
         * 他找了个媒婆帮他找到他的相亲对象
         * 媒婆就是那个所谓的代理类
         */
        /*
            Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),调用处理器对象)
            第一个参数:目标对象的类加载器,
            第二个参数:目标对象接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口
            第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler
         */
        //强转接口,而不是实现对象。
        // 运行类型为class jdk.proxy2.$Proxy7
        Person manProxy = ((Person) Proxy.newProxyInstance((target.getClass().getClassLoader()), target.getClass().getInterfaces(), new MyInvocationHandler(target)));
        manProxy.blindDate();
    }
}
  1. 运行效果
  • 注意 :当调用其他方法时,也有可能会调用invoke方法。
    • .getClass() 方法是获取一个对象的运行时类信息,它不会触发 invoke() 方法的执行。
    • 调用代理对象的其他方法:当你调用代理对象的非目标方法时,例如 toString(), hashCode(), equals() 等方法,都会触发 invoke() 方法的执行。
    • Object 类中的方法:如果代理的目标实现了 Object 类中的方法,如 wait(), notify(), notifyAll() 等,调用这些方法也会触发 invoke() 方法的执行。
    • 默认方法(Default Method):如果代理目标实现了接口中的默认方法,并且你调用了这些默认方法,也会触发 invoke() 方法的执行。

CGLIB代理

  • 如果被代理类没有实现接口,那么这么实现动态代理?这时候就需要用到CGLIB了。这种代理方式就叫做CGLIB代理。

  • CGLIB代理也叫作子类代理,他是通过在内存中构建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,然后加入自己需要的操作。因为使用的是继承的方式,所以不能代理final 类

    代码实现

    1. 先引入cglib依赖
    xml 复制代码
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>
    1. 准备一个没有实现类的接口
java 复制代码
/**
 * 没有实现接口的类
 * @author hly
 * @version 1.0
 */
public class UserService {
    public void login(){
        System.out.println("用户正在登录系统....");
    }

    public void logout(){
        System.out.println("用户正在退出系统....");
    }
}
  1. 使用CGLIB在内存中为UserService类生成代理类,并创建对象
java 复制代码
 public  void test01(){
        // 创建字节码增加器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        //和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,
        // 而是:net.sf.cglib.proxy.MethodInterceptor
        enhancer.setCallback(方法拦截器对象);
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userService = (UserService) enhancer.create();
        userService.login();
        userService.logout();
    }
  1. 编写MethodInterceptor即方法拦截器对象,与JDK动态代理中的InvocationHandler有异曲同工之妙。
java 复制代码
/**
 * @author hly
 * @version 1.0
 */
public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target){
        this.target =target;
    }

    /**
     * 当代理对象调用方法时,底层会走这个方法
     * @param proxy 代理对象,方便后期使用
     * @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("底层通过反射调用被代理对象的method:"+method);
        Object result = method.invoke(target, args);
        return result;
    }
}
  1. 调用
java 复制代码
    @Test
    public  void test01(){
        // 创建字节码增加器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        //和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,
        // 而是:net.sf.cglib.proxy.MethodInterceptor
        enhancer.setCallback(new MyMethodInterceptor());
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userService = (UserService) enhancer.create();
        userService.login();
        userService.logout();
    }

结果

JDK动态代理与CGLIB的区别

  • 两者都可以实现动态代理机制,在运行过程中生成代理类写入程序。
  • JDK动态代理只能代理接口类,底层通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现
  • CGLIB既可以代理类也可以代理接口,大部分情况下代理类,通过继承被代理类实现其子类,来完成代理模式。底层通过net.sf.cglib.proxy.Enhancer类,和net.sf.cglib.proxy.MethodInterceptor;接口实现
  • 在不破坏代理类源码的情况下,两者都起到了增加作用,遵循oop原则。解决静态代理带来的类爆炸问题
  • 当代理类调用目标方法时,两者底层都转发到各自特定的一个接口实现类通过反射机制,-->method.invoke方法实现,但具体实现又不一样。一个是被代理方法,另一个是代理方法
java 复制代码
//JDK动态代理
InvocationHandle
    invoke()
    	// 被代理方法
    	method.invoke(target,args);
// CGLIB
MethodInterceptor
    intercept()
    	// 代理方法
    	methodProxy.invokeSuper(o,objects);
相关推荐
艾迪的技术之路11 分钟前
redisson使用lock导致死锁问题
java·后端·面试
今天背单词了吗98030 分钟前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
天天摸鱼的java工程师32 分钟前
使用 Spring Boot 整合高德地图实现路线规划功能
java·后端
东阳马生架构1 小时前
订单初版—2.生单链路中的技术问题说明文档
java
咖啡啡不加糖1 小时前
暴力破解漏洞与命令执行漏洞
java·后端·web安全
风象南1 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
DKPT1 小时前
Java享元模式实现方式与应用场景分析
java·笔记·学习·设计模式·享元模式
Percep_gan1 小时前
idea的使用小技巧,个人向
java·ide·intellij-idea
缘来是庄1 小时前
设计模式之迭代器模式
java·设计模式·迭代器模式