JAVA代理模式 --- 静态代理、JDK动态代理、CGLib动态代理

声明:本文中大部分概念内容转载至CSDN博主「电竞丶小松哥」

原文链接:https://blog.csdn.net/qq_39038178/article/details/125282017

目录

一、代理模式

1.1  什么是代理模式?

1.2 代理模式的作用

1.3 代理模式的分类

1.4 编程思想

二、静态代理

2.1 什么是静态代理

2.2 静态代理的特点

 2.3 静态代理的缺陷

2.4 静态代理案例

三、JDK动态代理

2.1 什么是动态代理

2.2 JDK动态代理

2.3 JDK动态代理用到的类和接口

2.4 JDK动态代理案例

2.5 JDK动态代理原理

 四、CGLib动态代理

 4.1 什么是CGLib动态代理?

4.2 CGLib动态代理的特点

 4.3 CGLib动态代理实现步骤

4.4 CGLib动态代理案例

五、JDK动态代理与CGLib动态代理的区别


一、代理模式

1.1  什么是代理模式?

        代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,

一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户和目标对象之间起到中介的作用。换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。客户类真正想要访问的对象是目标对象,但客户类真正可以访问的对象是代理对象。

客户类对目标对象的访问是通过访问代理对象来实现的。当然,代理类与目标类要实现同一个接口。

        例如:有A、B、C三个类,A原来可以调用C类的方法,现在因为某种原因C类不允许A类调用其方法,但B类可以调用C类的方法。A类通过B类调用C类的方法。这里B是C的代理。A通过代理B访问C。————>目标对象不可访问,通过代理对象增强功能访问。

1.2 代理模式的作用

(1)控制目标对象的访问

(2)增强功能

1.3 代理模式的分类

(1)静态代理——>静态代理要求目标对象和代理对象实现同一个业务接口。代理对象中的核心功能是由目标对象来完成,代理对象负责增强功能。

(2)动态代理——>JDK动态代理 + CGLib动态代理

1.4 编程思想

不能随便修改源码,如果需要修改源码,通过代理的方式实现功能的拓展。

二、静态代理

2.1 什么是静态代理

        代理模式是在不修改目标对象(被代理对象)的基础上,通过代理对象(扩展代理类),进行一些功能的扩展与增强——>静态代理是在不改变源代码的基础上增加新的功能。  

2.2 静态代理的特点

(1)静态代理要求目标对象和代理对象实现同一个业务接口。代理对象中的核心功能是由目标对象来完成,代理对象负责增强功能。

(2)目标对象(被代理对象)必须实现接口。

(3)代理对象在程序运行前就已经存在——>区别于动态代理

(4)支持目标对象灵活的切换,无法对功能灵活的处理——>动态代理可解决此问题。

 2.3 静态代理的缺陷

(1)代理复杂,难于管理

        代理类和目标类实现了相同的接口,每个代理都需要实现目标类的方法,这样就出现了大量的代码重复。如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现此方法。——>增加了代码维护的复杂度。 

(2)代理类依赖目标类+代理类过多

        代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理,静态代理在程序规模稍大时就无法胜任了,代理类数量过多。 

2.4 静态代理案例

需求:假设现在有针对学生Student对象的查询与新增方法,现在要求对所有方法执行前后进行增强。

(1)业务接口

public interface IStudentService {
    //查询功能
    Student queryStudent(Long id);
    //新增功能
    void addStudent();
}

 (2)目标对象

public class StudentServiceImpl implements IStudentService {
    @Override
    public Student queryStudent(Long id) {
        System.out.println(this.getClass().getSimpleName() + ": 模拟查询操作...");
        Student student = new Student();
        student.setId(id);
        student.setName("佩奇");
        student.setAge(5);
        return student;
    }

    @Override
    public void addStudent() {
        System.out.println(this.getClass().getSimpleName() + ": 模拟新增操作...");
    }
}

