05-Java代码审计-设计模式-代理模式

137 阅读10分钟

Java设计模式-代理模式(Proxy Pattern)

目录

  • 什么是代理模式
  • 三种代理模式的实现(未完成)
  • JavaSE代理模式的应用
  • Struts2代理模式的应用

1、使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式

2、目标对象相对稳定

一、什么是代理模式

抖音上你看到一个主播很漂亮,不对,是很专业,你想找她带货,打开她的主页你能直接联系到她吗?不可以,一般都是找到她的助理,代理经过一番筛选后才会到漂亮主播那里,不对,是专业主播。

这就是代理,就像明星的助理、领导的秘书,你要找领导办事、找主播带货,最终是谁帮你?是主播和领导,他们是最终做事的人。而助理是屏蔽一些干扰的额信息、过滤领导不喜欢的人,对不对。

代理模式是一种使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式。

结合前面的例子,使用代理模式的原因可以总结为:

  1. 隔离:客户类不能直接引用一个对象,代理类在客户类和委托对象之间起到代理作用,实现双方的沟通
  2. 开闭原则:委托对象的功能相对稳定,当有新的扩展功能时修改代理类,而不修改委托对象,例如在业务功能类的基础上添加权限管理、日志记录等功能

二、三种代理模式的实现

代理模式可以分为:静态代理、动态代理、cglib动态代理,代理模式的主要角色有

  1. 抽象角色(Subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实角色(Real Subject):实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy):提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
  4. 客户 : 使用代理角色来进行一些操作 .
2.1 静态代理

静态代理是最基础的的代理模式实现方法

package org.proxypattern.staticProxy;
/**
 * 老板类,老板可以开会和参加晚会
 */
public abstract class Boss {
    public abstract void doMeeting(String meetingName);
    public abstract void doParty(String partyName);
    public boolean freeTime(){
        // 使用随机模拟是否有时间
        Random random = new Random();
        return random.nextBoolean();
    }
}

/**
 * 真实老板
 */
public class RealBoss extends Boss{

    @Override
    public void doMeeting(String meetingName) {
        System.out.println("老板参加了【" + meetingName + "】会议");
    }

    @Override
    public void doParty(String partyName) {
        System.out.println("老板参加了【" + partyName + "】宴会");
    }
}

/**
 * 代理类
 */
public class BossProxy extends Boss{
    private Boss boss;
    public BossProxy(){
        // 默认代理RealBoss类
        boss = new RealBoss();
    }
    public BossProxy(Boss boss){
        // 也可以代理其他的boss类
        this.boss = boss;
    }

    @Override
    public void doMeeting(String meetingName) {
        // 如果老板有时间就可以安排会议
        if (this.freeTime()){
            System.out.println("老板有时间,可以安排会议");
            this.boss.doMeeting(meetingName);
        }else {
            System.out.println("老板没时间参加会议,请回吧");
        }
    }

    @Override
    public void doParty(String partyName) {
        // 如果老板有时间就可以安排晚宴
        if (this.freeTime()){
            System.out.println("老板有时间,可以安排晚宴");
            this.boss.doParty(partyName);
        }else {
            System.out.println("老板没时间参加晚宴,请回吧");
        }
    }
}

public class Client {
    public static void main(String[] args) {
        BossProxy bossProxy = new BossProxy();
        // 模拟三个人申请老板的时间
        for (int i=0; i<3;i++) {
            bossProxy.doMeeting("融资会议");
            bossProxy.doParty("庆功酒");
            System.out.println("-----------------------------------------------");
        }
    }
}

// 运行结果
老板有时间,可以安排会议
老板参加了【融资会议】会议
老板没时间参加晚宴,请回吧
-----------------------------------------------
老板没时间参加会议,请回吧
老板有时间,可以安排晚宴
老板参加了【庆功酒】宴会
-----------------------------------------------
老板有时间,可以安排会议
老板参加了【融资会议】会议
老板有时间,可以安排晚宴
老板参加了【庆功酒】宴会

