快速理解动态代理(从理解到AOP应用)

73 阅读7分钟

快速理解动态代理(从理解到AOP应用) 简单的来说就是我们在不用修改原代码的情况下,可以对一个对象进行扩展和装饰,给他披上新衣。代理的意思可以这样理解,我写好一段对象代码等同于我制造出一只🐔,现在我把这只🐔交给Proxy类,并告诉他这只🐔的信息(种类(类名),会跳什么舞(方法),重量(参数)),代理类proxy得知了这之后,会在使用时内部实现invoke()方法,就可以对这只🐔进行代理操作,比如可以给他穿背带裤,穿滑板鞋,但是这只🐔的内核不会变。

Java中的动态代理是一种技术,允许在运行时创建一个对象,该对象可以实现指定接口的方法。这通常被用来在不修改原始类的情况下,为原始类的方法提供额外的功能。例如,可以使用动态代理为方法添加日志记录功能或者实现访问控制。

要创建动态代理,需要两个主要部分:

1.一个接口,该接口定义了要代理的方法。 2.一个实现了 java.lang.reflect.InvocationHandler 接口的处理器对象,该对象将在调用代理方法时被调用。

Java 语言提供了一种特殊的接口,称为 InvocationHandler,可以用来定义动态代理类的行为。通常,当创建一个动态代理时,你需要提供一个实现了 InvocationHandler 接口的对象,该对象会定义代理类的行为。

同样的代理类也作为一个对象,需要被实例化,: 在 Java 中,动态代理类是通过 java.lang.reflect.Proxy 类来实现的。该类提供了一个静态方法 newProxyInstance,可以用来创建动态代理类的实例。

该方法接受三个参数:

1.一个 ClassLoader 对象,表示用于加载代理类的类加载器。 2.一个 Class 数组,表示要实现的接口。 3.一个 InvocationHandler 对象,表示要使用的处理器对象。

   MyInterface.class.getClassLoader(),  // 用于加载代理类的类加载器
    new Class[] { MyInterface.class },   // 要实现的接口
    new MyInvocationHandler(target));   // 要使用的处理器对象

在调用 newProxyInstance 方法时,该方法会根据传入的参数动态地创建一个代理类,并返回该代理类的实例。此后,你就可以通过该实例来调用代理类中定义的方法。

// 定义一个接口
public interface Hello {
  void sayHello();
}

// 定义实现该接口的类
public class HelloImpl implements Hello {
  @Override
  public void sayHello() {
    System.out.println("Hello, world!");
  }
}

// 定义一个实现了 InvocationHandler 接口的类
public class HelloInvocationHandler implements InvocationHandler {
  private Object target;

  public HelloInvocationHandler(Object target) {
    this.target = target;
  }

// 在代理方法被调用时,该方法会被调用
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 在调用原始方法之前,可以执行额外的操作
    System.out.println("Before calling method: " + method.getName());
      // 调用原始方法
    Object result = method.invoke(target, args);
    //同理
    System.out.println("After calling method: " + method.getName());
    return result;
  }
}

// 创建代理类的实例
Hello hello = (Hello) Proxy.newProxyInstance(
  Hello.class.getClassLoader(),
  new Class[] {Hello.class},
  new HelloInvocationHandler(new HelloImpl())
);

// 通过代理类的实例调用 sayHello 方法
hello.sayHello();

// 输出:
// Before calling method: sayHello
// Hello, world!
// After calling method: sayHello

在这个例子中,我们定义了一个接口 Hello 和一个实现该接口的类 HelloImpl。然后,我们定义了一个实现了 InvocationHandler 接口的类 HelloInvocationHandler,该类定义了代理类的行为。在这个例子中,我们定义了一个在调用代理类的方法前后都会打印消息的通知。

最后,我们使用 java.lang.reflect.Proxy 类的 newProxyInstance 方法来创建一个代理类的实例

实际应用(提升部分,没学过spring的不用看)

下面是一些动态代理的具体应用场景:

1.AOP(面向切面编程):通过动态代理可以在运行时动态地将切面的逻辑切入到目标方法中,实现对业务逻辑的横切。 2.远程方法调用:通过动态代理可以在运行时创建一个实现了某个远程接口的代理类,并通过该代理类来调用远程服务。 3.事务管理可能会与动态代理配合使用,以实现一些特定的目的。例如,在使用动态代理创建数据访问对象时,可以在代理类中添加事务管理逻辑,以保证数据库操作的正确性。 4对象的虚拟代理,例如用来延迟初始化或者限制对象的访问 5.对象的缓存代理,例如用来缓存网络资源或者数据库查询结果

