代理模式是常用的java设计模式,他的特征是代理类与委托类实现同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
代理模式最主要的就是有一个公共接口,一个委托类,一个代理类。
代理可以分为静态代理和动态代理。
静态代理
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,委托类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
例子:假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。
接口
public interface Person {
//上交班费
void giveMoney();
}
委托类
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}
代理类
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();
}
}
调用
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
Person zhangsan = new Student("张三");
//生成代理对象,并将张三传给代理对象
Person monitor = new StudentsProxy(zhangsan);
//班长代理上交班费
monitor.giveMoney();
}
}
为委托类添加功能:比如加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,并在最后称赞张三
public class StudentsProxy implements Person{
//被代理的学生
Student stu;
public StudentsProxy(Person stu) {
// 只代理学生对象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
System.out.println("张三最近学习有进步!");
stu.giveMoney();
System.out.println("张三太吊了!");
}
}
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。
相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
创建动态代理对象的方法可以为
//创建调用处理器
InvocationHandler stuHandler = new StuInvocationHandler<>(zhangsan);
//创建一个代理对象monitor来代理zhangsan,代理对象的每个执行方法都会替换执行InvocationHandler中的invoke方法
Person monitor = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
new Class<?>[]{Person.class}, stuHandler);
具体例子:将System.out.println("张三最近学习有进步!");
和System.out.println("张三太吊了!");
放到一个新类StuInvocationHandler,其余接口,委托类不变,StudentsProxy代理类删除
调用处理器
public class StuInvocationHandler<T> implements InvocationHandler {
private T target;
public StuInvocationHandler(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("张三最近学习有进步!");
Object result = method.invoke(target, args);
System.out.println("张三太吊了!");
return result;
}
}
调用
public class Main {
public static void main(String[] args){
//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
Person zhangsan = new Student("张三");
//创建调用处理器
InvocationHandler stuHandler = new StuInvocationHandler<>(zhangsan);
//创建一个代理对象monitor来代理zhangsan,代理对象的每个执行方法都会替换执行InvocationHandler中的invoke方法
Person monitor = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
new Class<?>[]{Person.class}, stuHandler);
//班长代理上交班费
monitor.giveMoney();
System.out.println();
monitor.toString();
}
}
上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有委托对象执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有委托对象的方法进行相同的操作了。
注意:委托对象执行的所有方法都会调用InvocationHandler中的invoke方法,包括toString(),equals()之类的
所以这里张三上交班费50元
,就放到了CommendStatement.start();
和CommendStatement.end();
的中间。而调用toString()也会打印句子。
动态代理的过程,代理对象和委托对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到类似StudentsProxy的代理类。
动态代理原理分析
实际上动态代理是存在代理类的,但这个代理类是动态生成的。Proxy类的newProxyInstance方法封装了创建动态代理类的步骤。
动态生成的代理类在内存中,反编译成java文件可以得到:
public final class $Proxy0 extends Proxy implements Person
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
*super(paramInvocationHandler),是调用父类Proxy的构造方法。
*父类持有:protected InvocationHandler h;
*Proxy构造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//这个静态块本来是在最后的,我把它拿到前面来,方便描述
static
{
try
{
//giveMoney是通过反射得到的,由m3代表
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("包名.Person").getMethod("giveMoney", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
/**
* 这里override接口中的giveMoney()方法
* 直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
*/
public final void giveMoney()
throws
{
try
{
// toString,hashCode、equals方法也一样,不过传入的是m2,m1,m0
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。
}
总结
-
jdk在运行时为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类
-
这个代理类实现了Person接口,并且继承了Proxy类。因此java动态代理只能对接口进行代理
-
代理类实现接口中的方法,是通过直接调用InvocationHandler对象中的invoke方法实现的
-
而InvocationHandler又持有委托类对象,调用处理器中的invoke方法可以由委托类的方法+额外功能代码组成的
-
代理类通过反射获得委托类中的方法
网友评论