- 代理模式是常用的java设计模式,代理类与委托类有同样的方法,本质是调用委托类的方法,但是代理类对其进行了增强
- 代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
- 简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途
1.静态代理
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
静态代理也可以看作是装饰者模式
简单实现
1.创建公共接口
/**
* 创建Person接口
*/
public interface Person {
//上交班费
void giveMoney();
}
2.被代理对象,完成具体的业务逻辑
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}
3.代理类实现接口,完成委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息
/**
* 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
* @author Gonjan
*
*/
public class StudentsProxy implements Person{
//被代理的学生
Student stu;
public StudentsProxy(Person stu) {
// 只代理学生对象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
stu.giveMoney();
}
}
4.main方法操作
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
Person zhangsan = new Student("张三");
//生成代理对象,并将张三传给代理对象
Person monitor = new StudentsProxy(zhangsan);
//班长代理上交班费
monitor.giveMoney();
}
2.动态代理
代理类在程序运行时创建的代理方式被称为动态代理。
我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
简单实现
在jdk的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
1.和静态代理类似,创建公共的接口由委托类和代理类实现
2.创建代理类
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理对象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
//代理过程中插入监测方法,计算该方法耗时
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
}
3.main方法使用
public class ProxyTest {
public static void main(String[] args) {
//创建一个实例对象,这个对象是被代理的对象
Person zhangsan = new Student("张三");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
//代理执行上交班费的方法
stuProxy.giveMoney();
}
}
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。
3.JDK动态代理实现
这里介绍JDK的动态代理(cglib是另一种动态代理的实现)。下面一起来过一遍JDK是如何实现动态代理的,这里只介绍一些关键的节点,详细的细节不作过多赘述
public static void main(String[] args) {
// 从这行代码入手
Proxy.newProxyInstance(//指定一个类加载器用于加载代理类
, new Class<?>[]{//这里写的是你需要实现的接口的class数组}
,//你自定义的处理器)
}
我们的核心方法就是Proxy.newProxyInstance,这个方法用于创建我们的代理类,下面来看下该方法如何创建我们的代理类的
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
// 将要实现的接口数组拷贝一份
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
// 后面的方法这里不关注,就是通过得到的cl Class对象,反射得到构造器,创建新对象
// ...
}
主要是通过getProxyClass0(loader, intfs)得到我们的class对象,然后通过反射即可创建我们的代理对象,下面要探究的是,这个方法如何创建出一个新的类的(即动态创建)
其后续实际调用的是 ProxyClassFactory类的apply方法(该类实现了BiFunction函数式接口)
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* 判断传入的类加载器是否可以成功加载接口
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader: " + loader);
}
/*
* 判断传入的是否为接口
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* 判断是否重复传入接口
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // 这个是我们后续代理类的包名
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 获取包名的操作 这里不去关注
// ...
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* 得到一个数字
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* 生成字节码文件
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
// ...
}
}
可以看到这里会验证传入的class数组是不是都是接口类型,否则会报错
得到了我们代理类的类名后,就会调用ProxyGenerator.generateProxyClass来生成我们的字节码文件
public static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
int i = name.lastIndexOf('.');
Path path;
if (i > 0) {
Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
// 将字节码文件写入
Files.createDirectories(dir);
path = dir.resolve(name.substring(i+1, name.length()) + ".class");
} else {
path = Paths.get(name + ".class");
}
Files.write(path, classFile);
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
ProxyGenerator是字节码文件生成类,可以看到最后得到的是一个字节数组,将这个数组通过io操作写入文件中,就完成了我们代理类的编写(详细字节码文件生成操作这里不作赘述)
在外层通过defineClass0操作返回我们的代理类的Class对象,至此生成代理类的操作就完成了
4.总结
简单描述,就是Proxy类有个创建代理对象的方法,通过传入调用的接口的类对象以及类加载器,以及我们自己定义的InvocationHandler类对象(后续执行的方法都是该类中的invoke方法)来创建代理对象;
实际创建的对象是$Proxy类,他默认继承了Proxy类以及实现了我们传入的接口,其中有个成员变量h(这个变量在Proxy中定义,存储的就是我们创建代理对象时候传入的InvocationHandler类对象),而接口中的所有方法中都调用了该对象的invoke方法,这也就是为什么我们不管调用什么方法都会执行invoke方法
而invoke方法中的传入的方法参数就是调用的方法的method对象。