总结下该代码所对应的代理模式的关键元素

  1. 抽象角色(Subject):Boss
  2. 真实角色(Real Subject):RealBoss
  3. 代理(Proxy):BossProxy
  4. 客户 : Client

静态代理的优点如下:

  • 使用静态代理时,Client不需要知道真实角色RealBoss
  • 能够在不修改真实角色的情况下,扩展真实对象功能

静态代理缺点如下:

  • 真实角色需要和代理同时实现接口,当代理多个角色时会导致两种情况
    • 只维护一个代理类,代理类实现多个抽象角色的接口,这样代理类会变得很大
    • 新建多个代理类,每个代理对应一个角色,这会导致代理数量会变多
  • 当抽象角色需要修改接口时,真实角色和代理都需要修改,不容易维护

总而言之,越来越多的抽象角色会导致越来越多的代理,无论是多个代理还是一个代理中庞大的代码,这都不是我们想要的,如果解决代理类不断增加的问题?

代理类的增加是因为代理类与抽象角色的耦合导致,如何将两者解耦是解决问题的关键。

2.2 动态代理

静态代理是我们手动编写代码,然后编译为class文件运行,而动态代理则是在运行的过程中产生class文件,然后运行,总结来说就是代理类是动态生成的,所以称为动态代理。

要搞懂动态代理需要两个前置知识:

  1. 类的动态加载
  2. 反射机制

这两个知识我们在《Mark链接-Java的反射机制》中有介绍,本文不在展开讲解

动态代理的创建步骤是这样的:

  • 实现InvocationHandler接口,在里面写扩展逻辑,相当于静态代理的Proxy
  • 通过Java的Proxy类创建动态代理,参数需要:代理角色的ClassLoader、interfaces、InvocationHandler的实现类,这个步骤是创建动态代理,因为要代理目标角色,所以必须要知道目标角色的ClassLoader和interfaces
  • Java的Proxy类内部通过反射动态创建目标角色的代理类,参数为InvocationHandler的实现类(该类持有目标角色对象,从而间接的调用目标角色的方法)
  • 使用生成的额代理类调用相应方法

我们用一个案例说明下整个过程

package org.proxypattern.dynamicProxy;

/**
 * 老板类,必须是接口,动态代理生成时会检测,老板可以开会和参加晚会
 */
public interface Boss {
    public void doMeeting(String meetingName);
    public void doParty(String partyName);
    public boolean freeTime();
}

public class RealBoss implements Boss {

    @Override
    public void doMeeting(String meetingName) {
        System.out.println("老板参加了【" + meetingName + "】会议");
    }

    @Override
    public void doParty(String partyName) {
        System.out.println("老板参加了【" + partyName + "】宴会");
    }
    @Override
    public boolean freeTime(){
        // 使用随机模拟是否有时间
        Random random = new Random();
        return random.nextBoolean();
    }
}

/**
 * InvocationHandler的实现类,invoke方法内添加自己的业务逻辑
 */
public class BossProxyHandler implements InvocationHandler {
    // 被代理的对象,可以用Object类型
    private Boss boss;

    public BossProxyHandler(Boss boss){
        this.boss = boss;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.before();
        // 如果老板有时间就可以安排会议
        if (this.boss.freeTime()){
            System.out.println("老板有时间,可以安排" + args[0]);
            method.invoke(boss,args);
        }else {
            System.out.println("老板没时间参加"+args[0]+",请回吧");
        }
        this.after();
        return null;
    }
    // 为了简单,不在添加方法体
    private void before(){}
    // 一般来说有返回值,为了简单,不在添加
    private void after(){}
}

public class Client {
    public static void main(String[] args) {
        RealBoss realBoss = new RealBoss();
        // InvocationHandler持有真正的目标角色
        InvocationHandler bossProxyHandler = new BossProxyHandler(realBoss);
        Boss realBoss1 = (Boss) Proxy.newProxyInstance(
                realBoss.getClass().getClassLoader(),
                realBoss.getClass().getInterfaces(),
                bossProxyHandler);
        for (int i=0; i<3;i++) {
            realBoss1.doMeeting("融资会议");
            realBoss1.doParty("庆功酒");
            System.out.println("-----------------------------------------------");
        }
    }
}

