参考:https://javaguide.cn/java/basis/proxy/
一、反射
1、反射的作用是什么?
反射可以让我们在运行期获取并调用一个类的所有属性和方法,使得代码更加灵活。注解和动态代理就是基于反射实现的,且像 Spring
、 Spring Boot
和 Mybatis
等框架都大量应用了反射机制。
虽然反射让我们拥有了在运行时操作类的能力,但这同样增加了安全问题,比如可以无视泛型的安全检查(泛型的安全检查发生在编译期)。
2、简单描述反射的原理
以String
为例,当JVM
加载String
类时,它首先读取String.class
文件到内存,然后为String
类创建一个Class
对象并关联起来:
Class cls = new Class(String)
每个 Class
对象都指向了一个数据类型(类或接口),这个Class
对象是JVM
内部创建的,我们自己的程序是无法创建 Class
对象的。只要获取到了某个 Class
对象,就能获取到该实例对应的类的信息,包括类名、包名、父类、实现的接口、所有的方法和属性等。
因此只要获取到了 Class
对象,就可以来创建实例所对应的类的对象。
3、获取 Class
对象的方法有哪些?(以 String
为例)
方法一:通过静态变量class
来获取。
Class cls = String.class;
方法二:如果已经有了String
类的对象,则可以通过对象的getClass()
方法来获取。
String s = "hello";
Class cls = s.getClass();
方法三:通过 Class.forName()
传入类路径来获取。
Class cls = Class.forName("java.lang.String");
注意:JVM
中 Class
对象是唯一的,因此上面三种方法创建的 Class
对象是同一个对象
方法四:通过类加载器 xxxClassLoader.loadClass()
传入类路径获取。
Class cls = ClassLoader.loadClass("java.lang.String");
注意:通过类加载器获取 Class
对象不会进行初始化,意味着静态块和静态对象不会得到执行。
4、通过 Class
对象创建类对象的方法
获取到 Class
对象后,就可以使用该 Class
对象来创建对应类的对象(即可以创建 Class
对象对应的类对象)。创建对应的类对象的方法主要有两种,如下所示:
方法一:使用Class.newInstance()
创建类实例
//获取String的Class实例
Class cls=Class.forName("java.lang.String");
//创建一个String实例
String s=(String) cls.newInstance();
上述代码相当于new String()
。这种方法的局限是:只能调用public
的无参数构造方法。
方法二:先通过Class
对象获取指定的Constructor
对象,再调用Constructor
对象的newInstance()
方法来创建实例。这种方法可以用指定的构造器来构造类实例。
//获取String所对应的Class实例
Class cls=String.class;
//获取String类带一个String参数的构造方法
Constructor constructor=cls.getConstructor(String.class);
//根据构造器来创建实例
String s=(String)constructor.newInstance("2333");
反射的一些基本操作
1、创建准备用反射来操作的类 TargetObject
package cn.javaguide;
public class TargetObject {
private String value;
public TargetObject() {
value = "JavaGuide";
}
public void publicMethod(String s) {
System.out.println("I love " + s);
}
private void privateMethod() {
System.out.println("value is " + value);
}
}
2 使用反射操作 TargetObject
中的方法和属性
package cn.javaguide;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/**
* 获取TargetObject类的Class对象并且创建TargetObject类实例
*/
Class<?> tagetClass = Class.forName("cn.javaguide.TargetObject");
TargetObject targetObject = (TargetObject) tagetClass.newInstance();
/**
* 获取所有类中所有定义的方法
*/
Method[] methods = tagetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
/**
* 获取指定方法并调用
*/
Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",
String.class);
publicMethod.invoke(targetObject, "JavaGuide");
/**
* 获取指定参数并对参数进行修改
*/
Field field = tagetClass.getDeclaredField("value");
//为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "JavaGuide");
/**
* 调用 private 方法
*/
Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
//为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}
输出内容:
publicMethod
privateMethod
I love JavaGuide
value is JavaGuide
取消安全检查:https://blog.csdn.net/kangkang12221222/article/details/122650918
二、代理
1、什么是代理模式
使用代理对象来代替对目标对象的访问,这样就可以在不修改目标对象的前提下,提供额外的功能,进而扩展了目标对象的功能。
因此可以说代理模式的主要作用就是扩展目标对象的功能,比如在目标对象的某个方法执行前后增加一些自定义的操作。
2、静态代理
静态代理中,我们对目标对象每个方法的增强都是手动完成的,非常不灵活,比如接口一旦新增加了方法,目标对象和代理对象都要改变。
静态代理在编译时就将接口、实现类、代理类都变成了一个个实际的 class
文件。
静态代理的实现步骤:
1)定义一个接口及其实现类
2)创建一个代理类,同样来实现这个接口
3)将目标对象注入进代理类中,用代理类的对应方法调用目标类中的对应方法,这样就可以通过代理类屏蔽对目标对象的访问,并且在目标方法执行前后做一些其他事情。
3、动态代理
相对于静态代理,动态代理不需要针对每个目标类单独创建代理类,且不需要必须实现接口(基于子类的 CGLIB
动态代理机载)
动态代理是在运行时动态生成类字节码,并加装到 JVM
中。
4、JDK
动态代理机载
在 java
动态代理机制中, InvocationHandler
接口和 Proxy
类是核心,并使用Proxy
中的 newProxyInstance()
,用来生成代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
loader
:类加载器,用于加载代理对象
interfaces
:被代理类实现的一些接口
h
:实现了InvocationHandler
接口的对象
要实现动态代理的话,还需要实现 InvocationHandler
接口来自定义处理逻辑。当代理对象调用一个方法时,就会被转到调用实现 InvocationHandler
接口类的 invoke()
。
public interface InvocationHandler {
/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
proxy
:动态生成的代理类
method
:与代理类对象调用的方法相对应
args
:当前 method
方法的参数
因此,通过 Proxy
类的 newProxyInstance()
方法创建的代理对象在调用方法时,实际上会调用到实现 InvocationHandler
接口的类中的 invoke()
,且可以在 invoke()
中自定义处理逻辑。
5、JDK
动态代理的实现步骤:
1)自定义一个接口及其实现类
2)实现 InvocationHandler
接口并重写 invoke()
方法,在 invoke()
中调用被代理类的方法并自定义额外的处理逻辑
3)通过 Proxy.newProxyInstance(ClassLoader loader, Class<?> interfaces, InvocationHandler h)
来创建代理对象。
代码实现:
1)定义发送短信的接口
public interface SmsService {
String send(String message);
}
2)实现 SmsService
接口
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3)实现 InvocationHandler
接口,重写 invoke()
并增加自定义的处理逻辑
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author shuang.kou
* @createTime 2020年05月11日 11:23:00
*/
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return result;
}
}
4)获取代理对象的工厂类
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}
5)实际使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
打印输出:
before method send
send message:java
after method send
6、JDK
动态代理和 CGLIB
动态代理的区别
1)JDK
动态代理只能代理实现了接口的类;
CGLIB
动态代理通过生成一个被代理类的子类(在字节码底层去继承)来拦截被代理类中的方法调用,因此可以代理未实现任何接口的类。(CGLIB
动态代理不能代理声明为 final
类型的类和方法)
2)CGLIB
动态代理所创建的代理对象在实际运行时的性能比JDK
动态代理要高(10倍差距),但是在创建代理对象时所花费的时间比 JDK
多(8倍差距)。因此在创建单例的代理对象时,由于无需频繁创建代理对象,所以比较适合采用 CGLIB
动态代理,反之比较适用 JDK
动态代理。
spring
中默认采用 JDK
动态代理,如果被代理类不是实现类则强制采用 CGLIB
动态代理。