AOP

那么在 AOP 中,动态代理的工作流程如下: 1。容器在启动时,会扫描应用程序中的切面,并根据切面定义的切点和通知,自动生成一个代理类。 2.当应用程序试图通过代理类的实例调用被拦截的方法时,代理类会拦截这些调用。 3.在拦截方法调用时,代理类会检查该方法是否匹配切面定义的切点,如果匹配,则执行切面中定义的通知。 4.在执行完通知后,代理类会将方法调用委托给原始的目标对象,并返回方法调用

下面是一个简单的例子,展示了如何使用动态代理来实现 AOP:

// 定义一个接口
public interface Hello {
  void sayHello();
}

// 定义实现该接口的类
public class HelloImpl implements Hello {
  @Override
  public void sayHello() {
    System.out.println("Hello, world!");
  }
}

// 定义切面
@Aspect
public class LoggingAspect {
  @Before("execution(void sayHello())")
  public void beforeSayHello() {
    System.out.println("Before calling sayHello() method");
  }

  @After("execution(void sayHello())")
  public void afterSayHello() {
    System.out.println("After calling sayHello() method");
  }
}

// 创建代理类的实例
Hello hello = (Hello) Proxy.newProxyInstance(
  Hello.class.getClassLoader(),
  new Class[] {Hello.class},
  new LoggingAspect()
);

// 通过代理类的实例调用 sayHello 方法
hello.sayHello();

// 输出:
// Before calling sayHello() method
// Hello, world!
// After calling sayHello() method

在这个例子中,我们定义了一个接口 Hello 和一个实现该接口的类 HelloImpl。

事务管理

下面是一个使用动态代理和事务管理的例子。假设我们有一个数据访问对象,用来从数据库中读取和写入数据。为了保证数据库操作的正确性,我们需要在代理类中添加事务管理逻辑。 首先,我们定义一个接口,表示数据访问对象:

public interface DataAccessObject {
    void insert();
    void update();
    void delete();
}

然后,我们定义一个实现类,用来实现接口中定义的方法:

public class DataAccessObjectImpl implements DataAccessObject {
    @Override
    public void insert() {
        // 插入数据
    }

    @Override
    public void update() {
        // 更新数据
    }

    @Override
    public void delete() {
        // 删除数据
    }
}

接下来,我们使用 JDK 中的 Proxy 类来创建动态代理:

DataAccessObject dao = new DataAccessObjectImpl();

DataAccessObject proxy = (DataAccessObject) Proxy.newProxyInstance(
    dao.getClass().getClassLoader(),
    dao.getClass().getInterfaces(),
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 在这里添加事务管理逻辑
            return method.invoke(dao, args);
        }
    }
);

在上面的代码中,我们通过 Proxy.newProxyInstance() 方法创建了一个动态代理类。这个代理类会实现 DataAccessObject 接口,并在每个方法调用前后添加事务管理逻辑。 我们就可以使用这个动态代理类来执行数据库操作:

// 插入数据
proxy.insert();

// 更新数据
proxy.update();

// 删除数据
proxy.delete();

在执行上面的代码时,动态代理类会拦截每个方法的调用,并在调用前后添加事务管理逻辑。这样,我们就可以保证数据库操作的正确性和完整性。

当然,这只是一个简单的例子,实际应用中会更复杂一些。不过,总体思路是一样的:通过动态代理类来拦截方法调用,并在调用前后添加事务管理逻辑。

这里歪一下,简单说说事务管理:

那么接下来我就来为你说明一下如何添加事务管理逻辑。

首先,我们需要确定事务的隔离级别和传播行为。隔离级别用来解决脏读、幻读和不可重复读等问题,常见的隔离级别有:读未提交、读已提交、可重复读和串行化。传播行为则用来控制事务的范围,常见的传播行为有:REQUIRED、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED 等。

具体的代码实现方式可能会因为使用的框架不同而有所差异。下面是一个简单的例子,使用 Spring 的声明式事务管理来实现事务管理逻辑:

事务管理逻辑

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 在调用前打开事务
    transactionManager.begin();

    try {
        // 执行原来的方法
        Object result = method.invoke(dao, args);

        // 在调用后提交事务
        transactionManager.commit();

        return result;
    } catch (Exception e) {
        // 在出现异常时回滚事务
        transactionManager.rollback();
        throw e;
    }
}