(3)代理对象 ---> 需要与目标对象实现同一个业务接口

public class StudentProxy implements IStudentService {

    private IStudentService studentService;

    public StudentProxy(IStudentService studentService) {
        this.studentService = studentService;
    }

    @Override
    public Student queryStudent(Long id) {
        System.out.println("方法执行前增强 ---> 前置通知");
        Student student = studentService.queryStudent(id);
        System.out.println("方法执行后增强 ---> 后置通知");
        return student;
    }

    @Override
    public void addStudent() {
        System.out.println("方法执行前增强 ---> 前置通知");
        studentService.addStudent();
        System.out.println("方法执行后增强 ---> 后置通知");
    }
}

(4)测试

@SpringBootTest()
class TestStaticProxy {

    @Test
    void test1() {
        IStudentService studentService = new StudentServiceImpl();
        StudentProxy studentProxy = new StudentProxy(studentService);
        studentProxy.addStudent();
    }
}

(5)测试结果

方法执行前增强 ---> 前置通知
StudentServiceImpl: 模拟新增操作...
方法执行后增强 ---> 后置通知

静态代理总结:

1. 代理对象需要和目标对象实现同一个业务接口;

2. 代理对象中的核心功能是由目标对象来完成,代理对象负责增强功能。

3. 静态代理的缺陷:

        (1)无法对功能进行灵活的处理。如果需要对业务接口中的多个功能进行同样的增强,那么在代理对象中,就会存在大量的重复性代码 ---> 比如此案例中,对queryStudent()与addStudent()方法的增强。

        (2)代码繁杂,不利于维护。代理对象也需要实现业务接口,如果此时业务接口中新增加了一个功能,比如删除学生deleteStudent(),那么,除了目标对象外,所有的代理对象也需要重写该方法,增加了代码的复杂度,不利于维护;

        (3)代理能力有限。一个代理对象,只能对与其实现相同业务接口的目标对象进行增强,比如此处的IStudentService。假设此时又增加了针对老师Tercher对象的查询和新增操作,并且需要做同样的增强,此时,该代理对象就无能为力了,只能重新创建一个针对老师的代理对象,增加了大量重复性代码。

三、JDK动态代理

2.1 什么是动态代理

        动态代理是指代理类对象在程序运行时由JVM根据反射机制动态生成的。动态代理不需要定义代理类的.java源文件——>jdk运行期间动态创建class字节码并加载JVM——>代理对象在程序运行的过程中动态在内存构建,可以灵活的进行业务功能的切换。 

2.2 JDK动态代理

 A. 目标对象必须实现业务接口。
 B. JDK代理对象不需要实现业务接口。
 C. JDK动态代理的对象在程序运行前不存在,在程序运行时动态的在内存中构建。
 D. JDK动态代理灵活的进行业务功能的切换。
 E. 本类中的方法(非接口中的方法)不能被代理。 

2.3 JDK动态代理用到的类和接口

(1)Proxy类

通过JDK的java.lang.reflect.Proxy类实现动态代理,会使用其静态方法newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {...}

三个参数分别为:

loader:目标类的类加载器

interfaces:目标类实现的所有接口的集合

h:函数式接口InvocationHandler,调用处理器,负责完成调用目标方法,并增强功能。

前两个参数都好得到,关键是第三个参数h。本案例通过创建InvocationHandler接口实现类的方式实现。

(2)InvocationHandler 接口

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

实现InvocationHandler接口后,需要重写 invoke() 方法。其三个参数的含义如下:

proxy:  代表生成的代理对象

method:  代表目标方法

args: 代表目标方法的参数

2.4 JDK动态代理案例

需求:

假设现在不仅有针对学生Student对象的查询与新增方法,还有针对老师Teacher对象的新增与查询方法,现在要求对所有功能执行前后进行增强。

(1)业务接口 --- 学生

