反射机制
这篇文章我是参考了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 类的完整类名
方法四:对于基本数据类型(int、char、boolean、float 等),通过 基本数据类型.class 获取:
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();
调用Class.newInstance()的局限是,它只能调用该类的public无参构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
为了调用任意的构造方法,Java 的反射 API 提供了Constructor类对象,它包含一个构造方法的所有信息,通过Constructor类对象可以创建一个类的实例对象。Constructor类对象和Method类对象非常相似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回一个类的实例对象:
import java.lang.reflect.Constructor;public class Main {public static void main(String[] args) throws Exception {// 获取构造方法 Integer(int),形参为 intConstructor cons1 = Integer.class.getConstructor(int.class);// 调用构造方法:// 传入的形参必须与构造方法的形参类型相匹配Integer n1 = (Integer) cons1.newInstance(123);System.out.println(n1);// 获取构造方法Integer(String),形参为 StringConstructor 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();
动态代理
我们来比较 Java 的类class和接口interface的区别:
- 可以实例化类class(非abstract);
- 不能实例化接口interface。
所有接口interface类型的变量总是通过某个实现了接口的类的对象向上转型再赋值给接口类型的变量:
CharSequence cs = new StringBuilder();
有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?
这是可能的,因为 Java 标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。
什么叫运行期动态创建?听起来好像很复杂。所谓动态代理,是和静态相对应的。我们来看静态代理代码怎么写:
一、定义接口:
public interface Hello {
void morning(String name);
}
二、编写实现类:
public class HelloWorld implements Hello {
public void morning(String name) {
System.out.println("Good morning, " + name);
}
}
三、创建实例,转型为接口并调用:
Hello hello = new HelloWorld();
hello.morning("Bob");
这种方式就是我们通常编写代码的方式。
还有一种方式是动态代码,我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过 JDK 提供的一个Proxy.newProxyInstance()方法创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代理。JDK 提供的动态创建接口对象的方式,就叫动态代理。
一个最简单的动态代理实现如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}interface Hello {
void morning(String name);
}
在运行期动态创建一个interface实例的方法如下:定义一个InvocationHandler实例,它负责实现接口的方法调用;通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:使用的ClassLoader,通常就是接口类的ClassLoader;需要实现的接口数组,至少需要传入一个接口进去;用来处理接口方法调用的InvocationHandler实例。将返回的Object强制转型为接口。动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning", String.class),
new Object[] { name });
}
}
其实就是 JVM 帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。
小结
Java 标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;
动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。
动态代理实现方式
为了让生成的代理类与目标对象(真实主题角色)保持一致性,从现在开始将介绍以下两种最常见的方式:
- 通过实现接口的方式 -> JDK动态代理
- 通过继承类的方式 -> CGLIB动态代理
注:使用ASM对使用者要求比较高,使用Javassist会比较麻烦。
JDK动态代理
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;}@Overridepublic 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. 获取对应的 ClassLoaderClassLoader classLoader = userServiceImpl.getClass().getClassLoader();// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,Class[] interfaces = userServiceImpl.getClass().getInterfaces();// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用// 这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImplInvocationHandler 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)
用于获取指定代理对象所关联的调用处理器
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文件
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.invoke
实际上是在创建代理的时候传递给Proxy.newProxyInstance
的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑
而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();Object result = method.invoke(target, args); // 调用 target 的 method 方法after();return result; // 返回方法的执行结果}
JDK动态代理执行方法调用的过程简图如下:
代理类的调用过程相信大家都明了了,而关于Proxy的源码解析,还请大家另外查阅其他文章或者直接看源码
CGLIB动态代理
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*/@Overridepublic 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执行的是子类的方法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()));}
}
测试
import net.sf.cglib.proxy.Enhancer;public class CglibTest {public static void main(String[] args) {DaoProxy daoProxy = new DaoProxy(); Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Dao.class); // 设置超类,cglib是通过继承来实现的enhancer.setCallback(daoProxy);Dao dao = (Dao)enhancer.create(); // 创建代理类dao.update();dao.select();}
}
运行结果
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 {@Overridepublic 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 {@Overridepublic 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动态代理与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方法,无法进行代理。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:技术实现相对难理解些。
CGlib 对接口实现代理?
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.UserService;
import java.lang.reflect.Method;/*** 创建代理类的工厂 该类要实现 MethodInterceptor 接口。* 该类中完成三样工作:* (1)声明目标类的成员变量,并创建以目标类对象为参数的构造器。用于接收目标对象* (2)定义代理的生成方法,用于创建代理对象。方法名是任意的。代理对象即目标类的子类* (3)定义回调接口方法。对目标类的增强这在这里完成*/
public class CGLibFactory implements MethodInterceptor {// 声明目标类的成员变量private UserService target;public CGLibFactory(UserService target) {this.target = target;}// 定义代理的生成方法,用于创建代理对象public UserService myCGLibCreator() {Enhancer enhancer = new Enhancer();// 为代理对象设置父类,即指定目标类enhancer.setSuperclass(UserService.class);/*** 设置回调接口对象 注意,只所以在setCallback()方法中可以写上this,* 是因为MethodIntecepter接口继承自Callback,是其子接口*/enhancer.setCallback(this);return (UserService) enhancer.create();// create用以生成CGLib代理对象}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("start invoke " + method.getName());Object result = method.invoke(target, args);System.out.println("end invoke " + method.getName());return result;}
}
参