Java 动态代理
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
简单结构示意图:
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。
Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
- (1)Interface InvocationHandler:该接口中仅定义了一个方法:
public object invoke(Object obj,Method method, Object[] args)
在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。
- (2)Proxy:该类即为动态代理类,其中主要包含以下内容:
protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。
static Class getProxyClass (ClassLoaderloader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类(DynamicProxy)可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)
所谓代理类 DynamicProxy 是这样一种class:它是在运行时生成的 class,在生成它时你必须提供一组 interface 给它,然后该 class 就宣称它实现了这些 interface。你当然可以把该 class 的实例当作这些 interface 中的任何一个来用。当然,这 DynamicProxy 其实就是一个 Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个 handler ,由它接管实际的工作。
在使用动态代理类时,我们必须实现InvocationHandler接口
通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系。
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法
- 创建被代理的类以及接口
- 通过Proxy的静态方法 newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
- 通过代理调用方法
/**
* 需要动态代理的接口
*/
public interface Subject
{
/**
* 你好
*
* @param name
* @return
*/
public String SayHello(String name);
/**
* 再见
*
* @return
*/
public String SayGoodBye();
}
/**
* 实际对象
*/
public class RealSubject implements Subject
{
/**
* 你好
*
* @param name
* @return
*/
public String SayHello(String name)
{
return "hello " + name;
}
/**
* 再见
*
* @return
*/
public String SayGoodBye()
{
return " good bye ";
}
}
/**
* 调用处理器实现类
* 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
*/
public class InvocationHandlerImpl implements InvocationHandler
{
/**
* 这个就是我们要代理的真实对象
*/
private Object subject;
/**
* 构造方法,给我们要代理的真实对象赋初值
*
* @param subject
*/
public InvocationHandlerImpl(Object subject)
{
this.subject = subject;
}
/**
* 该方法负责集中处理动态代理类上的所有方法调用。
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
*
* @param proxy 代理类实例
* @param method 被调用的方法对象
* @param args 调用参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("在调用之前,我要干点啥呢?");
System.out.println("Method:" + method);
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object returnValue = method.invoke(subject, args);
//在代理真实对象后我们也可以添加一些自己的操作
System.out.println("在调用之后,我要干点啥呢?");
return returnValue;
}
}
/**
* 动态代理演示
*/
public class DynamicProxyDemonstration
{
public static void main(String[] args)
{
//代理的真实对象
Subject realSubject = new RealSubject();
/**
* InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
* 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
* 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
*/
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
ClassLoader loader = realSubject.getClass().getClassLoader();
Class[] interfaces = realSubject.getClass().getInterfaces();
/**
* 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
*/
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
System.out.println("动态代理对象的类型:"+subject.getClass().getName());
String hello = subject.SayHello("jiankunking");
System.out.println(hello);
// String goodbye = subject.SayGoodBye();
// System.out.println(goodbye);
}
}
因为JDK生成的最终真正的代理类,它继承自Proxy并实现了我们定义的Subject接口, 在实现Subject接口方法的内部,通过反射调用了InvocationHandlerImpl的invoke方法。
通过分析代码可以看出Java 动态代理,具体有如下四步骤:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
常用的代理方式可以粗分为:静态代理和动态代理。
静态代理的实现比较简单:编写一个代理类,实现与目标对象相同的接口,并在内部维护一个目标对象的引用。通过构造器塞入目标对象,在代理对象中调用目标对象的同名方法,并添加前拦截,后拦截等所需的业务功能。
自己手动写一个类并实现接口实在太麻烦了。仔细一想,我们其实想要的并不是代理类,而是代理对象!那么,能否让JVM根据接口自动生成代理对象呢?
比如,有没有一个方法,我传入接口,它就给我自动返回代理对象呢?
需求:在项目现有所有类的方法前后打印日志。
你如何在不修改已有代码的前提下,完成这个需求?
静态代理
具体做法如下:
-
为现有的每一个类都编写一个对应的代理类,并且让它实现和目标类相同的接口(假设都有)
-
在创建代理对象时,通过构造器塞入一个目标对象,然后在代理对象的方法内部调用目标对象同名方法,并在调用前后打印日志。也就是说,代理对象 = 增强代码 + 目标对象(原对象),有了代理对象后,就不用原对象了
静态代理的缺陷
程序员要手动为每一个目标类,编写对应的代理类。如果当前系统已经有成百上千个类,工作量太大了。所以,现在我们的努力方向是:如何少写或者不写代理类,却能完成代理功能?
接口创建对象的可行性分析
复习对象的创建过程
首先,在很多初学者的印象中,类和对象的关系是这样的:
虽然知道源代码经过javac命令编译后会在磁盘中得到字节码文件(.class文件),也知道java命令会启动JVM将字节码文件加载进内存,但也仅仅止步于此了。至于从字节码文件加载进内存到堆中产生对象,期间具体发生了什么,他们并不清楚。
所谓“万物皆对象”,字节码文件也难逃“被对象”的命运。它被加载进内存后,JVM为其创建了一个对象,以后所有该类的实例,皆以它为模板。这个对象叫Class对象,它是Class类的实例。
大家想想,Class类是用来描述所有类的,比如Person类,Student类…那我如何通过Class类创建Person类的Class对象呢?这样吗:
Class clazz = new Class();
好像不对吧,我说这是Student类的Class对象也行啊。有点晕了…
其实,程序员是无法自己new一个Class对象的,它仅由JVM创建。
- Class类的构造器是private的,杜绝了外界通过new创建Class对象的可能。当程序需要某个类时,JVM自己会调用这个构造器,并传入ClassLoader(类加载器),让它去加载字节码文件到内存,然后JVM为其创建对应的Class对象
- 为了方便区分,Class对象的表示法为:Class
,Class
所以借此机会,我们不妨换种方式看待类和对象:
也就是说,要得到一个类的实例,关键是先得到该类的Class对象!只不过new这个关键字实在太方便,为我们隐藏了底层很多细节,我在刚开始学习Java时甚至没意识到Class对象的存在。
接口Class和类Class的区别
来分析一下接口Class和类Class的区别。以Calculator接口的Class对象和CalculatorImpl实现类的Class对象为例:
public class ProxyTest {
public static void main(String[] args) {
/*Calculator接口的Class对象
得到Class对象的三种方式:1.Class.forName(xxx)
2.xxx.class
3.xxx.getClass()
注意,这并不是我们new了一个Class对象,而是让虚拟机加载并创建Class对象
*/
Class<Calculator> calculatorClazz = Calculator.class;
//Calculator接口的构造器信息
Constructor[] calculatorClazzConstructors = calculatorClazz.getConstructors();
//Calculator接口的方法信息
Method[] calculatorClazzMethods = calculatorClazz.getMethods();
//打印
System.out.println("------接口Class的构造器信息------");
printClassInfo(calculatorClazzConstructors);
System.out.println("------接口Class的方法信息------");
printClassInfo(calculatorClazzMethods);
//Calculator实现类的Class对象
Class<CalculatorImpl> calculatorImplClazz = CalculatorImpl.class;
//Calculator实现类的构造器信息
Constructor<?>[] calculatorImplClazzConstructors = calculatorImplClazz.getConstructors();
//Calculator实现类的方法信息
Method[] calculatorImplClazzMethods = calculatorImplClazz.getMethods();
//打印
System.out.println("------实现类Class的构造器信息------");
printClassInfo(calculatorImplClazzConstructors);
System.out.println("------实现类Class的方法信息------");
printClassInfo(calculatorImplClazzMethods);
}
public static void printClassInfo(Executable[] targets){
for (Executable target : targets) {
// 构造器/方法名称
String name = target.getName();
StringBuilder sBuilder = new StringBuilder(name);
// 拼接左括号
sBuilder.append('(');
Class[] clazzParams = target.getParameterTypes();
// 拼接参数
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append(',');
}
//删除最后一个参数的逗号
if(clazzParams!=null && clazzParams.length != 0) {
sBuilder.deleteCharAt(sBuilder.length()-1);
}
//拼接右括号
sBuilder.append(')');
//打印 构造器/方法
System.out.println(sBuilder.toString());
}
}
}
- 接口Class对象没有构造方法,所以Calculator接口不能直接new对象
- 实现类Class对象有构造方法,所以CalculatorImpl实现类可以new对象
- 接口Class对象有两个方法add()、subtract()
- 实现类Class对象除了add()、subtract(),还有从Object继承的方法
也就是说,接口和实现类的 Class 信息除了构造器,基本相似。
既然我们希望通过接口创建实例,就无法避开下面两个问题:
- 接口方法体缺失问题
首先,接口的Class对象已经得到,它描述了方法信息。
但它没方法体。
没关系,反正代理对象的方法是个空壳,只要调用目标对象的方法即可。
JVM可以在创建代理对象时,随便糊弄一个空的方法体,反正后期我们会想办法把目标对象塞进去调用。
所以这个问题,勉强算是解决。
动态代理
java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类的支持。Proxy后面会用到InvocationHandler,因此我打算以Proxy为切入点。首先,再次明确我们的思路:
Proxy类有一个静态方法:Proxy.getProxyClass():返回代理类的Class对象。
也就说,只要传入目标类实现的接口的Class对象,getProxyClass()方法即可返回代理Class对象,而不用实际编写代理类。这相当于什么概念?
public class ProxyTest {
public static void main(String[] args) {
/*
* 参数1:Calculator的类加载器(当初把Calculator加载进内存的类加载器)
* 参数2:代理对象需要和目标对象实现相同接口Calculator
* */
Class calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
//以Calculator实现类的Class对象作对比,看看代理Class是什么类型
System.out.println(CalculatorImpl.class.getName());
System.out.println(calculatorProxyClazz.getName());
//打印代理Class对象的构造器
Constructor[] constructors = calculatorProxyClazz.getConstructors();
System.out.println("----构造器----");
printClassInfo(constructors);
//打印代理Class对象的方法
Method[] methods = calculatorProxyClazz.getMethods();
System.out.println("----方法----");
printClassInfo(methods);
}
public static void printClassInfo(Executable[] targets) {
for (Executable target : targets) {
// 构造器/方法名称
String name = target.getName();
StringBuilder sBuilder = new StringBuilder(name);
// 拼接左括号
sBuilder.append('(');
Class[] clazzParams = target.getParameterTypes();
// 拼接参数
for (Class clazzParam : clazzParams) {
sBuilder.append(clazzParam.getName()).append(',');
}
//删除最后一个参数的逗号
if (clazzParams != null && clazzParams.length != 0) {
sBuilder.deleteCharAt(sBuilder.length() - 1);
}
//拼接右括号
sBuilder.append(')');
//打印 构造器/方法
System.out.println(sBuilder.toString());
}
}
}
输出结果:
com.nostyling.wcms.test.DynamicProxy.test3.CalculatorImpl
com.sun.proxy.$Proxy0
----构造器----
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
----方法----
add(int,int)
equals(java.lang.Object)
toString()
hashCode()
subtract(int,int)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
getInvocationHandler(java.lang.Object)
wait(long)
wait(long,int)
wait()
getClass()
notify()
notifyAll()
也就是说,通过给Proxy.getProxyClass()传入类加载器和接口Class对象,我们得到了一个加强版的Class:即包含接口的方法信息add()、subtract(),又包含了构造器$Proxy0(InvocationHandler),还有一些自己特有的方法以及从Object继承的方法。
梳理一下:
-
原先我们本打算直接根据接口Class得到代理对象,无奈接口Class只有方法信息,没有构造器
-
于是,我们想,有没有办法创建一个Class对象,既有接口Class的方法信息,同时又包含构造器方便创建代理实例呢?
-
利用Proxy类的静态方法getProxyClass()方法,给它传一个接口Class对象,它能返回一个加强版Class对象。也就是说getProxyClass()的本质是:用Class,造Class。
Proxy类和JVM,让我们不写代理类却直接得到代理Class对象,进而得到代理对象。
静态代理:
动态代理:
既然Class<$Proxy0>有方法信息,又有构造器,我们试着用它得到代理实例吧:
invocationHandler的作用,倒像是把“方法”和“方法体”分离。JVM只造一个空的代理对象给你,后面想怎么玩,由你自己组装。反正代理对象中有个成员变量invocationHandler,每一个方法里只有一句话:handler.invoke()。所以调任何一个代理方法,最终都会跑去调用invoke()方法。
设法在invoke()方法得到目标对象,并调用目标对象的同名方法。
不编写实现类,直接在运行期创建某个interface的实例.
Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface 的实例。
先定义了接口:
interface Hello {
void morning(String name);
}
通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
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);
}