代理设计模式(Proxy Design Pattern)是一种结构型设计模式,它为其他对象提供一个代理,以控制对这个对象的访问。代理模式可以用于实现懒加载、安全访问控制、日志记录等功能。
在设计模式中,代理模式可以分为静态代理和动态代理。静态代理是指代理类在编译时就已经确定,而动态代理是指代理类在运行时动态生成。
在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
1.缓存代理
缓存代理通常会在内部维护一个缓存数据结构,如 HashMap 或者 LinkedHashMap,用来存储已经处理过的请求及其结果。
假设有一个数据查询接口,它从数据库或其他数据源中检索数据。在没有缓存代理的情况下,每次查询都需要访问数据库,这可能会导致较高的资源消耗和延迟。通过引入缓存代理,我们可以将查询结果存储在内存中,从而避免重复查询数据库。
public interface DataQuery {
String query(String queryKey);
}
public class DatabaseDataQuery implements DataQuery {
@Override
public String query(String queryKey) {
// 使用数据源从数据库查询数据很慢
return "result";
}
}
创建一个缓存代理类,它同样实现了 DataQuery 接口,并在内部使用HashMap 作为缓存:
public class DatabaseDataQueryProxy implements DataQuery {
// 实现缓存,需要数据结构
private Map<String, String> cache = new HashMap<>(256);
// 你代理谁,就要持有谁
private DatabaseDataQuery dataQuery;
public DatabaseDataQueryProxy() {
// 1.屏蔽被代理对象
this.dataQuery = new DatabaseDataQuery();
}
@Override
public String query(String queryKey) {
// 2.对被代理对象的方法做增强
// 2.1.查询缓存,命中则返回
String result = cache.get(queryKey);
if (result != null) {
System.out.println("命中缓存,走缓存");
return result;
}
// 2.2.未命中,则查询数据库
result = dataQuery.query(queryKey);
// 2.2.1.如果有结果,需要将结果保存到缓存中,再返回
if (result != null) {
cache.put(queryKey, result);
}
System.out.println("未命中,走持久层");
return result;
}
}
// 测试代码
@Test
void test() {
DataQuery dataQuery = new DatabaseDataQueryProxy();
String value = dataQuery.query("key1");
System.out.println(value);
value = dataQuery.query("key1");
System.out.println(value);
value = dataQuery.query("key2");
System.out.println(value);
}
2.安全代理
用于控制对真实主题对象的访问。通过安全代理,可以实现访问控制、权限验证等安全相关功能。
假设我们有一个敏感数据查询接口,只有具有特定权限的用户才能访问:
3.虚拟代理
在需要时延迟创建耗时或资源密集型对象。虚拟代理在初始访问时才创建实际对象,之后将直接使用该对象。这可以避免在实际对象尚未使用的情况下就创建它,从而节省资源。
以下是一个虚拟代理的应用示例:
假设我们有一个大型图片类,它从网络加载图像。由于图像可能非常大,我们希望在需要显示时才加载它。为了实现这一点,我们可以创建一个虚拟代理来代表大型图片类。
4.远程代理
用于访问位于不同地址空间的对象。远程代理可以为本地对象提供与远程对象相同的接口,使得客户端可以透明地访问远程对象。通常,远程代理需要处理网络通信、序列化和反序列化等细节。
通过前四个案例,我们也大致了解了静态代理的使用方式,其大致流程如下:
这样,被代理类的方法就会被代理类所覆盖,实现了对被代理类的增强或修改。
静态代理需要手动编写代理类,代理类与被代理类实现相同的接口或继承相同的父类,对被代理对象进行包装。在程序运行前,代理类的代码就已经生成,并在程序运行时调用。静态代理的优点是简单易懂,缺点是需要手动编写代理类,代码复杂度较高,且不易扩展。
动态代理是在程序运行时动态生成代理类,无需手动编写代理类,大大降低了代码的复杂度。动态代理一般使用 Java 提供的反射机制实现,可以对任意实现了接口的类进行代理。动态代理的优点是灵活性高,可以根据需要动态生成代理类,缺点是性能相对较低,由于使用反射机制,在运行时会产生额外的开销。
使用缓存代理的例子
public interface DataQuery {
String query(String queryKey);
}
public class DatabaseDataQuery implements DataQuery {
@Override
public String query(String queryKey) {
// 使用数据源从数据库查询数据很慢
return "result";
}
}
创建一个代理类,实现 InvocationHandler
接口,实现invoke()
方法,并在其中定义一个被代理类的对象作为属性。
public class CacheInvocationHandler implements InvocationHandler {
private Map<String, String> cache = new HashMap<>(256);
private DatabaseDataQuery databaseDataQuery;
public CacheInvocationHandler() {
this.databaseDataQuery = new DatabaseDataQuery();
}
public CacheInvocationHandler(DatabaseDataQuery databaseDataQuery) {
this.databaseDataQuery = databaseDataQuery;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1.判断是哪一个方法 (只对query方法做缓存)
String result;
if ("query".equals(method.getName())) {
// 2.查缓存
// 2.1.命中直接返回
result = cache.get(args[0].toString());
if (result != null) {
System.out.println("从缓存拿数据");
return result;
}
// 2.2.未命中,查询数据库 (需要代理实例)
result = (String) method.invoke(databaseDataQuery, args);
// 3.查询到了,进行缓存
cache.put(args[0].toString(), result);
return result;
}
// 当其他的方法被调用,不希望被干预,直接调用原生的方法
return method.invoke(databaseDataQuery, args);
}
}
主要业务逻辑 (测试代码)
@Test
void testJdkDynamicProxy() {
// jdk提供的代理实现,主要是使用Proxy类来实现
// 参数1 classLoader:被代理类的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 参数2 代理类需要实现的接口数组
Class[] interfaces = new Class[]{DataQuery.class};
// 参数3 InvocationHandler
InvocationHandler invocationHandler = new CacheInvocationHandler();
DataQuery dataQuery = (DataQuery) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
// 调用query方法时,实际上是调用了invoke()方法
String result = dataQuery.query("key1");
System.out.println(result);
result = dataQuery.query("key1");
System.out.println(result);
result = dataQuery.query("key2");
System.out.println(result);
System.out.println("-----------------");
result = dataQuery.queryAll();
System.out.println(result);
}
基于 CGLIB 的动态代理需要使用 net.sf.cglib.proxy.Enhancer
类和 net.sf.cglib.proxy.MethodInterceptor
接口。
1.创建一个被代理类,定义需要被代理的方法 (以DatabaseDataQuery
为例)
public class DatabaseDataQuery {
public String query(String queryKey) {
// 使用数据源从数据库查询数据很慢
System.out.println("正在从数据库中查询数据");
return "result";
}
public String queryAll() {
System.out.println("正在从数据库中查询数据");
return "query All result";
}
}
2.创建一个方法拦截器类,实现 MethodInterceptor
接口,并在其中定义一个被代理类的对象作为属性。
intercept
方法中,我们可以对被代理对象的方法进行增强public class CacheMethodInterceptor implements MethodInterceptor {
private Map<String, String> cache = new HashMap<>(256);
private DatabaseDataQuery databaseDataQuery;
public CacheMethodInterceptor() {
this.databaseDataQuery = new DatabaseDataQuery();
}
public CacheMethodInterceptor(DatabaseDataQuery databaseDataQuery) {
this.databaseDataQuery = databaseDataQuery;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 1.判断是哪一个方法
String result;
if ("query".equals(method.getName())) {
// 2.查询缓存,命中则直接返回
result = cache.get(args[0].toString());
if (result != null) {
System.out.println("从缓存中提取数据");
return result;
}
// 3.未命中,查询数据库
result = (String) method.invoke(databaseDataQuery, args);
// 4.缓存到缓存中
cache.put(args[0].toString(), result);
return result;
}
return method.invoke(databaseDataQuery, args);
}
}
3.在使用代理类时,创建被代理类的对象和代理类的对象,并使用 Enhancer.create
方法生成代理对象。
@Test
void testCgkibDynamicProxy() {
// cglib通过enhancer
Enhancer enhancer = new Enhancer();
// 1.设置父类
enhancer.setSuperclass(DatabaseDataQuery.class);
// 2.设置一个方法拦截器,用来拦截方法
enhancer.setCallback(new CacheMethodInterceptor());
// 3.创建代理类
DatabaseDataQuery databaseDataQuery = (DatabaseDataQuery) enhancer.create();
String value = databaseDataQuery.query("key1");
System.out.println(value);
value = databaseDataQuery.query("key1");
System.out.println(value);
value = databaseDataQuery.query("key2");
System.out.println(value);
}
在 Spring 中,AOP(面向切面编程)提供了一种有效的方式来对程序中的多个模块进行横切关注点的处理,例如日志、事务、缓存、安全等。使用 Spring AOP,可以在程序运行时动态地将代码织入到目标对象中,从而实现对目标对象的增强。
Spring AOP 的使用步骤如下:
1.引入 AOP 相关依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
<version>2.3.9.RELEASEversion>
dependency>
2.在Main
中开启自动代理@EnableAspectJAutoProxy
@SpringBootApplication
@EnableAspectJAutoProxy
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
3.定义接口和实现类,并将具体实现注入容器 (以DatabaseDataQuery
为例)
// 接口
public interface DataQuery {
String query(String queryKey);
}
// 实现类
@Component
public class DatabaseDataQuery implements DataQuery {
@Override
public String query(String queryKey) {
// 使用数据源从数据库查询数据很慢
System.out.println("正在从数据库中查询数据");
return "result";
}
}
4.定义切面类,对方法做增强
..
代表任意方法@Component
@Aspect
public class CacheAspectj {
private static Map<String,String> cache = new ConcurrentHashMap<>(256);
@Pointcut("execution(* com.dcy.structural.proxy.dynamicProxy.aop.impl.DatabaseDataQuery.query(..))")
public void pointcut() {}
@Around("pointcut()")
public String around(ProceedingJoinPoint joinPoint) {
// 1.查询缓存
Object[] args = joinPoint.getArgs();
String key = args[0].toString();
// 1.1.命中则返回
String result = cache.get(key);
if (result != null) {
System.out.println("数据从缓存中提取");
return result;
}
// 2.未命中,查询数据库,实际上是调用被代理bean的方法
try {
result = joinPoint.proceed().toString();
// 如果查询有结果,进行缓存
cache.put(key, result);
} catch (Throwable e) {
throw new RuntimeException(e);
}
return result;
}
}
5.测试用例
@SpringBootTest
public class AopTest {
@Resource
private DataQuery dataQuery;
@Test
void testSpringAop() {
String result = dataQuery.query("key1");
System.out.println(result);
result = dataQuery.query("key1");
System.out.println(result);
result = dataQuery.query("key2");
System.out.println(result);
}
}