07.实现ProxyFactory


title: 07.实现ProxyFactory
tag: 笔记 手写SSM AOP

通过JDK的动态代理和ByteBuddy字节码技术来生成代理代理对象

Spring的AOP

在实现了IOC容器后,我们就可以继续实现AOP了,在这之前我们需要首先了解Spring的AOP。

AOP与代理(Proxy)模式基本类似,关于代理模式在之前实现BeanPostProcessor时已经介绍过,基于一个接口或者基于继承来代理一个类对其功能进行增强。

我们又可以根据生成代理对象的方式分为两种代理:

  • 静态代理

这种代理方式就是手动为被代理对象编写一个代理对象来实现代理。

  • 动态代理

这种方式是在运行期间动态生成一个代理类的字节码码文件(.class文件),并通过Java的类加载器ClassLoader将动态生成的代理类字节码文件进行加载生成一个Class对象在堆中,这样就可以拿到代理类对象了。

同时,动态代理又有两种实现方式:

  1. 使用Java标准库的动态代理机制,不过仅支持对接口代理,无法对具体类实现代理;
  2. 使用CGLIB或Javassist这些第三方库,通过动态生成字节码,可以对具体类实现代理。

Spring的实现方式是:

  • 如果被代理对象实现了了接口,Spring直接使用JDK实现对接口的代理。
  • 如果被代理对象没有实现接口,那么Spring就使用CGLIB动态生成字节码实现代理。

除了实现代理外,还得有一套机制让用户能定义代理。Spring又提供了多种方式:

  1. 用AspectJ的语法来定义AOP,比如execution(public * com.itranswarp.service.*.*(..))
  2. 用注解来定义AOP,比如用@Transactional表示开启事务。

Summer的实现思路

我们使用和Spring相同的代理实现方式:

  • 如果被代理对象实现了了接口,Spring直接使用JDK实现对接口的代理。
  • 如果被代理对象没有实现接口,那么Spring就使用CGLIB动态生成字节码实现代理。

而切面的类我们同样仿照Spring的方式来实现:

  • 切面(Aspect):用户需要使用@Aspect定义一个切面类。
  • 通知(Advice):我们项目仅支持Around的通知,因为其它通知可以使用Around来实现。
  • 切入点(Pointcut):我们仅支持用户自定义注解来定义切面,用户需要在@Around注解中传入需要匹配的注解。
  • 目标对象(Target Object):即被代理的对象。

明确了需求,我们来看如何实现动态生成字节码。Spring采用的是CGLIB,而目前CGLIB已经停止维护,因此我们选择ByteBuddy来动态生成字节码。

我们这里先实现代理的创建,我们使用工厂模式来创建代理对象,并且使用策略模式来根据不同情况选择不同的方式来生成代理。

实现代理对象的创建

  • 如果被代理对象实现了了接口,Spring直接使用JDK实现对接口的代理。
  • 如果被代理对象没有实现接口,那么Spring就使用CGLIB动态生成字节码实现代理。

看到上面的需求我们就可以很自然的想到策略模式来实现。

实现创建代理的不同策略

我们先创建一个策略的接口GeneratorProxyStrategy

public interface GeneratorProxyStrategy {
    Object createProxy(Object bean, InvocationHandler handler);
}

策略类必须要实现一个createProxy方法。

之后我们可以根据需求实现GeneratorProxyStrategy

  1. 使用ByteBuddy动态生成字节码,这种方式是基于继承方式实现的代理。
public class ByteBuddyStrategy implements GeneratorProxyStrategy{
    ByteBuddy byteBuddy = new ByteBuddy();
    @Override
    public Object createProxy(Object bean, InvocationHandler handler){
        Class<?> targetClass = bean.getClass();
        Class<?> proxyClass = byteBuddy
                .subclass(targetClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)//默认构造器创建子类
                .method(ElementMatchers.isPublic()) //代理pubulic方法
                .intercept(InvocationHandlerAdapter.of(
                        handler)) //传入InvocationHandler,代理方法的逻辑
                .make() //生成字节码
                .load(targetClass.getClassLoader()) // 使用类加载器加载代理对象Class到堆中
                .getLoaded(); //拿到被代理对象
        Object proxy = null;
        try {
            proxy = proxyClass.getConstructor().newInstance();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
        return proxy;
    }
}

ByteBuddy生成代理对象的写法不难理解,我们需要重点注意的只是InvocationHandler。这个我们在最后介绍。

