设计模式-代理模式

代理模式

● 为对象提供一个代理类,增强该对象的方法,控制对这个对象的访问
● 静态代理和动态代理:静态代理就是编译的时候就已经确定,而动态代理就是运行时才会生成

静态代理的使用场景

缓存代理

● 提供数据的缓存功能,避免数据库重复查询

实践

  1. 定义数据查询的接口
public interface DataQuery {

    String query(String key);
}
  1. 接口实现类实现接口
package com.hillky.desgin_learn.Proxy;

public class DataBaseQuery implements DataQuery{
    @Override
    public String query(String key) {
        return "DataBaseQuery"+key;
    }
}
  1. 创建缓存代理类,实现接口,内部使用缓存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();
    }

    @Override
    public 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;
    }
}
  1. 建立测试类测试效果

安全代理

● 安全代理,可以实现访问控制、权限验证等安全相关功能。

实践

  1. 定义一个数据查询接口
package com.hillky.desgin_learn.Proxy.safeProxy;

public interface SensitiveDataQuery {

    String queryData(String userId);
}
  1. 实现真实敏感数据查询实现
package com.hillky.desgin_learn.Proxy.safeProxy;

public class SensitiveDataQueryImpl implements SensitiveDataQuery{
    @Override
    public String queryData(String userId) {
        return "SensitiveDataQuery form user :"+userId;
    }
}
  1. 安全代理类,内部进行验证
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();
    }

    @Override
    public String queryData(String userId) {
        if (userAuthenticator.hasPermission(userId)) {
            return sensitiveDataQuery.queryData(userId);
        } else {
            return "Access Denied: Insufficient permission for user" + userId;
        }
    }
}
  1. 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);
    }
}
  1. 测试代码编写

虚拟代理

● 用于需要延迟创建耗时或资源密集型对象
● 虚拟代理在初始时访问才创建实际对象,之后将直接使用该对象

实践

  1. 定义一个图片接口
package com.hillky.desgin_learn.Proxy.vitruralProxy;

public interface Image {
    void display();
}
  1. 实现一个大型图片类
package com.hillky.desgin_learn.Proxy.vitruralProxy;

public class LargeImage implements Image{

    private  String imageUrl;


    public LargeImage(String imageUrl) {
        this.imageUrl = imageUrl;
    }

    @Override
    public void display() {
        System.out.println("Displaying image: " + imageUrl);
    }
}
  1. 虚拟代理类
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;

    }

    @Override
    public void display() {
        if(largeImage==null){
            largeImage=new LargeImage(imageUrl);
        }
        largeImage.display();
    }
}
  1. 测试

可以看到虚拟代理如何实现懒加载,以减少资源消耗和提高程序性能。

远程代理

● 为本地对象提供与远程对象相同的接口,使得客户端可以透明地访问远程对象。需要做序列化反序列化,网络通信(不同语言的调用)

实践

  1. 定义一个服务接口
package com.hillky.desgin_learn.Proxy.remoteProxy;

public interface RemoteService {
    String fetchData(String dataId);
}
  1. ,实现一个远程服务类
package com.hillky.desgin_learn.Proxy.remoteProxy;

public class RemoteServiceImpl implements RemoteService{
    @Override
    public String fetchData(String dataId) {
        return "Data from remote service: " + dataId;
    }
}
  1. 创建一个远程代理类,它实现了 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();
    }

    @Override
    public 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;
    }
}
  1. 测试

请注意,为了简化代码,这里省略了实际的网 络通信、序列化和反序列化逻辑。

动态代理

● 静态代理需要手动编写代理类,代理类与被代理类实现相同的接口,对被代理对象进行包装。在程序运行前,代理类的代码就已经生成,并在程序运 行时调用。静态代理的优点是简单易懂,缺点是需要手动编写代理类,代码复杂度较 高,且不易扩展。
● 动态代理是在程序运行时动态生成代理类,无需手动编写代理类,大大降低了代码的 复杂度。通过反射实现

基于 JDK 的动态代理实现步骤

  1. 定义一个接口,声明需要代理的方法:
package com.hillky.desgin_learn.Proxy.remoteProxy;

public interface RemoteService {
    String fetchData(String dataId);
}
  1. 创建一个被代理类,实现这个接口,并在其中定义实现方法:
package com.hillky.desgin_learn.Proxy.remoteProxy;

public class RemoteServiceImpl implements RemoteService{
    @Override
    public String fetchData(String dataId) {
        return "Data from remote service: " + dataId;
    }
}
  1. 创建一个代理类,实现 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();
    }


    @Override
    public 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);
    }
}
  1. 测试代码,符合要求,只有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 {

    @Test
    void test() {
        // 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);
        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方法的使用,他是调用了invoke
        result = 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(面向切面编程)提供了一种有效的方式来对程序中的多个模块进行横切关注点的处理,例如日志、事务、缓存、安全等。从而实现对目标对象的增强

  1. 引入 AOP 相关依赖
<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-aopartifactId>
dependency>
  1. 开启自动代理
@EnableAspectJAutoProxy
@SpringBootApplication
@EnableAspectJAutoProxy
public class DesginLearnApplication {

    public static void main(String[] args) {
        SpringApplication.run(DesginLearnApplication.class, args);
    }

}
  1. 定义接口和实现类,并将具体实现注入容器
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{
    @Override
    public String query(String queryKey) {
        System.out.println("正在从数据库查询数据");
        return "result";
    }
}

  1. 定义切面类,对方法做增强
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;
    }

}
  1. 定义缓存
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);
    }
}
  1. 测试
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 {

    @Resource
    private DataQuery dataQuery;

    @Test
    void test(){
        dataQuery.query("key1");
        dataQuery.query("key1");
        dataQuery.query("key2");
    }

}

代理和 AOP 是两个相关的概念,代理是 AOP 的一种实现方式,它们都可以 用于在程序中实现对目标对象的增强。区别在于,代理主要是针对单个对象的方法调 用进行增强,而 AOP 则是针对程序中多个模块的横切关注点进行增强。

动态代理的应用场景

动态代理是一种代理模式,它在运行时动态生成代理对象,而无需提前创建具体的代理类。
使用场景:
● 日志记录
● 性能监控
● 事务管理
● 权限验证
● 缓存
● aop
● 速率限制

一般都可以用aop方便实现

日志记录

  1. 引入 AOP 相关依赖
<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-aopartifactId>
dependency>
  1. 开启自动代理
@EnableAspectJAutoProxy
@SpringBootApplication
@EnableAspectJAutoProxy
public class DesginLearnApplication {

    public static void main(String[] args) {
        SpringApplication.run(DesginLearnApplication.class, args);
    }

}
  1. 创建一个切面类(Aspect)来实现日志记录的逻辑
package com.hillky.desgin_learn.Proxy.aop;

public interface DataQuery {
    String query(String queryKey);
}
  1. 定义切面类,对方法做增强
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);
    }
}

  1. 测试接口访问看控制台输出
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!";
    }
}
  1. 测试
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 {

    @Resource
    private DataQuery dataQuery;

    @Test
    void test(){
        dataQuery.query("key1");
        dataQuery.query("key1");
        dataQuery.query("key2");
    }

}

你可能感兴趣的:(设计模式,设计模式,代理模式)