通过以上代码可以看到BossProxyHandler已经不需要实现Boss类,但其实还是耦合Boss类,因为在BossProxyHandler内部出现了Boss对象,并且调用了freeTime方法,并不是一个很好的案例,可以考虑将freeTime的判断写到RealBoss的方法中,这样就能实现解耦,但老板会自己看时间表吗?哈哈,这件事其实还是得助理来做,大家有什么好方法解决吗?

代码中通过Proxy.newProxyInstance方法创建了代理,它内部是如何实现的呢?

Proxy类的newProxyInstance方法如下,省略了部分代码,只保留的关键部分

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
		// 克隆interfaces(返回的是Boss接口,因为RealBoss实现了Boss接口)
    final Class<?>[] intfs = interfaces.clone();
    // 获取Class,核心方法就是这里
    Class<?> cl = getProxyClass0(loader, intfs);

        
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    return cons.newInstance(new Object[]{h});
}
  
private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
    // 在缓存中获取,proxyClassCache为WeakCache实现类
    return proxyClassCache.get(loader, interfaces);
}

WeakCache类的get方法,只保留的关键代码

public V get(K key, P parameter) {
		// 生成获取缓存的key,classloader与interface组成联合主键
    Object cacheKey = CacheKey.valueOf(key, refQueue);

    // 获取key对应的valuesMap,Map中的Supplier为真正的创建Class的Factory
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    
    // 创建次级 Key
    // 同时检索是否存在过去有同一个 ClassLoader 加载的表示 Supplier<V>,也就是valuesMap的Key
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    // 获取subKey对应的supplier
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;

    while (true) {
        if (supplier != null) {
            // 获取
            V value = supplier.get();
            if (value != null) {
                return value;
            }
        }
        // 下面代码都是构造supplier,也就是用于构建Class的Factory
    }
}

public synchronized V get() { // serialize access
    // 构建Class对象      
    value = Objects.requireNonNull(valueFactory.apply(key, parameter));
}

在valueFactory.apply的内部是根据interface的方法(Boss接口)重新组装一个Class对象,也就是真正的动态代理类,原理就是拼接字节码

Proxy类的apply方法如下

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    // 创建byte类型的class文件
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
    // 有class文件就可以创建对象了
    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
}

ProxyGeneerator类的generateProxyClass方法如下

public static byte[] generateProxyClass(final String name,
                                            Class<?>[] interfaces,
                                            int accessFlags)
{
    ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
    // 生成class文件内容
    final byte[] classFile = gen.generateClassFile();
    // 如果需要保存,则写入到硬盘的文件中,如果不需要直接返回class内容
    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;
}

ProxyGeneerator类的generateClassFile方法如下,就是一个组装class文件的过程

private byte[] generateClassFile() {
		// 添加hashcode、equals、toString三个基本方法
    addProxyMethod(hashCodeMethod, Object.class);
    addProxyMethod(equalsMethod, Object.class);
    addProxyMethod(toStringMethod, Object.class);
    // 添加Boss接口的方法
    for (Class<?> intf : interfaces) {
        for (Method m : intf.getMethods()) {
            addProxyMethod(m, intf);
        }
    }
		
    try {
        // 添加构造函数
        methods.add(generateConstructor());
        
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            for (ProxyMethod pm : sigmethods) {

                // 为方法的method对象添加静态字段
                fields.add(new FieldInfo(pm.methodFieldName,
                    "Ljava/lang/reflect/Method;",
                     ACC_PRIVATE | ACC_STATIC));

                // generate code for proxy method and add it
                methods.add(pm.generateMethod());
            }
        }

        methods.add(generateStaticInitializer());
    }
    
}

组装完成后会有个class的字节码,然后通过defineClass0方法创建Java对象,该方法是native类型,无法看源码。这就是动态代理的生成过程,在generateProxyClass方法中如果saveGeneratedFiles属性为true则可以将class内容写入文件中,我们将saveGeneratedFiles改为true运行代码, 会在项目的com.sun.proxy中生成$Proxy0.class文件

