代理模式
● 为对象提供一个代理类,增强该对象的方法,控制对这个对象的访问
● 静态代理和动态代理:静态代理就是编译的时候就已经确定,而动态代理就是运行时才会生成
静态代理的使用场景
缓存代理
● 提供数据的缓存功能,避免数据库重复查询
实践
- 定义数据查询的接口
public interface DataQuery {String query(String key);
}
- 接口实现类实现接口
package com.hillky.desgin_learn.Proxy;public class DataBaseQuery implements DataQuery{@Overridepublic String query(String key) {return "DataBaseQuery"+key;}
}
- 创建缓存代理类,实现接口,内部使用缓存Map,成员是被代理类
package com.hillky.desgin_learn.Proxy.cacheProxy;import lombok.NoArgsConstructor;import java.util.HashMap;public class CachingDataQueryProxy implements DataQuery{private DataQuery dataQuery;private static HashMap<String,String> cache=new HashMap<>();public CachingDataQueryProxy(DataBaseQuery dataBaseQuery) {this.dataQuery = dataBaseQuery;}public CachingDataQueryProxy() {this.dataQuery=new DataBaseQuery();}@Overridepublic String query(String key) {String result = cache.get(key);if(result != null){System.out.println("从缓存中取数据");return result;}result = dataQuery.query(key);System.out.println("从数据库取数据");cache.put(key,result);return result;}
}
- 建立测试类测试效果
安全代理
● 安全代理,可以实现访问控制、权限验证等安全相关功能。
实践
- 定义一个数据查询接口
package com.hillky.desgin_learn.Proxy.safeProxy;public interface SensitiveDataQuery {String queryData(String userId);
}
- 实现真实敏感数据查询实现
package com.hillky.desgin_learn.Proxy.safeProxy;public class SensitiveDataQueryImpl implements SensitiveDataQuery{@Overridepublic String queryData(String userId) {return "SensitiveDataQuery form user :"+userId;}
}
- 安全代理类,内部进行验证
package com.hillky.desgin_learn.Proxy.safeProxy;import lombok.Data;@Data
public class SecurityProxy implements SensitiveDataQuery{private SensitiveDataQuery sensitiveDataQuery;private UserAuthenticator userAuthenticator;public SecurityProxy() {this.sensitiveDataQuery=new SensitiveDataQueryImpl();}@Overridepublic String queryData(String userId) {if (userAuthenticator.hasPermission(userId)) {return sensitiveDataQuery.queryData(userId);} else {return "Access Denied: Insufficient permission for user" + userId;}}
}
- UserAuthenticator 类来模拟用户权限验证
package com.hillky.desgin_learn.Proxy.safeProxy;import java.util.Arrays;
import java.util.List;public class UserAuthenticator {private final List<String> authorizedUserIds;public UserAuthenticator() {// 模拟从数据库或配置文件中获取已授权的用户列表authorizedUserIds = Arrays.asList("user1", "user2", "user3");}public boolean hasPermission(String userId) {return authorizedUserIds.contains(userId);}
}
- 测试代码编写
虚拟代理
● 用于需要延迟创建耗时或资源密集型对象
● 虚拟代理在初始时访问才创建实际对象,之后将直接使用该对象
实践
- 定义一个图片接口
package com.hillky.desgin_learn.Proxy.vitruralProxy;public interface Image {void display();
}
- 实现一个大型图片类
package com.hillky.desgin_learn.Proxy.vitruralProxy;public class LargeImage implements Image{private String imageUrl;public LargeImage(String imageUrl) {this.imageUrl = imageUrl;}@Overridepublic void display() {System.out.println("Displaying image: " + imageUrl);}
}
- 虚拟代理类
package com.hillky.desgin_learn.Proxy.vitruralProxy;public class VirtualImageProxy implements Image{private String imageUrl;private LargeImage largeImage;public VirtualImageProxy(String imageUrl) {this.imageUrl = imageUrl;}@Overridepublic void display() {if(largeImage==null){largeImage=new LargeImage(imageUrl);}largeImage.display();}
}
- 测试
可以看到虚拟代理如何实现懒加载,以减少资源消耗和提高程序性能。
远程代理
● 为本地对象提供与远程对象相同的接口,使得客户端可以透明地访问远程对象。需要做序列化反序列化,网络通信(不同语言的调用)
实践
- 定义一个服务接口
package com.hillky.desgin_learn.Proxy.remoteProxy;public interface RemoteService {String fetchData(String dataId);
}
- ,实现一个远程服务类
package com.hillky.desgin_learn.Proxy.remoteProxy;public class RemoteServiceImpl implements RemoteService{@Overridepublic String fetchData(String dataId) {return "Data from remote service: " + dataId;}
}
- 创建一个远程代理类,它实现了 RemoteService 接口,并在内部处理网络通信等细节:
package com.hillky.desgin_learn.Proxy.remoteProxy;public class RemoteServiceProxy implements RemoteService{private String remoteServiceUrl;private RemoteService remoteService;public RemoteServiceProxy(String remoteServiceUrl) {this.remoteServiceUrl = remoteServiceUrl;this.remoteService=new RemoteServiceImpl();}@Overridepublic String fetchData(String dataId) {// 网络通信、序列化和反序列化等逻辑System.out.println("Connecting to remote service at: " + remoteServiceUrl);// 假设我们已经获取到远程服务的数据String result = remoteService.fetchData(dataId);System.out.println("Received data from remote service.");return result;}
}
- 测试
请注意,为了简化代码,这里省略了实际的网 络通信、序列化和反序列化逻辑。
动态代理
● 静态代理需要手动编写代理类,代理类与被代理类实现相同的接口,对被代理对象进行包装。在程序运行前,代理类的代码就已经生成,并在程序运 行时调用。静态代理的优点是简单易懂,缺点是需要手动编写代理类,代码复杂度较 高,且不易扩展。
● 动态代理是在程序运行时动态生成代理类,无需手动编写代理类,大大降低了代码的 复杂度。通过反射实现
基于 JDK 的动态代理实现步骤
- 定义一个接口,声明需要代理的方法:
package com.hillky.desgin_learn.Proxy.remoteProxy;public interface RemoteService {String fetchData(String dataId);
}
- 创建一个被代理类,实现这个接口,并在其中定义实现方法:
package com.hillky.desgin_learn.Proxy.remoteProxy;public class RemoteServiceImpl implements RemoteService{@Overridepublic String fetchData(String dataId) {return "Data from remote service: " + dataId;}
}
- 创建一个代理类,实现 InvocationHandler 接口,并在其中定义一个被代理类的对象作为属性。
package com.hillky.desgin_learn.Proxy.jdkProxy;import javafx.beans.InvalidationListener;
import javafx.beans.Observable;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;public class CacheInvocationHandler implements InvocationHandler {private DataQuery dataQuery;private HashMap<String,String> cache = new LinkedHashMap<>(256);public CacheInvocationHandler(DataQuery databaseDataQuery) {this.dataQuery = databaseDataQuery;}public CacheInvocationHandler() {this.dataQuery=new DataBaseQuery();}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String result=null;if(method.getName().equals("query")){result = cache.get(args[0].toString());if(result!=null){System.out.println("数据从缓存重获取。");return result;}result = (String) method.invoke(dataQuery, args);cache.put(args[0].toString(),result);return result;}// 当其他的方法被调用,不希望被干预,直接调用原生的方法return method.invoke(dataQuery,args);}
}
- 测试代码,符合要求,只有query方法加了缓存
package com.hillky.desgin_learn.Proxy.jdkProxy;import org.junit.jupiter.api.Test;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;import static org.junit.jupiter.api.Assertions.*;class CacheInvocationHandlerTest {@Testvoid test() {// jdk提供的代理实现,主要是使用Proxy类来完成// 1、classLoader:被代理类的类加载器ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 2、代理类需要实现的接口数组Class[] interfaces = new Class[]{DataQuery.class};// 3、InvocationHandlerInvocationHandler invocationHandler = new CacheInvocationHandler();DataQuery dataQuery = (DataQuery) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);// 事实上调用query方法的使用,他是调用了invokeString result = dataQuery.query("key1");System.out.println(result);System.out.println("--------------------");result = dataQuery.query("key1");System.out.println(result);System.out.println("--------------------");result = dataQuery.query("key2");System.out.println(result);System.out.println("++++++++++++++++++++++++++++++++++++");// 事实上调用queryAll方法的使用,他是调用了invokeresult = dataQuery.queryAll("key1");System.out.println(result);System.out.println("--------------------");result = dataQuery.queryAll("key1");System.out.println(result);System.out.println("--------------------");result = dataQuery.queryAll("key2");System.out.println(result);System.out.println("--------------------");}
}
spring中aop的使用步骤
● 在 Spring 中,AOP(面向切面编程)提供了一种有效的方式来对程序中的多个模块进行横切关注点的处理,例如日志、事务、缓存、安全等。从而实现对目标对象的增强
- 引入 AOP 相关依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 开启自动代理
@EnableAspectJAutoProxy
@SpringBootApplication
@EnableAspectJAutoProxy
public class DesginLearnApplication {public static void main(String[] args) {SpringApplication.run(DesginLearnApplication.class, args);}}
- 定义接口和实现类,并将具体实现注入容器
package com.hillky.desgin_learn.Proxy.aop;public interface DataQuery {String query(String queryKey);
}
package com.hillky.desgin_learn.Proxy.aop;import org.springframework.stereotype.Component;@Component
public class DataBaseQuery implements DataQuery{@Overridepublic String query(String queryKey) {System.out.println("正在从数据库查询数据");return "result";}
}
- 定义切面类,对方法做增强
package com.hillky.desgin_learn.Proxy.aop;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Component
@Aspect
public class CacheAspectj {//定义切面@Pointcut("execution(* com.hillky.desgin_learn.Proxy.aop.DataQuery.query(..))")public void pointcut(){}@Around("pointcut()")public String around(ProceedingJoinPoint joinPoint){Object[] args = joinPoint.getArgs();String key = args[0].toString();// 1、查询缓存,命中则返回String result = Cache.get(key);if(result != null){System.out.println("数据从缓存中获取");return result;}// 未命中则去数据库查询,实际上是调用被代理bean的方法try {result = joinPoint.proceed().toString();// 如果查询有结果,进行缓存Cache.put(key,result);} catch (Throwable e) {throw new RuntimeException(e);}return result;}}
- 定义缓存
package com.hillky.desgin_learn.Proxy.aop;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class Cache {private static Map<String,String> map = new ConcurrentHashMap<>(256);public static String get(String key){return map.get(key);}public static void put(String key,String value){map.put(key, value);}
}
- 测试
package com.hillky.desgin_learn.Proxy.aop;import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class CacheAspectjTest {@Resourceprivate DataQuery dataQuery;@Testvoid test(){dataQuery.query("key1");dataQuery.query("key1");dataQuery.query("key2");}}
代理和 AOP 是两个相关的概念,代理是 AOP 的一种实现方式,它们都可以 用于在程序中实现对目标对象的增强。区别在于,代理主要是针对单个对象的方法调 用进行增强,而 AOP 则是针对程序中多个模块的横切关注点进行增强。
动态代理的应用场景
动态代理是一种代理模式,它在运行时动态生成代理对象,而无需提前创建具体的代理类。
使用场景:
● 日志记录
● 性能监控
● 事务管理
● 权限验证
● 缓存
● aop
● 速率限制
一般都可以用aop方便实现
日志记录
- 引入 AOP 相关依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 开启自动代理
@EnableAspectJAutoProxy
@SpringBootApplication
@EnableAspectJAutoProxy
public class DesginLearnApplication {public static void main(String[] args) {SpringApplication.run(DesginLearnApplication.class, args);}}
- 创建一个切面类(Aspect)来实现日志记录的逻辑
package com.hillky.desgin_learn.Proxy.aop;public interface DataQuery {String query(String queryKey);
}
- 定义切面类,对方法做增强
package com.hillky.desgin_learn.Proxy.log;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {private final Logger logger = LoggerFactory.getLogger(this.getClass());//定义切面@Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")public void restControllerMethods() {}@Before("restControllerMethods()")public void logMethodCall(JoinPoint joinPoint){String className = joinPoint.getSignature().getDeclaringTypeName();String methodName = joinPoint.getSignature().getName();logger.info("Entering method [{}.{}]", className, methodName);}@AfterReturning(pointcut = "restControllerMethods()", returning = "result")public void logMethodReturn(JoinPoint joinPoint, Object result) {String className = joinPoint.getSignature().getDeclaringTypeName();String methodName = joinPoint.getSignature().getName();logger.info("Exiting method [{}.{}], return value: {}", className,methodName, result);}
}
- 测试接口访问看控制台输出
package com.hillky.desgin_learn.Proxy.log;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class SampleController {@GetMapping("/hello")public String hello() {return "Hello, World!";}
}
- 测试
package com.hillky.desgin_learn.Proxy.aop;import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class CacheAspectjTest {@Resourceprivate DataQuery dataQuery;@Testvoid test(){dataQuery.query("key1");dataQuery.query("key1");dataQuery.query("key2");}}