Java的代理模式
前言
为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。
其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理。代理类和被代理类通常会存在关联关系(即上面提到的持有的被带离对象的引用),代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。
静态代理
需求
假如一个班的同学要向老师交班费,都是通过班长把自己的钱转交给老师。这里,班长就是学生的代理。
实现
-
首先,我们创建一个Person接口。
这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。
这样,学生上交班费就可以让班长来代理执行。
public interface Person { /** * 上交班费 */ void giveMoney(); }
-
Student类实现Person接口。Student可以具体实施上交班费的动作。
public class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override public void giveMoney() { System.out.println(name+"上交班费20元"); } }
-
StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,
同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。
public class StudentProxy implements Person { /** * 被代理的学生对象 */ Student student; public StudentProxy(Person person) { // 只代理学生对象 if (person.getClass() == Student.class){ this.student = (Student) person; } } @Override public void giveMoney() { System.out.println("由代理完成上交班费"); student.giveMoney(); } }
-
测试
public class StaticProxyTest { public static void main(String[] args) { //被代理的学生张三,他的班费上交有代理对象monitor(班长)完成 Person zhangsan = new Student("张三"); //生成代理对象,并将张三传给代理对象 Person monitor = new StudentProxy(zhangsan); //班长代理上交班费 monitor.giveMoney(); } }
结果:
由代理完成上交班费
张三上交班费20元
总结
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。
而且,代码编译时就确定了被代理的类是哪一个,这里就是Student类,所以这进一步是静态代码模式。
代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。
动态代理
简介
动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。
相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
比如说,想要在代理类中的每个方法前都加上一个处理方法:
在java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
实现(还是刚刚的需求)
-
新建一个Person接口
public interface Person { /** * 上交班费 */ void giveMoney(); }
-
创建需要被代理的实际类
public class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override public void giveMoney() { try { //假设数钱花了一秒时间 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name + "上交班费50元"); } }
-
创建自己的InvocationHandler
public class StuInvocationHandler<T> implements InvocationHandler { T target; public StuInvocationHandler(T target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理执行" +method.getName() + "方法"); //代理过程中插入监测方法,计算该方法耗时 Instant start = Instant.now(); Object result = method.invoke(target, args); Instant end = Instant.now(); Duration between = Duration.between(start, end); System.out.println("方法执行的时间:" + between.toMillis()); return result; } }
测试
public class DynamicProxyTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1、创建一个InvocationHandler对象
//创建一个实例对象,这个对象是被代理的对象
Person zhangsan = new Student("张三");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
//2、使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
//3、获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
Constructor<?> constructor = stuProxyClass.getConstructor(InvocationHandler.class);
//4、通过构造器constructor来创建一个动态实例stuProxy
Person stuProxy = (Person) constructor.newInstance(stuHandler);
stuProxy.giveMoney();
// 方式二,通过Proxy.newProxyInstance
Person stuProxy2 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
stuProxy.giveMoney();
}
}
总结
先想一下,我们创建了一个需要被代理的学生张三,将zhangsan对象传给了stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面执行代理对象的方法(包括giveMoney)都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。
动态代理的优势在于可以很方便的对代理类的所有方法进行统一的处理,而不用修改代理类中的每个方法。
是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,
就可以对所有被代理的方法进行相同的操作了。
原理
动态代理具体步骤:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
流程图