直接用IDEA打开即可自动反编译

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.proxypattern.dynamicProxy.Boss;

// 也是实现了Boss接口,只不过是动态生成的
public final class $Proxy0 extends Proxy implements Boss {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m5;
    private static Method m0;
    // 构造函数中传递InvocationHandler,InvocationHandler中持有RealBoss对象
    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);
        }
    }

    public final boolean freeTime() throws  {
        try {
            return (Boolean)super.h.invoke(this, m4, (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);
        }
    }
		// 动态创建的doParty方法
    public final void doParty(String var1) throws  {
        try {
            // InvocationHandler反射调用m3方法,也就是doParty,在下面的静态代码块中有定义
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void doMeeting(String var1) throws  {
        try {
            super.h.invoke(this, m5, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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"));
            m4 = Class.forName("org.proxypattern.dynamicProxy.Boss").getMethod("freeTime");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("org.proxypattern.dynamicProxy.Boss").getMethod("doParty", Class.forName("java.lang.String"));
            m5 = Class.forName("org.proxypattern.dynamicProxy.Boss").getMethod("doMeeting", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

通过阅读$Proxy0类的代码,可以看到实现了Boss接口,继承了Proxy类,调用InvocationHandler的Invoke方法,从而进入BossProxyHandler的invoke方法,然后反射调用RealBoss的方法,总结图如下

image-20230304200849818

2.3 CGLib动态代理(未完成)

JDK动态代理中提供一个Proxy类来创建代理类,而在CGLib动态代理中也提供了一个类似的类Enhancer

该篇章后期补齐,先挖个坑

三、JavaSE代理模式的应用

下面内容需要Java注解知识,可以先学习《Mark连接-Java注解原理》

Java的AnnotationInvocationHandler使用了动态代理模式(sun.reflect.annotation包中),AnnotationInvocationHandler是Java专门用于处理注解的Handler,类似于前面案例中使用的BossProxyHandler类,AnnotationInvocationHandler通过实现InvocationHandler接口,重写invoke方法完成对注解的处理,先看下类的内容

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;
    // 构造函数,传入注解类型以及注解类对应的注解属性和值
    // memberValues,键是我们注解属性名称,值就是该属性当初被赋上的值。
    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }
    // 实现Invoke方法
    public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            switch (var4) {
                case "toString":
                    return this.toStringImpl();
                case "hashCode":
                    return this.hashCodeImpl();
                case "annotationType":
                    // 如果是取annotationType,直接返回
                    return this.type;
                default:
                    // 取出注解key对应的值
                    Object var6 = this.memberValues.get(var4);
                    if (var6 == null) {
                        throw new IncompleteAnnotationException(this.type, var4);
                    } else if (var6 instanceof ExceptionProxy) {
                        throw ((ExceptionProxy)var6).generateException();
                    } else {
                        if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                            var6 = this.cloneArray(var6);
                        }

                        return var6;
                    }
            }
        }
    }
    // 省略部分代码
}

AnnotationInvocationHandler的作用说简单点就是获取被修饰的属性、方法、参数等等注解的值,例如下面的例子,可以获取getUserInfo的方法是否被@LoginRequired修饰,如如果方法被修饰了,那么就需要先登录。上面AnnotationInvocationHandler.invoke()获取的值就是@LoginRequired(value = true)中的true

class Resource{
    
    public void getImage(){
        System.out.println("获取图成功片");
    }

    @LoginRequired(value = true)
    public void getUserInfo(){
        System.out.println("获取用户信息成功");
    }
}

现在我们通过一个完整的例子讲解注解是如何实现动态代理的

要完成的需求:通过注解实现资源访问的权限验证功能,如果方法被@LoginRequired修饰为true,那么就需要先登录,如果为false则可以直接访问资源

代码如下:

package org.proxypattern.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 */
@Target(value = {ElementType.METHOD,ElementType.FIELD})  //  注解用于字段上
@Retention(RetentionPolicy.RUNTIME)  // 保留到运行时,可通过注解获取
public @interface LoginRequired {
    boolean value() default false;
}

/**
 * 测试类
 */
public class TestAnnotation {
    public static void main(String[] args) throws Exception {
        // 将动态生成的代理类,写入硬盘
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 模拟请求用户信息
        getUserInfo();
        System.out.println("-----------------------------");
        // 模拟获取图片
        getImage();
    }
    public static void getUserInfo() throws Exception {
        System.out.println("正在请求用户信息");
        if (isRequireLogin("getUserInfo")){
            System.out.println("用户信息请求失败,请您先登录系统");
        }else {
            new Resource().getUserInfo();
        }
    }

    public static void getImage() throws Exception {
        System.out.println("正在请求图片信息");
        if (isRequireLogin("getImage")){
            System.out.println("请您先登录系统");
        }
        new Resource().getImage();
    }

    /**
     * 判断请求的HTTP方法是否需要先登录
     * @param methodName 用户请求的方法名
     * @return
     * @throws Exception
     */
    public static boolean isRequireLogin(String methodName) throws Exception{
        Class cls = Resource.class;
        // 获取用户请求的方法名
        Method method = cls.getMethod(methodName);
        // 查看该方法是否被LoginRequired注解修饰
        LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
        // 如果不为空,说明被修饰了,需要登录
        return loginRequired.value();
    }
}

class Resource{

    public void getImage(){
        System.out.println("获取图成功片");
    }

    @LoginRequired(value = true)
    public void getUserInfo(){
        System.out.println("获取用户信息成功");
    }
}

// 运行结果如下
正在请求用户信息
用户信息请求失败,请您先登录系统
-----------------------------
正在请求图片信息
获取图成功片

可以看到运行结果符合预期,再看下动态生成的代理类

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.proxypattern.annotation.LoginRequired;

public final class $Proxy1 extends Proxy implements LoginRequired {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public $Proxy1(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);
        }
    }

    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 Class annotationType() throws  {
        try {
            return (Class)super.h.invoke(this, m4, (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);
        }
    }

    public final boolean value() throws  {
        try {
            return (Boolean)super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("org.proxypattern.annotation.LoginRequired").getMethod("annotationType");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("org.proxypattern.annotation.LoginRequired").getMethod("value");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

真的是和前面例子中BossProxyHandler结构几乎一样,AnnotationInvocationHandler生成的Proxy类同样继承了Proxy类,实现了自定义注解类LoginRequired,并且实现了value方法,通过AnnotationInvocationHandler的invoke方法获取注解对应的值,这样整个流程就非常的清晰了,代码调试验证下思路

打2个断点,如下面两个图

image-20230305104306037

添加条件断点var1.toString().indexOf("LoginRequire") > -1

image-20230305104624628

接下来断点运行到此处,可以看到memberValues中存放的是注解的key与value

image-20230305104446705

那是如何调用到AnnotationInvocationHandler的invoke方法的呢?流程如下,几乎和前面动态代理的流程图一样

image-20230305105624796

四、Struts2代理模式的应用

Struts2的Action使用了静态代理模式,也就是ActionProxy,需要先了解Struts2处理HTTP请求的过程,建议先阅读《Mark链接-Struts2基础知识》

总结下ActionProxy所对应的代理模式的关键元素

  1. 抽象角色(Subject):ActionInvocation
  2. 真实角色(Real Subject):DefaultActionInvocation
  3. 代理(Proxy):StrutsActionProxy
  4. 客户 : Dispatcher

总结下ActionProxy与ActionInvocation的功能

  1. ActionProxy对如何获取Action对象进行了封装

  2. ActionInvocation对如何执行Action进行了封装

调用的过程为:StrutsActionProxy.execute->DefaultActionInvocation.invoke()->xxInterceptor.intercept()-自定义Action类

在ActionProxy中持有ActionInvocation对象,ActionProxy的execute方法负责调用DefaultActionInvocation的invoke方法。

image-20230306104400515