Java代理
关于java代理
代理模式是常用的java设计模式,他的特征是代理类与委托类有相同的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
我们需要关注的重点是
- 代理类与委托类有同样的接口
- 代理类主要负责为委托类预处理消息、过滤消息等简而言之经过代理的类方法被调用后会先经过代理类的处理
- 一个代理类的对象与一个委托类的对象关联
从这里我们能够发现其实实现代理模式需要三个东西:一个公共接口,一个具体的类,一个代理类,代理类持有具体类的实例,代为执行具体类实例方法。
说白了代理就相当于我们租房市场中的中介,房东直接将房子交给中介,让中介来管理自己房子的出租,正因为房东将租房的权利交给了中介,中介就可以在对外租房的时候,做一些额外的操作。
JAVA中的代理机制分为静态代理和动态代理,动态代理在JAVA中较为常用,也是我们主要学习的部分。
静态代理
这种代理方式需要代理对象和目标对象实现一样的接口。但是当需要代理的对象过多就需要实现大量的代理类,并且一旦接口增加方法,目标对象与代理对象都要进行修改。
接下来我们通过代码来深入了解一下静态代理
这里我们就是用租房的示例来演示,首先我们创建一个公共代理接口--租房接口Rent,用来完成租房的功能,该接口就是被代理类和代理类的公共接口
租房接口
public interface Rent {
public void rent();
}
接着我们创建两个房子类型楼房和别墅(被代理类或委托类)
楼房租房
public class Lrent implements Rent{
@Override
public void rent() {
System.out.println("完成楼房租房,租金1000");
}
}
别墅租房
public class Brent implements Rent{
@Override
public void rent() {
System.out.println("完成别墅租房,租金20000");
}
}
接着我们创建中介(代理类)
代理
public class RentProxy implements Rent{
private Rent rent;// 被代理对象
public RentProxy(Rent rent){
this.rent = rent;
}
@Override
public void rent() {
System.out.println("签订租房合同");
rent.rent();
}
}
test类代码
public class test {
public static void main(String[] args) {
//楼房租房
Rent rent = new Lrent();
RentProxy rentProxy = new RentProxy(rent);
rentProxy.rent();
}
}
这里我们在考虑一种情况,如果中途房租增加了10元,我们就要对代理类中的代码进行变更
public class test {
public static void main(String[] args) {
//楼房租房
Rent lRent = new Lrent();
RentProxy lRentProxy = new RentProxy(lRent);
lRentProxy.rent();
System.out.println("============================\r\n");
//别墅租房
Rent bRent = new Brent();
RentProxy bRentProxy = new RentProxy(bRent);
bRentProxy.rent();
}
}
我们可以看到通过对对代理类中的代码进行修改,完成对楼房和租房房租的涨价,但是我们能很明显的感受这样的模式完成代理一个类是很容易的,但如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。并且当接口被改变代理类同样需要改变,这样就产生了更大的局限性和更多麻烦。这就需要动态代理来解决问题了。
动态代理
动态代理介绍
java动态代理首先我们要知道java.lang.reflect
包下提供了一个Proxy类和一个InvocationHandler
接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象,虽然不用创建代理类,但是需要创建InvocationHandler
实现类,来提供动态执行增强逻辑的方法,可以给它想象成一个静态代理
的生成工厂,可以定义一些生成规则。
InvocationHandler接口:负责提供调用代理操作。
是由代理对象调用处理器实现的接口,定义了一个invoke()方法,每个代理对象都有一个关联的接口。当代理对象上调用方法时,
该方法会被自动转发到InvocationHandler.invoke()方法来进行调用。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
//参数介绍
//proxy 被代理的实例对象 method 被调用方法名 args 被调用方法的参数数组
Proxy类:负责动态构建代理类
提供四个静态方法来为一组接口动态生成的代理类并返回代理类的实例对象
- getProxyClass(ClassLoader,Class<?>...):获取指定类加载器和动态代理类对象。
- newProxyInstance(ClassLoader,Class<?>[],InvocationHandler):指定类加载器,一组接口,调用处理器;
- isProxyClass(Class<?>):判断获取的类是否为一个动态代理类;
- getInvocationHandler(Object):获取指定代理类实例查找与它相关联的调用处理器实例
动态代理实现
1、使用java.lang.InvocationHandler接口创建自定义调用处理器,由它来实现invoke方法,执行代理函数;
2、使用java.lang.reflect.Proxy类指定一个ClassLoader,一组interface接口和一个InvocationHandler;
3、通过反射机制获得动态代理类的构造方法,其唯一参数类型是调用处理器接口类型;
4、调用java.lang.reflect.Proxy.newProxyInstance()方法,分别传入类加载器,被代理接口,调用处理器;创建动态代理实例对象。
5、通过代理对象调用目标方法;
动态代理和静态代理一样也需要三样东西:公共接口,代理对象,代理类。区别就是动态代理是利用反射机制在运行时创建代理类。
接着我们接续使用动态代理实现刚才的租房功能,因为接口类和委托类不需要更改所以,这里就不重复展示代码了
代理类
public class RentProxyTest implements InvocationHandler {
//rent为委托类(被代理类)对象
private Object rent;
public RentProxyTest(Object rent){
this.rent = rent;
}
//实现java.lang.reflect.InvocationHandler.invoke()方法
//重写invoke函数,每次调用动态代理的对象函数都会被替换成执行这个invoke函数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//添加自定义的委托类逻辑
//调用method调用invoke函数,这里的主要目的,就是为了使原函数生效
//既然如此如果对所有都加些其它操作,就可以再下边这行代码前后加语句了,就会令所有函数都生效了,例如我一会举例的都加收10元
System.out.println("加收10元房租");
//调用委托类的方法
Object result = method.invoke(rent,args);
return result;
}
}
测试类
public class testMain {
public static void main(String[] args) {
//获取委托类的实例对象
Lrent lrent = new Lrent();
//获取ClassLoader
ClassLoader classLoader = lrent.getClass().getClassLoader();
//获取所有接口
Class [] interfaces = lrent.getClass().getInterfaces();
//获取一个调用处理器
InvocationHandler invocationHandler = new RentProxyTest(lrent);
//查看生成的代理类
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//创建代理对象
Rent proxy = (Rent)Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
//调用代理对象
proxy.rent();
}
}
执行结果
在com/sun/proxy下我们可以看到一个$Proxy0
的class文件,这是我们生成的动态代理类的字节码文件
package com.sun.proxy;
import com.alexsel.test.Rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Rent {
//私有静态构造方法
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
//获取调用处理器实例对象
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//获取调用接口类的rent()方法,转发哦到调用处理器中的invoke()方法中进行处理
public final void rent() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (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"));
m3 = Class.forName("com.alexsel.test.Rent").getMethod("rent");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
动态代理简单利用
有了动态代理我们就需要考虑其如何进行利用,这里我们简单搭建一个攻击场景
首先,编写一个teacher公共接口类
public interface Teacher {
Object getObject();
void attack();
}
然后编写一个类A实现teacher类
public class A implements Teacher{
@Override
public Object getObject() {
return null;
}
@Override
public void attack() {
System.out.println("attack");
}
}
一个实现teacher类的后门类Backdoor
public class Backdoor implements Teacher{
@Override
public Object getObject() {
return null;
}
@Override
public void attack() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
一个可以利用的代理类myProxy
public class myProxy implements InvocationHandler {
private Object object;
public myProxy(Object o){
this.object = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return this.object;
}
}
一个代理类
public class myProxyHandler implements InvocationHandler {
private A object;
public myProxyHandler(Object object){
this.object = (A)object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method is "+method.getName());
method.invoke(this.object.getObject(),args);
return null;
}
}
我们的最终目的是需要调用到后门类里的attack方法,目前我们能够控制的是myProxyHandler.invoke()
的method方法名为attack,因为Teacher接口里面也有attack方法。目前的关键就是让this.object.getObject()
能够返回一个Backdoor后门类实例对象就可以完成attack对象的调用,所以我们现在需要寻找一个新的代理,我们能够控制这个代理的invoke返回对象,然后用它来代理myProxyHandler
的object,当调用到this.object.getObject()
进入到我们找的可以利用的代理对象中控制返回对象为一个Backdoor后门实例,所以很明显这里的myProxy
就是那个新的代理。
最终利用代码
public class testMain {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
A a = new A();
Backdoor backdoor = new Backdoor();
InvocationHandler backdoorHandler = new myProxy(backdoor);
Teacher proxyInstane = (Teacher) Proxy.newProxyInstance(
backdoor.getClass().getClassLoader(),
new Class[]{Teacher.class},
backdoorHandler);
InvocationHandler handler = new myProxyHandler(t);
Field field = handler.getClass().getDeclaredField("object");
field.setAccessible(true);
//field.set(),给对象的某个成员变量注入数据
field.set(handler,proxyInstane);
Teacher proxyTest = (Teacher) Proxy.newProxyInstance(t.getClass().getClassLoader(),
t.getClass().getInterfaces(),handler);
proxyTest.attack();
}
}
动态代理的特性:
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了