大家好,欢迎来到IT知识分享网。
反射机制
这篇文章我是参考了Java 中的反射机制(两万字超全详解)_java反射-CSDN博客
然后我在这里做一下总结,因为原文章真的很好,只能做出自己的总结补充并且写一下自己对这个的理解。
原理:
我们学过JVM都知道我们Java首先通过javac编译器变异成class字节码文件,然后我们通过ClassLoader将我们用到的class类加载到我们的方法区中,方法区在jdk8中使用元空间进行实现。元空间中保存了类的元数据
(包括方法代码、变量名、方法名、访问权限等等)。但是在我们同时会在堆空间中创建一个Class类的对象。有些同志对这个Class类对象就不理解了,注意这个Class是一个类,这个类继承自Object,这个Class类对象可以和我们实际的类进行关联。请看下面的例子:
以
String
类为例,当 JVM 加载String
类时,它首先读取String.class
文件到内存,然后,在堆中为String
类创建一个Class
类对象并将两者关联起来
Class cls = new Class(String);
注意:这个Class类对象是 JVM 内部创建的,如果我们查看 JDK 源码,可以发现Class类的构造方法是private,即只有 JVM 能创建Class类对象,我们程序员自己的 Java 程序是无法创建Class类对象的。
所以,JVM持有的每个Class类对象都指向一个数据类型(class或interface):
┌───────────────────────────┐ │ Class Instance │──────> String ├───────────────────────────┤ │name = "java.lang.String" │ └───────────────────────────┘ ┌───────────────────────────┐ │ Class Instance │──────> Random ├───────────────────────────┤ │name = "java.util.Random" │ └───────────────────────────┘ ┌───────────────────────────┐ │ Class Instance │──────> Runnable ├───────────────────────────┤ │name = "java.lang.Runnable"│ └───────────────────────────┘
一个Class类对象包含了其对应的类class的所有完整信息:
┌───────────────────────────┐ │ Class Instance │──────> String ├───────────────────────────┤ │name = "java.lang.String" │ ├───────────────────────────┤ │package = "java.lang" │ ├───────────────────────────┤ │super = "java.lang.Object" │ ├───────────────────────────┤ │interface = CharSequence...│ ├───────────────────────────┤ │field = value[],hash,... │ ├───────────────────────────┤ │method = indexOf()... │ └───────────────────────────┘
由于JVM为每个加载的类class创建了对应的Class类对象,并在实例中保存了该类class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class类对象,我们就可以通过这个Class类对象获取到其对应的类class的所有信息。
这种通过Class实例获取类class信息的方法称为反射(Reflection)。
使用反射的好处
使用反射的好处是什么呢?可以动态加载类的字节码文件,将字节码文件的加载推迟到程序运行过程中。第二个就是可以获取一个类的字段方法以及可以创建一个类的实例。
public class ClassLoad { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int key = sc.nextInt(); switch(key) { case 0: Cat cat = new Cat(); break; case 1: // 通过反射创建一个Dog 类对象,不提供代码,只是文字说明 break; } } }
上面代码中,根据 key 的值选择创建 Cat/Dog 对象,但是在代码编译时,编译器会先检查程序中是否存在 Cat 类,如果没有,则会编译报错;编译器不会检查是否存在 Dog 类,因为 Dog 类是使用反射的方式创建的,所以即使程序中不存在 Dog 类,也不会编译报错,而是等到程序运行时,我们真正选择了 key = 1 后,才会去检查 Dog 类是否存在。
获取类的对象方法等API
关于获取类的对象方法,以及创建对象都是调用的Class中的方法:
首先获取class对应的Class实例,有五种方式:
如何获取一个class的Class实例?有5个方法:
方法一:直接通过一个类class中的静态变量class获取:
Class cls = String.class;// class 是 String 类中的一个静态变量
方法二:如果我们有一个类class的对象,可以通过该对象引用提供的getClass()方法获取:
String s = "Hello"; Class cls = s.getClass();// 调用 String类对象 s的 getClass() 方法获取
方法三:如果知道一个类class的完整类名,可以通过Class类的静态方法Class.forName()获取:
Class cls = Class.forName("java.lang.String");// java.lang.String 是 String 类的完整类名
Class integerClass = int.class; Class characterClass = char.class; Class booleanClass = boolean.class; System.out.println(integerClass);// int
方法五:对于基本数据类型对应的包装类,可以通过类中的静态变量TYPE获取到Class类对象:
Class type1 = Integer.TYPE; Class type2 = Character.TYPE; System.out.println(type1);// int
获取字段值:
我们先看看如何通过Class类对象获取其对应的类定义的字段信息。Class类提供了以下几个方法来获取字段:
Field getField(name):根据字段名获取某个 public 的 field(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的某个 field(不包括父类)
Field[] getFields():获取所有 public 的 field(包括父类)
Field[] getDeclaredFields():获取当前类的所有 field(不包括父类)
public class Main { public static void main(String[] args) throws Exception { Class stdClass = Student.class; // 获取public字段"score": System.out.println(stdClass.getField("score")); // 获取继承的public字段"name": System.out.println(stdClass.getField("name")); // 获取private字段"grade": System.out.println(stdClass.getDeclaredField("grade")); } } class Student extends Person { public int score; private int grade; } class Person { public String name; }
调用方法:
我们已经能通过Class类的Field类对象获取其对应的类class中定义的所有字段信息,同样的,可以通过Class类获取所有Method信息。Class类提供了以下几个方法来获取类class中定义的Method:
Method getMethod(name, Class…):获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
Method[] getMethods():获取所有public的Method(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
// 一般情况下调用 String 类的 substring() 方法 String s = "Hello world"; String r = s.substring(6); // "world" import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { // String 对象: String s = "Hello world"; // 获取 String substring(int)方法,形参为 int: Method m = String.class.getMethod("substring", int.class); // 在 s 对象上调用该方法并获取结果: String r = (String) m.invoke(s, 6); // 打印调用结果: System.out.println(r); } }
调用构造方法:
一般情况下,我们通常使用new操作符创建新的对象:
Person p = new Person();
如果通过反射来创建新的对象,可以调用Class提供的newInstance()方法:
Person p = Person.class.newInstance();
import java.lang.reflect.Constructor; public class Main { public static void main(String[] args) throws Exception { // 获取构造方法 Integer(int),形参为 int Constructor cons1 = Integer.class.getConstructor(int.class); // 调用构造方法: // 传入的形参必须与构造方法的形参类型相匹配 Integer n1 = (Integer) cons1.newInstance(123); System.out.println(n1); // 获取构造方法Integer(String),形参为 String Constructor cons2 = Integer.class.getConstructor(String.class); Integer n2 = (Integer) cons2.newInstance("456"); System.out.println(n2); } }
通过Class实例获取Constructor的方法如下:
getConstructor(Class…):获取某个public的Constructor;
getDeclaredConstructor(Class…):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。
创建对象:
// 获取 String 的 Class 类对象: Class cls = String.class; // 通过 String 的 Class 类对象创建一个 String 类的实例对象: String s = (String) cls.newInstance();
代理模式
1. 概念
代理模式为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。这样做的好处是可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。
2. 代理模式类图
Client不直接调用TargetObject中的方法,而是通过ProxyObject调用TargetObject中的方法
3. 代理模式的形式
代理模式有不同的形式,主要有三种:静态代理、动态代理和Cglib代理
静态代理
1. 概念
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。
2. 类图
定义一个接口ITeacherDao,目标对象TeacherDAO实现接口ITeacherDAO。使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO。调用的时候通过调用代理对象的方法来调用目标对象。
注:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。
3. 代码示例
ITeacherDao接口,抽象定义授课方法
public interface ITeacherDao {
void teach();//授课
}
TeacherDao类,目标对象,实现ITeacherDao接口public class TeacherDao implements ITeacherDao{
@Override
public void teach() {
System.out.println(“老师授课中….”);
}
}
TeacherDaoProxy类,代理对象,也实现ITeacherDao接口,通过接口聚合目标对象,执行代理方法
public class TeacherDaoProxy implements ITeacherDao {
private ITeacherDao target;//目标对象,通过接口聚合
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println(“代理开始,完成某些操作”);
target.teach();
System.out.println(“提交”);
}
}
Client类,创建目标对象(被代理对象);创建代理对象,同时将目标对象传递给代理对象;通过代理对象,调用到目标对象的方法
public class Client {
public static void main(String[] args) {
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(new TeacherDao());
teacherDaoProxy.teach();
}
}
输出结果
静态代理的缺点
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
- 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
- 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
如何改进?
当然是让代理类动态的生成啦,也就是动态代理。
动态代理
动态代理实现方式
为了让生成的代理类与目标对象(真实主题角色)保持一致性,从现在开始将介绍以下两种最常见的方式:
- 通过实现接口的方式 -> JDK动态代理
通过 JDK 提供的一个Proxy.newProxyInstance()方法创建了一个实现了目标对象实现接口的代理对象
- 通过继承类的方式 -> CGLIB动态代理
通过字节码技术创建一个目标对象的子类代理对象
JDK是基于反射机制,生成一个实现代理接口的匿名类,然后重写方法,实现方法的增强.
它生成类的速度很快,但是运行时因为是基于反射,调用后续的类操作会很慢.
而且他是只能针对接口编程的.
CGLIB是基于继承机制,继承被代理类,所以方法不要声明为final,然后重写父类方法达到增强了类的作用.
它底层是基于asm第三方框架,是对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理.
生成类的速度慢,但是后续执行类的操作时候很快.
可以针对类和接口.
JDK动态代理
1. 概念
动态代理在使用时,代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。代理对象的生成,是利用JDK的API(java.lang.reflect.Proxy.newProxyInstace()), 动态的在内存中构建代理对象。动态代理也叫做:JDK代理、接口代理。
2. 类图
3. 代码示例
ITeacherDao接口,抽象定义授课方法
public interface ITeacherDao {
void teach();//授课
}
TeacherDao类,目标对象,实现ITeacherDao接口public class TeacherDao implements ITeacherDao{
@Override
public void teach() {
System.out.println(“老师授课中….”);
}
}
ProxyFactory类,维护一个目标对象,利用JDK的API,动态的在内存中构建代理对象
public static object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取加载器的方法固定
2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
3. InvocationHandler h: 事情处理,执行目标对象的方法时,会触发事情处理器方法,会把当前执行的目标对象作为参数传入
public class ProxyFactory {
//维护一个目标对象,Object
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象,生成一个代理对象
public Object getProxyInstance(){
//
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“JDK代理开始..”);
//反射机制调用目标对象的方法
Object returnVal = method.invoke(target, args);
System.out.println(“JDK代理提交..”);
return returnVal;
}
});
}
}
Client类
public class Client {
public static void main(String[] args) {
ITeacherDao target = new TeacherDao();
ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
//class com.sun.proxy.$Proxy0内存中动态生成了代理对象
System.out.println(proxyInstance.getClass());
proxyInstance.teach();
}
}
输出结果
案例
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
,我们仍然通过案例来学习编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke
方法中编写方法调用的逻辑处理。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Date; public class LogHandler implements InvocationHandler { Object target; // 被代理的对象,实际的方法执行者 public LogHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); // 调用 target 的 method 方法 after(); return result; // 返回方法的执行结果 } // 调用invoke方法之前执行 private void before() { System.out.println(String.format("log start time [%s] ", new Date())); } // 调用invoke方法之后执行 private void after() { System.out.println(String.format("log end time [%s] ", new Date())); } }
编写客户端,获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法,具体步骤可见代码和注释
import proxy.UserService; import proxy.UserServiceImpl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Client2 { public static void main(String[] args) throws IllegalAccessException, InstantiationException { // 设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名 // System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 1. 创建被代理的对象,UserService接口的实现类 UserServiceImpl userServiceImpl = new UserServiceImpl(); // 2. 通过反射方式 获取对应的 ClassLoader ClassLoader classLoader = userServiceImpl.getClass().getClassLoader(); // 3. 通过反射方式 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService, Class[] interfaces = userServiceImpl.getClass().getInterfaces(); // 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用 // 这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl 传入的是被代理对象 InvocationHandler logHandler = new LogHandler(userServiceImpl); /* 5.根据上面提供的信息,创建代理对象 在这个过程中, a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码 b.然后根据相应的字节码转换成对应的class, c.然后调用newInstance()创建代理实例 */ UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler); // 调用代理的方法 proxy.select(); proxy.update(); // 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy // ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy"); } }
运行结果
log start time [Thu Dec 20 16:55:19 CST 2018] 查询 selectById log end time [Thu Dec 20 16:55:19 CST 2018] log start time [Thu Dec 20 16:55:19 CST 2018] 更新 update log end time [Thu Dec 20 16:55:19 CST 2018]
InvocationHandler 和 Proxy 的主要方法介绍如下:
java.lang.reflect.InvocationHandler
Object invoke(Object proxy, Method method, Object[] args)
定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用
java.lang.reflect.Proxy
static InvocationHandler getInvocationHandler(Object proxy)
用于获取指定代理对象所关联的调用处理器 super.h返回的就是InvocationHandler
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回指定接口的代理类
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法
static boolean isProxyClass(Class<?> cl)
返回 cl 是否为一个代理类
代理类的调用过程
生成的代理类到底长什么样子呢?借助下面的工具类,把代理类保存下来再探个究竟(通过设置环境变量sun.misc.ProxyGenerator.saveGeneratedFiles=true也可以保存代理类)
import sun.misc.ProxyGenerator; import java.io.FileOutputStream; import java.io.IOException; public class ProxyUtils { / * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下 * params: clazz 需要生成动态代理类的类 * proxyName: 为动态生成的代理类的名称 */ public static void generateClassFile(Class clazz, String proxyName) { // 根据类信息和提供的代理类名称,生成字节码 byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces()); String paths = clazz.getResource(".").getPath(); System.out.println(paths); FileOutputStream out = null; try { //保留到硬盘中 out = new FileOutputStream(paths + proxyName + ".class"); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
然后在 Client2 测试类的main的最后面加入一行代码
// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
IDEA 再次运行之后就可以在 target 的类路径下找到 UserServiceProxy.class,双击后IDEA的反编译插件会将该二进制class文件
Spring生成的UserServiceProxy 的代码如下所示:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import proxy.UserService; public final class UserServiceProxy extends Proxy implements UserService { private static Method m1; private static Method m2; private static Method m4; private static Method m0; private static Method m3; public UserServiceProxy(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { // 省略... } public final String toString() throws { // 省略... } public final void select() throws { try { super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { // 省略... } public final void update() throws { try { super.h.invoke(this, m3, (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")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m4 = Class.forName("proxy.UserService").getMethod("select"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("proxy.UserService").getMethod("update"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
从 UserServiceProxy 的代码中我们可以发现:
- UserServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
- 由于 UserServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
- 类和所有方法都被
public final
修饰,所以代理类只可被使用,不可以再被继承 - 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以
m + 数字
的格式命名 - 调用方法的时候通过
super.h.invoke(this, m1, (Object[])null);
调用,其中的super.h
实际上是在创建代理的时候传递给Proxy.newProxyInstance
的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑,super.h.invoke
调用这个LogHandler的方法。
而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); // 调用 target 的 method 方法 after(); return result; // 返回方法的执行结果 }
JDK动态代理执行方法调用的过程简图如下:
代理类的调用过程相信大家都明了了,而关于Proxy的源码解析,还请大家另外查阅其他文章或者直接看源码
关于super.h.invoke内部详解
super.h.invoke(this, m3, (Object[])null);super也就是Proxy,看下Proxy的h如下图:
所以h代表的就是InvocationHandler这个接口,而他的具体实现类是ProxyHandler,这样最终proxySubject.getName();执行的代码是ProxyHandler中的invoke方法。
CGLIB动态代理
1. 概念
静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是Cglib代理。Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。广泛被许多AOP的框架使用,例如Spring AOP,实现方法拦截。
在AOP编程中如何选择代理模式: 1.目标对象需要实现接口,用JDK代理
2.目标对象不需要实现接口,用Cglib代理
Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
注意事项:
1. 使用Cglib代理需要先导入相应包
2. 在内存中动态构建子类,注意代理的类不能为final,否则报错
3. 目标对象的方法如果为final/static,那么就不会被拦截
2. 类图
3. 代码示例
’TeacherDao类,无接口
public class TeacherDao {
public void teach(){
System.out.println(“老师授课中..cglib代理,不需要实现接口”);
}
}
ProxyFactory类,实现MethodInterceptor接口,通过设置目标对象子类来代理目标对象,重写intercept()方法,调用目标对象方法
public class ProxyFactory implements MethodInterceptor{
//维护一个目标对象 这个就是父类的代理啊。
private Object target;
//构造器,传入目标对象
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象,是target对象的代理对象
public Object getProxyInstance(){
//1.创建一个工具类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback(new MyInterceptor());
//4.创建子对象,即代理对象
return enhancer.create();
}
//重写 intercept方法,会调用目标对象的方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println(“Cglib代理模式—开始”);
Object returnVal = method.invoke(target,args);
System.out.println(“Cglib代理模式—提交”);
return null;
}
}
Client类,创建目标对象;获取到代理对象,并且将目标对象传递给代理对象;执行代理对象的方法,触发intercept 方法,从而实现对目标对象的调用
public class Client {
public static void main(String[] args) {
//创建目标对象
TeacherDao target = new TeacherDao();
//获取到代理对象,并且将目标对象传递给代理对象
TeacherDao proxyInstance =(TeacherDao) new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法,触发intercept 方法,从而实现对目标对象的调用
proxyInstance.teach();
}
}
输出结果
案例
maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()
public class UserDao { public void select() { System.out.println("UserDao 查询 selectById"); } public void update() { System.out.println("UserDao 更新 update"); } }
编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调
import java.lang.reflect.Method; import java.util.Date; public class LogInterceptor implements MethodInterceptor { / * @param object 表示要进行增强的对象 * @param method 表示拦截的方法 * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用 * @return 执行结果 * @throws Throwable */ @Override public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(object, objects); // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法 当然我们也可以向上面一样传递一个target然后使用metho.invoke(target,args) after(); return result; } private void before() { System.out.println(String.format("log start time [%s] ", new Date())); } private void after() { System.out.println(String.format("log end time [%s] ", new Date())); } }
测试
public class CglibTest { public static void main(String[] args) { LogInterceptor logInterceptor = new LogInterceptor(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserDao.class); // 设置超类,cglib是通过继承来实现的 enhancer.setCallback(logInterceptor); UserDao dao = (UserDao)enhancer.create(); // 创建代理类 dao.select(); dao.update(); } }
运行结果
log start time [Fri Dec 21 00:06:40 CST 2018] UserDao 查询 selectById log end time [Fri Dec 21 00:06:40 CST 2018] log start time [Fri Dec 21 00:06:40 CST 2018] UserDao 更新 update log end time [Fri Dec 21 00:06:40 CST 2018]
还可以进一步多个 MethodInterceptor 进行过滤筛选
public class LogInterceptor2 implements MethodInterceptor { @Override public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(object, objects); after(); return result; } private void before() { System.out.println(String.format("log2 start time [%s] ", new Date())); } private void after() { System.out.println(String.format("log2 end time [%s] ", new Date())); } } // 回调过滤器: 在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。 public class DaoFilter implements CallbackFilter { @Override public int accept(Method method) { if ("select".equals(method.getName())) { return 0; // Callback 列表第1个拦截器 } return 1; // Callback 列表第2个拦截器,return 2 则为第3个,以此类推 } }
再次测试
public class CglibTest2 { public static void main(String[] args) { LogInterceptor logInterceptor = new LogInterceptor(); LogInterceptor2 logInterceptor2 = new LogInterceptor2(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserDao.class); // 设置超类,cglib是通过继承来实现的 enhancer.setCallbacks(new Callback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE}); // 设置多个拦截器,NoOp.INSTANCE是一个空拦截器,不做任何处理 enhancer.setCallbackFilter(new DaoFilter()); UserDao proxy = (UserDao) enhancer.create(); // 创建代理类 proxy.select(); proxy.update(); } }
运行结果
log start time [Fri Dec 21 00:22:39 CST 2018] UserDao 查询 selectById log end time [Fri Dec 21 00:22:39 CST 2018] log2 start time [Fri Dec 21 00:22:39 CST 2018] UserDao 更新 update log2 end time [Fri Dec 21 00:22:39 CST 2018]
CGLIB 创建动态代理类的模式是:
- 查找目标类上的所有非final 的public类型的方法定义;
- 将这些方法的定义转换成字节码;
- 将组成的字节码转换成相应的代理的class对象;
- 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求
实现原理
jdk
底层原理解析
JDK动态代理是基于Java的反射机制实现的。在创建代理对象时,JDK会动态生成一个新的类,实现了目标对象所实现的接口,并将代理逻辑写入 invoke 方法中。当调用代理对象的方法时,实际上是调用了 invoke 方法,然后由 invoke 方法根据方法名来调用目标对象的方法。
代理类的创建
Java动态代理的实现是通过 java.lang.reflect.Proxy 类来完成的。当我们调用 Proxy.newProxyInstance 方法时,它会做以下几件事情:
1.检查提供的接口是否全部为公共接口。
2.确定要使用的类加载器。
3.生成代理类的二进制字节码。
4.加载这些二进制字节码,定义代理类。
Proxy.newProxyInstance
方法分析
public class Proxy { // 获取或创建一个动态代理类的实例的静态方法 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 确保提供的InvocationHandler不为null Objects.requireNonNull(h); // 克隆接口数组,以避免对原数组的潜在修改 final Class<?>[] intfs = interfaces.clone(); // 获取系统的安全管理器 final SecurityManager sm = System.getSecurityManager(); // 如果有安全管理器,检查创建代理实例的权限 if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * 查找或生成指定的代理类。 */ // 获取代理类 Class<?> cl = getProxyClass0(loader, intfs); /* * 调用其构造函数并用指定的调用处理器InvocationHandler作为参数。 */ try { // 如果安全管理器存在,进一步检查权限 if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } // 查找代理类的构造函数,这个构造函数预期接收一个InvocationHandler作为参数 final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; // 如果代理类不是public的,需要特权访问权限,以便设置构造函数为可访问状态 if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } // 使用提供的InvocationHandler实例化代理类 return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { // 如果代理对象无法正常创建,抛出内部错误 throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { // 如果构造函数调用抛出异常,解包异常并处理 Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { // 如果没有找到预期的构造函数,抛出内部错误 throw new InternalError(e.toString(), e); } } // 其他省略的代码... }
方法的执行流程如下:
(1)参数验证:验证传入的 InvocationHandler h 是否为 null。
(2)安全权限检查(如果存在安全管理器):使用 checkProxyAccess 方法检查是否有创建代理实例的权限。
(3)代理类的查找或生成:调用 getProxyClass0 方法来获取或生成相应的代理类。
(4)调用代理类的构造函数创建实例:找到代理类的唯一构造函数(该构造函数以 InvocationHandler 为参数),并判断是否需要通过权限检查调用 setAccessible 来允许访问非公共构造函数。
(5)创建并返回代理实例:最后,通过反射调用构造器的 newInstance 方法,并将 InvocationHandler h 传递为参数,创建代理对象的实例。
注意
getProxyClass0()
方法的底层源码并没有公开,因为它是 JDK 内部的私有方法。但是,它的具体实现可以简单理解为使用了类似于 ASM(Java 字节码操作框架)这样的工具来动态生成字节码。
看到下面生成的代理对象的字节码文件,是不是一切都明白你了,原理竟然如此简单!
public final class $Proxy0 extends Proxy implements ServiceInterface {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;public $Proxy0(InvocationHandler var1) throws {
super(var1);
}public final void execute() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}…..省略toString/hashCode/equals等方法
static {
try {
m1 = Class.forName(“java.lang.Object”).getMethod(“equals”, Class.forName(“java.lang.Object”));
m2 = Class.forName(“java.lang.Object”).getMethod(“toString”);
m3 = Class.forName(“com.useful.tool.ServiceInterface”).getMethod(“execute”);
m0 = Class.forName(“java.lang.Object”).getMethod(“hashCode”);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
通过该文件可以看出:
(1)代理类继承了Proxy类,其主要目的是为了传递InvocationHandler。
(2)代理类实现了被代理的接口ServiceInterface,这也是为什么代理类可以直接强转成接口的原因。
(3)有一个公开的构造函数,参数为指定的InvocationHandler,并将参数传递到父类Proxy中。
(4)每一个实现的方法,都会调用InvocationHandler中的invoke方法,并将代理类本身、Method实例、入参三个参数进行传递。这也是为什么调用代理类中的方法时,总会分派到InvocationHandler中的invoke方法的原因。
注意的地方
public final void execute() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
上面的super
也就是我们的Proxy
类,super.h
也就是Proxy里面的InvocationHandler h
变量
cjlib
- CGLib采用了非常底层的字节码技术,其原理是通过目标类的字节码为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
- 底层使用字节码处理框架ASM,来转换字节码并生成新的类。
直接看源码
Enhancer.generateClass(ClassVisitor v) //AbstractClassGenerator.create(Object key) protected Object create(Object key) { try { Class gen = null; synchronized (source) { ClassLoader loader = getClassLoader(); Map cache2 = null; cache2 = (Map)source.cache.get(loader); if (cache2 == null) { cache2 = new HashMap(); cache2.put(NAME_KEY, new HashSet()); source.cache.put(loader, cache2); } else if (useCache) { Reference ref = (Reference)cache2.get(key); gen = (Class) (( ref == null ) ? null : ref.get()); } if (gen == null) { Object save = CURRENT.get(); CURRENT.set(this); try { this.key = key; if (attemptLoad) { try { gen = loader.loadClass(getClassName()); } catch (ClassNotFoundException e) { // ignore } } if (gen == null) { //生成的Class的二进制存储 byte[] b = strategy.generate(this); String className = ClassNameReader.getClassName(new ClassReader(b)); //将类名放入Cache中,Cache实际上是一个Hashset的实例 getClassNameCache(loader).add(className); //将生成的类装载路JVM gen = ReflectUtils.defineClass(className, b, loader); } if (useCache) { //将装载的类放入cache cache2.put(key, new WeakReference(gen)); } return firstInstance(gen); } finally { CURRENT.set(save); } } } return firstInstance(gen); } catch (RuntimeException e) { throw e; } catch (Error e) { throw e; } catch (Exception e) { throw new CodeGenerationException(e); } } //ReflectUtils.defineClass(className, b, loader) public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception { Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), PROTECTION_DOMAIN }; //DEFINE_CLASS 是通过静态块来实例化的一个java.lang.ClassLoader.defineClass的方法对象 Class c = (Class)DEFINE_CLASS.invoke(loader, args); // Force static initializers to run. Class.forName(className, true, loader); return c; }
实际上总结为
public class Enhancer { // ... public Object create() { // ... 省略部分代码 // 生成代理类的字节码 byte[] bytecode = generateClass(classVisitor); // 加载代理类并创建实例 return firstInstance(generate(classLoader, bytecode)); } // 使用字节码数组生成代理类 protected Object firstInstance(Class type) throws IllegalAccessException, InstantiationException { return type.newInstance(); } // 通过字节码生成 Class 对象 protected Class generate(ClassLoader classLoader, byte[] bytecode) { return new ClassLoaderData(classLoader, bytecode).loadClass(); } // 生成代理类的字节码 protected byte[] generateClass(ClassVisitor v) { // 使用 ASM 库等生成字节码的工具来生成代理类的字节码 } // ... }
可以知道了,我么cjlib使用的 ASM 库等生成字节码的工具来生成代理类的字节码,然后使用反射机制生成Class并生成对象的对象实例
相关问题
JDK动态代理与CGLIB动态代理对比
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
基于类似 cglib 框架的优势:
- 无需现接口,达到代理类无侵入
- 只操作我实们关心的类,而不必为其他相关类增加工作量。
- 高性能
面试题
描述动态代理的几种实现方式?分别说出相应的优缺点
代理可以分为 “静态代理” 和 “动态代理”,动态代理又分为 “JDK动态代理” 和 “CGLIB动态代理” 实现。
静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object
- 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
- 缺点:不同的接口要有不同的代理类实现,会很冗余
JDK 动态代理:
为了解决静态代理中,生成大量的代理类造成的冗余;JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象 jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口。
优点:解决了静态代理中冗余的代理实现类问题。
缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。
CGLIB 代理:
由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。 同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:技术实现相对难理解些。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/156214.html