声明:本文中大部分概念内容转载至CSDN博主「电竞丶小松哥」
原文链接:https://blog.csdn.net/qq_39038178/article/details/125282017
目录
一、代理模式
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修饰 | 第一次调用时,会生成多个字节码文件比较耗费时间,多次调用性能还行 |
仅自己学习记录,如有错误,敬请谅解~,谢谢~~~