动态代理模式

本文章已经生成可运行项目,

一、代理模式

       代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。

代理模式角色分为 3 种:
● Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,通常被设计成接口;
● RealSubject(真实主题角色):真正实现业务逻辑的类;
● Proxy(代理主题角色):用来代理和封装真实主题;

       代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真是对象和代理对象,在代理模式中引入了抽象层。

如果根据字节码的创建时间,可以分为静态代理和动态代理:
● 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
● 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件

二、静态代理

       假设有UserService接口及其实现类UserServiceImpl,我们需要在不改变实现类代码的基础上,增加日志记录的功能。

public interface UserService {
    public void select();   
    public void update();
}
public class UserServiceImpl implements UserService {  
    public void select() {  
        System.out.println("查询 selectById");
    }
    public void update() {
        System.out.println("更新 update");
    }
}

       我们通过静态代理对UserServiceImpl进行功能增强,在调用select和update之前记录一些日志。

       静态代理通过UserServiceProxy实现,代理类同时也需要实现UserService接口。

public class UserServiceProxy implements UserService {
    private UserService target; // 被代理的对象

    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    public void select() {
        before();
        target.select();    // 这里才实际调用真实主题角色的方法
        after();
    }
    public void update() {
        before();
        target.update();    // 这里才实际调用真实主题角色的方法
        after();
    }

    private void before() {     // 在执行方法之前执行
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {      // 在执行方法之后执行
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}
public class Test {
    public static void main(String[] args) {
        UserService userServiceImpl = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userServiceImpl);

        proxy.select();
        proxy.update();
    }
}

       通过静态代理,我们达到了功能增强的目的,而且没有入侵原代码,这是静态代理的一个优点。

       虽然静态代理实现简单,且不侵入原代码,但是,场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
● 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
● 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

三、动态代理

       Java中两种常见的动态代理方式:JDK原生动态代理和CGLIB动态代理。

3.1 JDK动态代理

       JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。我们通过编写一个调用逻辑处理器 LogHandler 类案例来提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke()方法中编写方法调用的逻辑处理。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class LogHandler implements InvocationHandler {
    Object target;  // 被代理的对象,实际的方法执行者

    public LogHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);  // 调用 target 的 method 方法
        after();
        return result;  // 返回方法的执行结果
    }
    // 调用invoke方法之前执行
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    // 调用invoke方法之后执行
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}
import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        
        // 1. 创建被代理的对象,UserService接口的实现类
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        
        // 2. 获取对应的 ClassLoader
        ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
        
        // 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,
        Class[] interfaces = userServiceImpl.getClass().getInterfaces();
        
        // 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
        //     这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
        InvocationHandler logHandler = new LogHandler(userServiceImpl);
/*
		   5.根据上面提供的信息,创建代理对象 在这个过程中,
               a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
               b.然后根据相应的字节码转换成对应的class,
               c.然后调用newInstance()创建代理实例
		 */
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
        
        // 调用代理的方法
        proxy.select();
        proxy.update();
    }
}

3.2 CGLIB动态代理

       CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它允许在运行时扩展或修改类和接口。CGLIB 是一个开源项目,广泛应用于 AOP(面向切面编程)框架( Spring AOP)、ORM 框架(HibernateMyBatis)等需要在运行时生成代理类的场景。
1. 动态代理:CGLIB 的核心功能是通过继承目标类来创建动态代理,弥补了 Java 反射机制只能代理接口的不足。
2. 高性能:CGLIB 使用字节码生成技术(基于 ASM 库),生成的代理类执行效率高于 Java 反射。
3. 方法拦截:通过实现MethodInterceptor接口,可以拦截并增强目标类的方法。
4. 无需接口:可以代理没有实现接口的类,只需目标类不是final类型。

CGLIB依赖:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
// 目标类(没有实现任何接口)
class Target {
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 方法拦截器实现
public class MyInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 前置增强
        System.out.println("Before method invocation");
        
        // 调用原始方法
        Object result = proxy.invokeSuper(obj, args);
        
        // 后置增强
        System.out.println("After method invocation");
        
        return result;
    }
}

1. MethodInterceptor:回调接口,当代理对象的方法被调用时,会触发intercept方法。
2. MethodProxy:用于调用原始方法(invokeSuper),比反射调用更高效。

import net.sf.cglib.proxy.Enhancer;

// 主类
public class Main {
    public static void main(String[] args) {
        // 创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        
        // 设置父类(目标类)
        enhancer.setSuperclass(Target.class);
        
        // 设置回调(拦截器)
        enhancer.setCallback(new MyInterceptor());
        
        // 创建代理对象
        Target proxy = (Target) enhancer.create();
        
        // 调用代理方法
        String result = proxy.sayHello("World");
        System.out.println("Result: " + result);
    }
}

       Enhancer:CGLIB 的核心类,用于创建代理对象。通过设置父类和回调函数,可以生成目标类的子类代理。

四、动态代理的用途

       动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。

五、小结

● Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;
● 动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。

本文已生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值