设计模式の简单理解代理模式

设计模式の深度分析代理模式

所谓代理模式(Proxy Pattern)

是指为其他对象提供一种代理,以控制对这个对象的访问,代理对象在客服端和目标对象之间起到中介作用,代理模式属于结构型设计模式,使用 代理模式主要有两个目的:一保护目标对象,二增强目标对象,如:租房中介、售票黄牛、婚介

代理模式又分为静态代理动态代理,下面我们来看一下

静态代理

这里我们使用分库分表的业务场景来讲解静态代理,业务场景为通过订单的添加年份来实现分库

我们需要配置多个数据源,我们通过设置数据源路由来动态切换数据源

原有基本代码结构如下:

  • 实体类

@Data
public class Order {
    private int id;
    private String orderName;
    private Long createTime;
}
  • Mapper

/**
 * @description: 订单持久操作Mapper ,这里不接入数据库,功能演示即可
 */
public class OrderMapper {
    public int saveOrder(Order order){
        System.out.println("保存订单成功" + JSON.toJSONString(order));
        return 1;
    }
}
  • 业务Service

public interface OrderService {
    public int saveOrder(Order order);
}
public class OrderServiceImpl implements OrderService {
​
    private OrderMapper orderMapper;
    
    /**
     * 这里不使用Spring自动注入,我们手动注入演示效果即可
     */
    public OrderServiceImpl() {
        this.orderMapper = new OrderMapper();
    }
    
    @Override
    public int saveOrder(Order order){
        return orderMapper.saveOrder(order);
    }
}

以上就是我们寻常的代码了,下面由于我们数据库数据过多,想要使用到分库来分散数据库压力

根据开闭原则,原来我们写好的逻辑我们不做变更,通过代理对象来完成上面我们的需求

首先我们使用一个容器来保存数据源

public class DynamicDataSourceEntry {
​
    // 我们使用 ThreadLocal 的单例实现
    private final static ThreadLocal<String> local = new ThreadLocal<String>();
​
    // 根据年份动态设置数据源
    public static void set(int year) { local.set("DB_" + year); }
​
}

创建切换数据源的代理类

/**
 * @description: 切换数据源的静态代理类
 */
public class OrderServiceImplStaticProxy  implements OrderService{
​
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
​
    private OrderService orderService;
​
    public OrderServiceImplStaticProxy() {}
​
    public OrderServiceImplStaticProxy(OrderService orderService) {
        this.orderService = orderService;
    }
​
    private void before(){ System.out.println("代理前置方法"); }
​
    private void after(){ System.out.println("代理后置方法"); }
    
    @Override
    public int saveOrder(Order order) {
        before();
        Long time = order.getCreateTime();
        Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
        System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据。");
        DynamicDataSourceEntry.set(dbRouter);
        orderService.saveOrder(order);
        after();
        return 0;
    }
}

测试Main方法,可以发现已经实现根据订单的添加年份来实现分库保存功能

动态代理

  • 大家都知道JDK有个动态代理,下面我们针对上方的数据源静态路由再来搞一个数据源动态路由

创建动态代理的类

public class OrderServiceImplDynamicProxy implements InvocationHandler {
​
    //被代理的对象,把引用给保存下来
    private Object target;
​
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
​
    public Object getInstance(Object target){
        this.target = target;
        Class<?> clazz = target.getClass();
        //通过JDK Proxy获取代理对象
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before(args[0]);
        Object object = method.invoke(target,args);
        after();
        return object;
    }
​
​
    private void before(Object target){
        try {
            System.out.println("动态代理前置方法");
            //因为我们是对时间进行划分,这里我用过getCreateTime()反射,获取值
            Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
            Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
            System.out.println("动态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据。");
            DynamicDataSourceEntry.set(dbRouter);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
​
    private void after(){
        System.out.println("动态代理后置方法");
    }
}

测试Main方法,可以发现依然能够达到相同运行效果

JDK Proxy 生成对象的步骤

JDK Proxy 采用字节重组,重新生的对象来替代原始的对象以达到动态代理的目的。JDK Proxy 生成对象的步骤如下

  1. 通过反射获取被代理对象的引用,并且获取到它的所有的接口

  2. JDK Proxy 类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口

  3. 动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现)

  4. 编译新生成的 Java 代码.class

  5. 再重新加载到 JVM 中运行

以上这个过程就叫字节码重组

CGLib 和 JDK 动态代理对比

  1. JDK 动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象

  2. JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM 框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类比 JDK 效率低

  3. JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法,CGLib 执行效率更高

静态代理和动态的本质区别

  • 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则

  • 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则

代理模式优缺点

  • [优点] 代理模式能将代理对象与真实被调用的目标对象分离。

  • [优点] 一定程度上降低了系统的耦合度,扩展性好

  • [优点] 可以起到保护目标对象的作用

  • [优点] 可以对目标对象的功能增强

  • [缺点] 代理模式会造成系统设计中类的数量增加

  • [缺点] 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢

  • [缺点] 增加了系统的复杂度

 

 

 

 

 

 

posted @ 2021-11-29 21:00  鞋破露脚尖儿  阅读(58)  评论(0编辑  收藏  举报