public interface IStudentService {
    //查询功能
    Student queryStudent(Long id);
    //新增功能
    void addStudent();
}

 (2)业务接口 --- 老师

public interface ITeacherService {
    //查询功能
    Teacher queryTeacher(Long id);
    //新增功能
    void addTeacher();
}

(3)目标对象 --- 学生

public class StudentServiceImpl implements IStudentService {
    @Override
    public Student queryStudent(Long id) {
        System.out.println(this.getClass().getSimpleName() + ": 模拟查询学生操作...");
        Student student = new Student();
        student.setId(id);
        student.setName("佩奇");
        student.setAge(5);
        return student;
    }

    @Override
    public void addStudent() {
        System.out.println(this.getClass().getSimpleName() + ": 模拟新增学生操作...");
    }
}

(4)目标对象 --- 老师

public class TeacherServiceImpl implements ITeacherService {
    @Override
    public Teacher queryTeacher(Long id) {
        System.out.println(this.getClass().getSimpleName() + ": 模拟查询老师操作...");
        Teacher teacher = new Teacher();
        teacher.setId(id);
        teacher.setSubjects("数学");
        return teacher;
    }

    @Override
    public void addTeacher() {
        System.out.println(this.getClass().getSimpleName() + ": 模拟新增老师操作...");
    }
}

(5)InvocationHandler实现类

public class MyInvocationHandler implements InvocationHandler {

    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法执行前增强 ---> 前置通知");
        Object result = method.invoke(target, args);
        System.out.println("方法执行后增强 ---> 后置通知");
        return result;
    }
}

(6)测试

@SpringBootTest
class TestDynamicProxy {

    @Test
    void test1() {
        IStudentService studentService = new StudentServiceImpl();
        MyInvocationHandler handler = new MyInvocationHandler(studentService);
        IStudentService proxyInstance = (IStudentService)Proxy.newProxyInstance(studentService.getClass().getClassLoader(),
                studentService.getClass().getInterfaces(), handler);
        Student student = proxyInstance.queryStudent(1L);
        System.out.println(student);
    }

    @Test
    void test2() {
        ITeacherService teacherService = new TeacherServiceImpl();
        MyInvocationHandler handler = new MyInvocationHandler(teacherService);
        ITeacherService proxyInstance = (ITeacherService)Proxy.newProxyInstance(teacherService.getClass().getClassLoader(),
                teacherService.getClass().getInterfaces(), handler);
        Teacher teacher = proxyInstance.queryTeacher(1L);
        System.out.println(teacher);
    }
}

(7)测试结果

test1执行结果:
方法执行前增强 ---> 前置通知
StudentServiceImpl: 模拟查询学生操作...
方法执行后增强 ---> 后置通知
Student(id=1, name=佩奇, age=5)

*****************************************

test2执行结果:
方法执行前增强 ---> 前置通知
TeacherServiceImpl: 模拟查询老师操作...
方法执行后增强 ---> 后置通知
Teacher(id=1, subjects=数学)

JDK动态代理总结:

1. 代理类不需要手动的去创建,也不需要实现业务接口;

2. 代理能力比静态代理强,如上例所示,不仅可以代理学生Student类,同样也可以代理老师Teacher类,如果后续还有其他业务接口也需要做同样的增强,只需要在使用Proxy.newProxyInstance() 时传入相应的参数即可,不需要修改或创建额外的代理类,灵活方便;

3. 相对于静态代理,节省了大量重复性代码,也更加利于维护;

2.5 JDK动态代理原理

