jdk动态代理
在程序执行过程中,使用jdk反射机制,创建代理类对象,并动态指定要代理的目标类
- 创建代理接口
public interface IStudentService {
void save(Student student);
Student select();
}
-
实现接口
public class StudentService implements IStudentService{ @Override public void save(Student student) { System.out.println("插入学生数据信息:" + student); } @Override public Student select() { return new Student(); } }
-
创建实体类
public class Student { private String username; private String password; private Integer age; public Student() { } public Student(String username, String password, Integer age) { this.username = username; this.password = password; this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Student{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; } }
-
测试
public class Test { public static void main(String[] args) { // 增强类对象 StudentService studentService = new StudentService(); // 使用动态代理获取增强类 IStudentService proxy = (IStudentService) Proxy.newProxyInstance( studentService.getClass().getClassLoader(), new Class[]{IStudentService.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("save".equals(method.getName())) { System.out.println("新增学生增强模拟"); Object invoke = method.invoke(studentService, args); System.err.println("新增学生失败....."); return invoke; } else if ("select".equals(method.getName())) { System.out.println("模拟返回查询学生增强..."); Student invoke = (Student) method.invoke(studentService, args); invoke.setUsername("jack"); invoke.setPassword("123456"); invoke.setAge(20); return invoke; } return method.invoke(args); } } ); proxy.save(new Student("lisi", "123456", 18)); Student res = proxy.select(); System.out.println("查询结果:" + res); } }public class Test { public static void main(String[] args) { // 增强类对象 StudentService studentService = new StudentService(); // 使用动态代理获取增强类 IStudentService proxy = (IStudentService) Proxy.newProxyInstance( studentService.getClass().getClassLoader(), new Class[]{IStudentService.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("save".equals(method.getName())) { System.out.println("新增学生增强模拟"); Object invoke = method.invoke(studentService); System.err.println("新增学生失败....."); return invoke; } else if ("select".equals(method.getName())) { System.out.println("模拟返回查询学生增强..."); Student invoke = (Student) method.invoke(studentService, args); invoke.setUsername("jack"); invoke.setPassword("123456"); invoke.setAge(20); return invoke; } return method.invoke(args); } } ); proxy.save(); Student res = proxy.select(); System.out.println("查询结果:" + res); } }
-
JDK 动态代理的执行原理:
- 定义接口:首先定义一个接口,该接口是目标对象和代理对象共同实现的接口。
- 实现 InvocationHandler 接口:创建一个实现 InvocationHandler 接口的类,该类中包含了对目标方法的增强逻辑。InvocationHandler 接口只有一个方法
invoke(Object proxy, Method method, Object[] args)
,在这个方法中可以对目标方法进行增强操作。 - 创建代理对象:通过
Proxy.newProxyInstance(ClassLoader, Class[], InvocationHandler)
方法创建代理对象。该方法需要传入一个类加载器、目标对象实现的接口列表以及实现了 InvocationHandler 接口的对象。在运行时,JDK 会动态生成一个代理类,并将接口方法的调用委托给 InvocationHandler 对象的 invoke 方法。 - 调用目标方法:当调用代理对象的方法时,实际上是调用 InvocationHandler 的 invoke 方法。在 invoke 方法中,可以根据需要执行一些额外的操作,例如记录日志、检查权限等。
- 委托给目标对象:在 invoke 方法中,可以通过反射机制调用目标对象的方法,并传入相应的参数。通过这种方式,实现了对目标对象方法的代理调用。
- 返回结果:根据目标方法的返回值类型,将其作为 invoke 方法的返回值返回。
-
jdk动态代理为什么一定要实现接口
JDK动态代理是一种基于接口的代理机制,它要求被代理的类必须实现至少一个接口。这是因为JDK动态代理的现原理是通过生成理类的字节码,并在运行时动态地创建代理对象。由于Java是一静态类型语言,编译器在编译时需要知道对象的类型信息,便进行类型检查和方法调用。
当一个类实现了接时,它就可以视为实现了该接定义的所有方法。在JDK动态代理中,代理类会现与目标类同的接口,并且在代理类的方法中调用目类的对应方法。因此,只有目标类实现了接口,才能够通过接口来定义代理类行为。
另外,使用接口作为代理的基础也有助于实现松合的设计。通过面接口编程,可以将代理类与具体的标类解耦,得代理类可以适于多个不同的标类,提高了代码的灵活和可复用性。
总结起来,JDK动态代理要实现接口是为了在编时获取类型信息,并且通过面向口编程实现松耦合的设计
cglib动态代理
cglib是三方工具库,原理是继承,cglib通过继承目标类,创建他的子类i,然后在子类中重写父类中重名的方法,然后实现功能的修改
因为cglib是继承,重写方法,所以要求目标类不能是final的,方法也不能是final的
-
引入cglib的jar
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
-
学生类,service与上面一致
-
实现MethodInterceptor
public class CGLibInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if ("save".equals(method.getName())) { System.out.println("新增学生增强模拟"); Object invoke = methodProxy.invokeSuper(o, objects); System.err.println("新增学生失败....."); return invoke; } else if ("select".equals(method.getName())) { System.out.println("模拟返回查询学生增强..."); Student invoke = (Student) methodProxy.invokeSuper(o, objects); invoke.setUsername("jack"); invoke.setPassword("123456"); invoke.setAge(20); return invoke; } return methodProxy.invokeSuper(o, objects); } }
-
测试
public class Test { public static void main(String[] args) { // 得到方法拦截器 CGLibInterceptor interceptor = new CGLibInterceptor(); // 使用cglib框架生成目标类的子类(代理类)生成增强 Enhancer enhancer = new Enhancer(); // 设置父类字节码 enhancer.setSuperclass(StudentService.class); // 设置拦截处理 enhancer.setCallback(interceptor); StudentService service = (StudentService) enhancer.create(); service.save(new Student("lisi", "123456", 18)); Student student = service.select(); System.out.println("cglib增强查询到学生信息"+student); } }
jdk动态代理和cglib动态代理共同点和区别
- 共同点
- 都是在运行时生成代理类和代理对象。
- 都可以在代理对象中添加额外的逻辑,如前置处理、后置处理、异常处理等。
- 都可以实现横切关注点的解耦,使得代理类专注于横切逻辑,而不需要修改目标对象的代码。
- 不同点
- JDK 动态代理只能代理实现了接口的目标对象,而 CGLib 动态代理可以代理没有实现接口的目标对象。
- JDK 动态代理是通过生成目标对象接口实现的代理类来实现的,而 CGLib 动态代理是通过生成目标对象的子类来实现的。
- JDK 动态代理使用 Java 标准库中的
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现,而 CGLib 动态代理使用第三方库CGLib
实现。 - JDK 动态代理基于接口,因此对于目标对象的方法调用是通过反射的方式来实现的,相对而言速度较慢。而 CGLib 动态代理则是通过继承目标对象的方式,因此对于目标对象的方法调用是直接调用的,速度较快。
- JDK 动态代理在生成代理类时需要耗费一定的时间,而 CGLib 动态代理在生成代理类时会消耗更多的内存。
优缺点
- 优点
- 高度灵活性:动态代理允许在运行时对目标对象的方法进行增强或拦截,而无需修改源代码。通过使用动态代理,可以根据需要动态地添加、修改或删除代理逻辑,从而具备更高的灵活性。
- 低耦合性:代理类作为中间层存在于目标对象和调用方之间,可以将具体的业务逻辑与代理逻辑解耦。这样一来,无论是修改代理逻辑还是替换目标对象,都不会影响到调用方的代码。
- 概念清晰:动态代理采用统一的概念和模式,使得代码的结构更加清晰和易于维护。代理对象负责处理与目标对象相关的通用逻辑,而目标对象则专注于具体的业务逻辑,实现了职责的分离。
- 可以实现横切关注点(cross-cutting concerns):横切关注点指的是在系统中多个模块或对象共享的通用功能,例如日志记录、事务管理等。动态代理可以在不修改目标对象的前提下,通过代理逻辑实现对这些横切关注点的统一处理。
- 缺点
- 性能影响:动态代理在运行时需要通过反射等机制来生成代理对象,并将方法调用委托给代理对象。相比直接调用目标对象的方法,这种额外的过程会带来一定的性能开销。
- 只能代理接口:JDK 动态代理只能代理实现了接口的目标对象,无法代理没有接口的类。如果目标对象没有继承或实现任何接口,就不能使用 JDK 动态代理。
- 无法代理 final 方法:由于动态代理是通过生成代理类字节码来实现的,而 final 方法无法被子类覆写,因此无法对 final 方法进行代理。