动态代理模式介绍
动态代理模式是一种在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象的设计模式。在动态代理中,代理类和委托类的关系是在运行时确定的。这种模式通常用于提高系统的灵活性,例如在远程调用、事务处理或缓存等方面。
动态代理模式的主要优点是可以在运行时动态地改变代理对象,从而在不修改原有代码的情况下实现对目标对象的访问和控制。此外,动态代理还可以提高系统的性能,因为它可以避免在运行时进行大量的反射操作。
动态代理模式的实现通常需要用到反射机制,以便在运行时动态地创建代理对象并调用其方法。在 Java 编程语言中,动态代理可以通过 Java 代理类来实现。代理类是一个实现了特定接口的类,它可以在运行时动态地生成并返回一个实现了该接口的代理对象。通过使用代理类,可以在不修改原有代码的情况下实现对目标对象的访问和控制。
动态代理实现 - jdk 实现方式
使用 jdk 的动态代理的要求是目标类一定是有接口的,不然会报错
JDK 动态代理需要接口的原因是因为 Java 的动态代理机制是基于 Java 反射机制实现的,而 Java 反射机制只能处理接口类型的类。
在 Java 中,接口是一种定义了方法签名的抽象类型,它不包含任何实现。Java 反射机制允许在运行时动态地访问和修改类的成员,包括方法、属性、构造函数等。因此,Java 反射机制只能处理接口类型的类,因为接口只包含方法签名,不包含实现。
在 JDK 动态代理中,代理类是在运行时动态生成的,它实现了目标类所实现的接口,并且在代理类中重写了目标类的方法。这样,在运行时,代理类就可以通过反射机制动态地访问和修改目标类的方法。如果目标类没有实现接口,那么代理类就无法通过反射机制访问和修改目标类的方法,因此 JDK 动态代理需要目标类实现接口。
需要注意的是,Java 的动态代理机制只能代理接口类型的类,如果目标类没有实现接口,那么就无法使用 JDK 动态代理。但是,可以使用第三方库,如 CGLIB 等,来实现对目标类的代理。
- 定义一个具体的抽象接口。
package xyz.xiaolinz.demo.proxy.dynamicproxy.jdk;
/**
* @author huangmuhong
* @version 1.0.0
* @date 2023/8/4
*/
public interface DataQuery {
String query(String queryKey);
String queryAll();
}
- 定义接口的具体实现类,也就是被代理类
package xyz.xiaolinz.demo.proxy.dynamicproxy.jdk;
/**
* @author huangmuhong
* @version 1.0.0
* @date 2023/8/4
*/
public class DataBaseDataQuery implements DataQuery {
@Override
public String query(String queryKey) {
// 使用数据源查询数据
System.out.println("从数据库查询数据");
return "query";
}
@Override
public String queryAll() {
return "queryAll";
}
}
- 编写具体的动态代理类,通过实现
InvocationHandler
接口来获取动态代理的相关能力
package xyz.xiaolinz.demo.proxy.dynamicproxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author huangmuhong
* @version 1.0.0
* @date 2023/8/7
*/
public class CacheInvocationHandler implements InvocationHandler {
private final Map<String, String> cache = new HashMap<>(10);
private final DataQuery bean;
public CacheInvocationHandler(DataQuery bean) {
this.bean = bean;
}
/**
* @param proxy 代理对象
* @param method 方法
* @param args 参数
* @return {@link Object}
* @throws Throwable 可投掷
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过代理对象调用方法时,会调用这个方法
// 判断是否是 query 方法
if ("query".equals(method.getName())) {
// 如果是 query 方法,就使用缓存
if (cache.containsKey(args[0])) {
System.out.println("从缓存中查询数据");
return cache.get(args[0]);
} else {
// 如果没有缓存,就调用被代理对象的方法
final var result = method.invoke(bean, args);
// 将结果缓存
cache.put((String) args[0], (String) result);
return result;
}
}
// 返回被代理对象的方法
return method.invoke(bean, args);
}
}
- 通过
Proxy
工具类的相关方法来创建动态代理对象并执行相关代理方法
package xyz.xiaolinz.demo.proxy.dynamicproxy.jdk;
import java.lang.reflect.Proxy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* @author huangmuhong
* @version 1.0.0
* @date 2023/8/7
*/
@Component
@Slf4j
public class JdkProxyRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// jdk动态代理主要通过 Proxy 类来完成
/**
* 1. classLoader: 类加载器,用于加载代理类,可以通过被代理类的class.getClassLoader()获取 2. interfaces:
* 被代理类的接口,可以通过被代理类的class.getInterfaces()获取 3. invocationHandler: 代理类的调用处理器,需要实现
* InvocationHandler 接口
*/
final var classLoader = Thread.currentThread().getContextClassLoader();
Class[] interfaces = new Class[] {DataQuery.class};
final var cacheInvocationHandler = new CacheInvocationHandler(new DataBaseDataQuery());
final var proxy =
((DataQuery) Proxy.newProxyInstance(classLoader, interfaces, cacheInvocationHandler));
log.info(proxy.query("test"));
log.info(proxy.query("test"));
}
}
动态代理实现 - CGLIB 实现方式
基于 CGLIB 的动态代理需要使用 Enhancer
类和 MethodInterceptor
接口。
使用 CGLIB 的方式被代理类实现接口不是必须的,即与 JDK 的动态代理最大的不同点即是 jdk 强制需要接口而他不需要
- 定义被代理类,不需要实现接口
package xyz.xiaolinz.demo.proxy.dynamicproxy.cglib;
import lombok.extern.slf4j.Slf4j;
/**
* @author huangmuhong
* @version 1.0.0
* @date 2023/8/4
*/
@Slf4j
public class DataBaseDataQuery {
public String query(String queryKey) {
// 使用数据源查询数据
log.info("从数据库查询数据");
return "query";
}
public String queryAll() {
log.info("从数据库查询所有数据");
return "queryAll";
}
}
- 定义动态代理类,实现
MethodInterceptor
接口
package xyz.xiaolinz.demo.proxy.dynamicproxy.cglib;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
* 缓存方法拦截器
*
* <p>实现了 Spring 的 MethodInterceptor 接口,这个接口是 CGLIB 的方法拦截器,用于拦截被代理对象的方法。 该接口只需要实现 intercept
* 方法,该方法会在被代理对象的方法被调用时调用。 intercept 方法的参数说明:
*
* <ul>
* <li>obj:被代理对象
* <li>method:被代理对象的方法
* <li>args:被代理对象的方法的参数
* <li>proxy:代理人
* <li>返回值:被代理对象的方法的返回值
* <li>throws Throwable:可投掷
* </ul>
*
* @author huangmuhong
* @date 2023/08/08
* @see MethodInterceptor
*/
@Slf4j
public class CacheMethodInterceptor implements MethodInterceptor {
private final Map<String, String> cache = new HashMap<>(10);
/**
* 截距
*
* @param obj 对象
* @param method 方法
* @param args 参数
* @param proxy 代理人
* @return {@link Object }
* @author huangmuhong
* @date 2023/08/08
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
// 我们只对 query 方法进行缓存
if (method.getName().equals("query")) {
if (cache.containsKey(args[0])) {
log.info("从缓存中查询数据");
return cache.get(args[0]);
}
// 从数据库查询数据
final var result = proxy.invokeSuper(obj, args);
// 将查询结果缓存
cache.put((String) args[0], (String) result);
return result;
}
// 调用默认的方法
return proxy.invokeSuper(obj, args);
}
}
- 通过
Enhancer
创建动态代理对象并调用
package xyz.xiaolinz.demo.proxy.dynamicproxy.cglib;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.stereotype.Component;
/**
* @author huangmuhong
* @date 2023/8/8
*/
@Component
public class CglibRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// 创建被代理对象
final var enhancer = new Enhancer();
// cglib 跟 JDK 动态代理的不同之处在于,它是针对类实现代理的,原理是对指定的目标类生成一个子类,可以没有接口。
// 我么这里的目标类是 DataBaseDataQuery,所以需要设置父类,且没有接口 CGLIB 也可以为我们创建动态代理的对象
enhancer.setSuperclass(DataBaseDataQuery.class);
// 设置方法拦截器
enhancer.setCallback(new CacheMethodInterceptor());
// 创建代理对象
final var proxy = (DataBaseDataQuery) enhancer.create();
// 调用代理对象的方法
proxy.query("queryKey");
proxy.query("queryKey");
proxy.queryAll();
}
}
评论区