(1)首先通过如下方法反编译得到代理类的字节码文件$Proxy1.class

    /**
     * 生成代理实例字节码文件,用来观察调用逻辑
     */
    @Test
    void getProxyClass() {
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy1", StudentServiceImpl.class.getInterfaces());
        try (FileOutputStream outputStream = new FileOutputStream(new File("E:\\13-muma\\02-dunamic-proxy\\src\\main\\resources\\$Proxy1.class"))) {
            outputStream.write(bytes);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

(2)字节码文件如下:

import com.muma.domain.Student;
import com.muma.service.IStudentService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy1 extends Proxy implements IStudentService {
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;

    public $Proxy1(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final Student queryStudent(Long var1) throws  {
        try {
            return (Student)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void addStudent() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.muma.service.IStudentService").getMethod("queryStudent", Class.forName("java.lang.Long"));
            m4 = Class.forName("com.muma.service.IStudentService").getMethod("addStudent");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

(3)可以看到,代理类$Proxy1实现了IStudentService这个业务接口,并重写了其中的抽象方法。拿test1()举例,其调用逻辑如下所示:

 其中,m3为:

 四、CGLib动态代理

思考:想要功能扩展,但目标对象没有实现接口,怎样功能扩展?

解决方案:子类的方式——>以子类的方式实现(cglib代理),在内存中构建一个子类对象从而实现对目标对象功能的扩展。

JDK动态代理有一个前提,需要被代理的类必须实现接口,如果没有实现接口,只能通过CGLib来实现,其实就是对于JDK动态代理的一个补充。

 4.1 什么是CGLib动态代理?

通过动态的在内存中构建子类对象,重写父类的方法进行代理功能的增强。

4.2 CGLib动态代理的特点

(1)CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception。

(2)CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不推荐直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。 

(3)被代理的类不能为final, 否则报错。目标对象的方法如果为final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。 

 4.3 CGLib动态代理实现步骤

A. 引入cglib依赖。

B. 引入依赖后,就可以在内存中动态构建子类

C. 被代理的类不能为final,否则报错。

D. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

4.4 CGLib动态代理案例

需求:

假设现在有一个StudentServiceImpl类,里面包含针对Student对象的查询与新增方法,但是该类没有实现任何业务接口,现在要求对所有功能执行前后进行增强。

 (1)目标类

@Service
public class StudentServiceImpl {
    public Student queryStudent(Long id) {
        System.out.println(this.getClass().getSimpleName() + ": 模拟查询学生操作...");
        Student student = new Student();
        student.setId(id);
        student.setName("佩奇");
        student.setAge(5);
        return student;
    }

    public void addStudent() {
        System.out.println(this.getClass().getSimpleName() + ": 模拟新增学生操作...");
    }
}

(2) 自定义方法拦截器,实现MethodInterceptor接口

public class MyCglibInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法执行前增强 ---> 前置通知");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("方法执行后增强 ---> 后置通知");
        return result;
    }
}

(3)测试

@SpringBootTest
public class TestCglib {
    @Test
    void test1() {
        //使用Cglib框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(StudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(new MyCglibInterceptor());
        //创建子类(代理)对象
        StudentServiceImpl studentProxy = (StudentServiceImpl)enhancer.create();
        Student student = studentProxy.queryStudent(2L);
        System.out.println(student);
    }
}

(4) 测试结果

方法执行前增强 ---> 前置通知
StudentServiceImpl$$EnhancerByCGLIB$$e13796d2: 模拟查询学生操作...
方法执行后增强 ---> 后置通知
Student(id=2, name=佩奇, age=5)

五、JDK动态代理与CGLib动态代理的区别

代理类型实现机制回调方式使用场景效率
JDK动态代理通过实现接口,通过反射机制获取到接口里面的方法,并且自定义InvocationHandler 接口,实现方法拦截调用invoke方法目标类有接口实现1.8高于CGLib
CGLib动态代理继承机制,通过继承重写目标方法,使用MethodInterceptor 调用父类的目标方法从而实现代理调用intercept方法目标类有没有实现接口都可以,但前提是目标类不能被final修饰第一次调用时,会生成多个字节码文件比较耗费时间,多次调用性能还行

仅自己学习记录,如有错误,敬请谅解~,谢谢~~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值