目录
1. 背景概念
1.1 静态代理
1.1.1 定义
(1)创建一个接口,然后创建被代理的类,实现该接口并且实现该接口中的抽象方法。
(2)之后再创建一个代理类,同时使其也实现这个接口。
(3)在代理类中持有一个被代理对象的引用,而后在代理类的方法中调用该对象的方法。
1.1.2 静态代理的本质
由程序员创建或工具生成代理类的源码,再编译代理类,生成字节码文件。
所谓静态,也就是在程序运行前。就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
1.2 动态代理
1.2.1 定义
(1)动态代理类的源码,是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。
(2)代理类和委托类的关系是在程序运行时确定。
1.2.2 动态代理的本质
(1)JDK提供了一个生成动态代理的接口,然后我们传入实例。
(2)接下来JDK把传入的实例进行解析,生成了一个新的类$proxcy0(动态代理类)。
(3)最后调用类$proxcy0的动态方法来实现对我们被代理类方法的调用。
(4)而在静态代理中是没有$proxcy0的。
1.2.3 JDK中关于动态代理的重要API
(1)java.lang.reflect.Proxy
这是Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。 最重要的方法是:
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) ,该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例。
(2)java.lang.reflect.InvocationHandler
这是调用处理器接口,定义了一个invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
Object invoke(Object proxy, Method method, Object[] args) 该方法负责集中处理动态代理类上的所有方法调用。第一个参数即是代理类实例,第二个参数是被调用的方法对象 ,第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行。
(3)java.lang.ClassLoader
这是类装载器类,负责将类的字节码装载到Java 虚拟机(JVM)中并为其定义类对象。然后该类才能被使用。
Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是JVM在运行时动态生成的,而非预先存在于任何一个.class 文件中。
每次生成动态代理类对象时都需要指定一个类装载器对象。
1.2.4 Spring的动态代理模式有两种
(1)JDK动态代理(默认代理模式)
是通过JDK中的java.lang.reflect.Proxy类实现的,但只能为接口创建代理。
(2)CGLIB(若要使用,需要进行配置)
是一个高性能开源的代码生成包,它被需要AOP框架所使用,其底层是通过使用一个小而快的字节码处理框架ASM转换字节码并生成新的类。其实现机制是通过继承实现的。其也是动态地创建一个类,但这个类的父类是被代理类。
1.2.5 配置信息
通过配置<aop:config proxy-target-class="">实现动态代理的配置。
true使用CGLIB产生代理对象。false使用JDK动态代理,默认是false。
2. 静态代理的示例工程
2.1 接口
package com.my.test;
public interface BuyHouse {
void buyHouse();
}
2.2 被代理类
package com.my.test;
public class HouseBuyer implements BuyHouse{
public void buyHouse() {
System.out.println("我想要买房子0918");
}
}
2.3 代理类
package com.my.test;
public class HouseProxy implements BuyHouse{
private BuyHouse buyHouse1;
//构造函数
public HouseProxy(BuyHouse buyHouse) {
this.buyHouse1 = buyHouse;
}
public void buyHouse() {
System.out.println("买房前:攒首付");
buyHouse1.buyHouse();
System.out.println("买房后:还贷款");
}
}
2.4 测试类
package com.my.test;
public class MyDemoTest1 {
public static void main(String[] args) {
BuyHouse buyer = new HouseBuyer(); //注意:左边是接口,右边new的是实现接口的类
HouseProxy proxy = new HouseProxy(buyer);
proxy.buyHouse();
}
}
2.5 配置文件 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.my.test</groupId>
<artifactId>SpringProxyDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
</project>
3. 动态代理的示例工程
3.1 接口
package com.my.test.dynamicproxy;
public interface HelloInterface {
void sayHello();
}
3.2 被代理类
package com.my.test.dynamicproxy;
public class Hello implements HelloInterface{
public void sayHello() {
System.out.println("Hello ya Sheryl 0918");
}
}
3.3 代理类/或者说:自己的调用处理器
实现InvocationHandler接口,创建自己的调用处理器
package com.my.test.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler{
private Object object;
//构造方法
public ProxyHandler(Object object) {
this.object = object;
}
//重写invoke方法, 该方法相当于是对代理方法的一个调用,并进行一些处理
//该方法负责集中处理动态代理类上的所有方法调用。第一个参数是代理类实例,第二个参数是被调用的方法对象 ,第三个方法是调用参数。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before Invoke: " + method.getName());
method.invoke(object, args);
System.out.println("after Invoke: " + method.getName());
return null;
}
}
3.4 测试类
package com.my.test.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
HelloInterface hello = new Hello(); //左边是接口,右边new的是被代理的类,这个类继承了接口
//把被代理类hello的实例传入动态代理处理器
//Jdk把传入的实例进行解析,生成了一个新的类$proxcy0(动态代理类)
InvocationHandler handler = new ProxyHandler(hello);
//生成动态代理类实例。jdk中动态代理执行的固定方法
//Proxy.newroxyInstance 相当于是生成了一个代理的实例,通过参数handler 把ProxyHandler 与其联系在了一起
//本质是 Proxy.newProxyInstance 生成了一个实例,而ProxyHandler 的invoke方法 是这个实例中的一个方法
//使用动态代理的好处是,对使用了我动态代理类的方法,进行了统一的管理
HelloInterface helloProxy = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler);
helloProxy.sayHello(); //在代理类$ProxyN的实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的invoke 方法执行
}
}
运行结果:
before Invoke: sayHello
Hello ya Sheryl 0918
after Invoke: sayHello
3.5 (补充)打印生成的动态代理类
再来看一个实例,修改类3.4中的DynamicProxyTest,代码如下:
public class DynamicProxyTest {
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
HelloInterface hello = new Hello();
InvocationHandler handler = new ProxyHandler(hello);
HelloInterface helloProxy = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler);
helloProxy.sayHello();
System.out.println(helloProxy.getClass().getName());
}
}
输出结果:
before Invoke: sayHello
Hello ya Sheryl 0918
after Invoke: sayHello
com.sun.proxy.$Proxy0
结果解析:
(1)我们发现proxyHello的类型是.$Proxy0而不是HelloInterface。我们通过反编译来查看$Proxy0的源码,在工程的com.sun.proxy目录下。
注意:必须添加下面的代码:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
(2)在代理类$ProxyN的实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的invoke 方法执行;
(3)代理类的根类java.lang.Object 中的三个方法:hashCode,equals 和 toString也同样会被分派到调用处理器的invoke 方法中执行。
3.5 配置文件 pom.xml(同2.5)
4. 静态代理和动态代理的几个重要知识点
(1)静态代理在程序运行前就已经存在,代理类的字节码文件中确认了代理类和委托类的关系;
(2)动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。 动态代理根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。
其实现原理如下:
由于JVM是通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。
(3)静态代理的缺点是在程序规模稍大时,维护代理类的成本高,静态代理无法胜任;
(4)JDK默认动态代理只能为了实现接口的类创建代理。延伸: CGLIB