设计模式の简单理解代理模式
所谓代理模式(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 生成对象的步骤如下
-
通过反射获取被代理对象的引用,并且获取到它的所有的接口
-
JDK Proxy 类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口
-
动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现)
-
编译新生成的 Java 代码.class
-
再重新加载到 JVM 中运行
以上这个过程就叫字节码重组
CGLib 和 JDK 动态代理对比
-
JDK 动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象
-
JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM 框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类比 JDK 效率低
-
JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法,CGLib 执行效率更高
静态代理和动态的本质区别
-
静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则
-
动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则
代理模式优缺点
-
[优点] 代理模式能将代理对象与真实被调用的目标对象分离。
-
[优点] 一定程度上降低了系统的耦合度,扩展性好
-
[优点] 可以起到保护目标对象的作用
-
[优点] 可以对目标对象的功能增强
-
-
[缺点] 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
-
[缺点] 增加了系统的复杂度