  1. 使用JDK动态生成字节码,这种方式是基于接口方式实现的代理。
public class JDKStrategy implements GeneratorProxyStrategy{
    @Override
    public Object createProxy(Object bean, InvocationHandler handler) {
        return  Proxy.newProxyInstance(bean.getClass().getClassLoader(), //传入类加载器
                bean.getClass().getInterfaces(), //拿到要代理的接口方法
                handler); 
    }
}

实现ProxyFactory

/**
 * @author 白日
 * @create 2024/1/1 16:06
 * @description 创建代理对象的工厂
 */

public class ProxyFactory {
    GeneratorProxyStrategy proxyStrategy;
    public Object createProxy(Object bean, InvocationHandler handler){
        chooseStrategy(bean);//选择代理策略
        return proxyStrategy.createProxy(bean, handler);//创建实例
    }
   /**
     * 根据对象的接口数量,选择代理策略
     * @param bean 代理对象
     */
    private void chooseStrategy(Object bean){
        if(isUniqueInterface(bean.getClass())){
            this.proxyStrategy = new JDKStrategy();
        }else{
            this.proxyStrategy = new ByteBuddyStrategy();
        }
    }
    //该类是否只有一个接口
    private Boolean isUniqueInterface(Class<?> clazz){
        Class<?>[] interfaces = clazz.getInterfaces();
        return interfaces.length == 1;
    }
}

这样,我们只需要一个Bean和一个InvocationHandler就可以创建这个Bean的一个代理对象,最后我们来了解一个这个InvocationHandler是什么。

InvocationHandler

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用

invoke方法的签名:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
}
  • proxy:表示代理对象
  • method:表示被代理对象的一个方法
  • args:方法的参数

我们可以在invoke方法中编写代理对象方法的执行逻辑。

前面说过代理对象调用方法时都会将调用转发到InvocationHandler接口类的invoke方法来调用,那么我们在invoke方法中再次使用proxy调用method方法,就会再次转发到InvocationHandler接口类的invoke方法,这样就可以实现一种递归的结构

下面我们编写一个InvocationHandler实现回溯:

public class PoliteInvocationHandler implements InvocationHandler {
    int index = 0;
    Object target; //原始对象
    public PoliteInvocationHandler(Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method originMethod = target.getClass()
                .getDeclaredMethod(method.getName(), method.getParameterTypes());
        // 修改标记了@Polite的方法返回值:
        if (originMethod.getAnnotation(Polite.class) == null) {
            return method.invoke(proxy, args);
        }
        if(index == 3) return method.invoke(target); // 递归出口,调用被代理对象的方法
        index++;
        System.out.println("执行被代理方法前" + index);
        Object res = method.invoke(proxy,args);   //递归入口,等于Object res = invoke(proxy, method, args);
        System.out.println("执行被代理方法后" + index);
        index--;
        return res;
    }
}

执行效果:

执行被代理方法前1
执行被代理方法前2
执行被代理方法前3
hello1,Bob
执行被代理方法后3
执行被代理方法后2
执行被代理方法后1

从执行结果可以看出我们在InvocationHandler中实现了递归的结构,这在下节实现Around中一个PointCut对应多个Advice的主要思想。

`Moq.ProxyFactory` 是 Moq 框架中的一个内部类,用于创建代理对象。如果在初始化或使用过程中引发异常,可能是因为你尝试设置了一个无效的默认行为(如 `CreateProxy` 方法)或者传入了不正确的参数。 要解决这个问题,首先查看异常的具体信息,它通常会告诉你错误的原因。常见的问题包括: 1. **配置错误**:确保你在创建 `Mock` 对象时没有误用 `ProxyFactory` 的构造函数。例如,如果你需要指定自定义的行为或者策略,可能需要提供合适的参数,比如 `new Mock<T>(null, o => o.Strict())`。 2. **依赖注入问题**:如果 `ProxyFactory` 需要某个特定类型的依赖而没有被正确注入,检查 DI 注册是否正确。 3. **接口实现问题**:确认你要模拟的类型实现了所有必要的接口,否则在创建代理时可能会因为缺少某些方法而失败。 4. **版本兼容性**:检查使用的 Moq 版本是否与项目其他库兼容。有时更新或降级 Moq 可能会解决问题。 修改步骤如下: ```csharp // 示例代码(假设异常来源于这里) var mock = new Mock<IMyInterface>(MockBehavior.Strict, new ProxyFactory()); ``` 根据异常消息进行调整: - 如果是因为严格的模式导致的(`o => o.Strict()`),你可以尝试更宽松的行为模式,如 `MockBehavior.Default` 或者去掉行为约束。 - 如果是关于依赖的,确保已注册并注入到构造器中。 - 如果是接口实现问题,确保`IMyInterface`正确实现了所有所需的方法。 一旦找到具体的问题所在,修复代码后异常就应该消失了。记得在修复后测试以验证问题已经解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值