在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端与目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。
代理模式
代理模式(Proxy Pattern):为某一个对象提供一个代理或占位符,并由代理对象控制对原对象的访问。代理模式是一种对象结构型模式。
设计思想
抽象主题角色(Subject):抽象主题类的职责是声明了真实主题和代理主题共有的接口方法。
代理主题角色(Proxy):也称为代理类,内部持有RealSubject的引用,因此完全具备对RealSubject的代理权。客户端调用代理对象的方法,也调用被代理对象的方法,但是会在代理对象前后增加一些处理代码。
真实主题角色(RealSubject):定义了代理角色所代表的真实对象,实现了真正的业务操作。
伪代码
//抽象主题角色
public interface Subject{
void request();
}
//真实主题角色
public class RealSubject implements Subject{
public void request(){
//业务方法
}
}
//代理主题角色
public class ProxySubject implements Subject{
//内部持有RealSubject的引用
RealSubject realSubject = new RealSubject();
//增加的业务
public void preRequest(){
...
}
public void request(){
preRequest();
//调用真实主题对象的方法
realSubject.request();
postRequest();
}
//增加的业务
public void postRequest(){
...
}
}
public class Client{
public static void main(String[] args){
ProxySubject proxy = new ProxySubject();
proxy.request();
}
}
业务需求
1.相亲:父亲张老三替儿子张三去物色对象
public interface Person{
void findLove();
}
public class ZhangSan implements Person{
public void findLove(){
System.out.println("张三提出自己的要求");
}
}
public class ZhangLaoSan implements Person{
ZhangSan zhangsan;
public ZhangLaoSan(ZhangSan zhangsan){
this.zhangsan = zhangsan;
}
public void findLove(){
System.out.println("张老三代替张三物色对象");
zhangsan.findLove();
System.out.println("开始交往");
}
}
public class Test{
ZhangLaoSan zhangLaoSan = new ZhanLaoSan(new ZhangSan());
zhangLaoSan.findLove();
}
上面的场景有个弊端,自己的父亲只会帮自己的子女物色对象。但社会上这项业务发展成了产业,出现了媒婆、婚介所等,还有各种各样的定制套餐。如果还使用静态代理成本就太高了,需要一个更加通用的解决方案,满足任何单身人士找对象的需求。这就由静态代理升级到了动态代理。采用动态代理基本上只要是人(Person)就可以提供相亲服务。
动态代理的底层实现一般不用我们亲自去实现,已经有很多现成的API。在Java生态中,目前普遍使用的是JDK自带的代理和CGLib提供的类库。首先基于JDK动态代理支持来升级一下代码。
//抽象主题角色:人
public interface Person{
void findLove();
}
//代理主题角色:媒婆
public class JdkMeipo implements InvocationHandler {
private Person target;
//返回代理对象
public Person getInstance(Person target){
this.target = target;
Class<?> clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
//代理对象在执行业务方法前后会增加其他服务
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
before();
Object result = method.invoke(this.target, args);
after();
return result;
}
private void before(){
System.out.println("我是媒婆,我在收集你的需求");
}
private void after(){
System.out.println("双方同意,开始交往");
}
}
//真实主题角色:赵六
public class Zhaoliu implements Person{
public void findLove(){
System.out.println("符合赵六的要求");
}
}
//客户端
public class Client{
public static void main(String[] args){
JdkMeipo meipo = new JdkMeipo();
Person proxy = meipo.getInstance(new Zhaoliu());
proxy.findLove();
}
}
2.切换数据源:根据订单创建事件自动按年进行分库
2.1 静态代理实现无感知动态切换数据源
实体类
//实体类Order
@Data
public class Order{
private Object orderInfo;
private Long createTime;
private String id;
}
Order层
//创建Order持久层操作类
public class OrderDao{
public int insert(Order order){
System.out.println("OrderDao创建Order成功");
return 1;
}
}
Service层
//创建OrderService
public interface OrderService{
int createOrder(Order order);
}
//创建OrederService的实现类,被代理类
@Service
pubic class OrderServiceImpl implements OrderService{
@Autowired
private OrderDao orderDao;
public int createOrder(Order order){
orderDao.insert(order);
}
}
动态切换数据源
public class DynamicDataSourceEntry{
//默认数据源
public final staic String DEFAULT_SOURCE = null;
private final static ThreadLocal<String> local = new ThreadLocal<>();
private DynamicDataSourceEntry(){};
//清空数据源
public staic void clear(){
local.remove();
}
//获取当前正在使用的数据源名字
public static String get(){
return local.get();
}
//还原默认数据库
public static void restore(){
local.set(DEFAULT_SOURCE);
}
//设置已知名字的数据库
public static void set(String source){
local.set(source);
}
//根据年份动态设置数据库
public static void set(int year){
local.set("DB_"+year);
}
}
切换数据源的静态代理类
public class OrderServiceStaticProxy implements OrderService{
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
@Autowired
private OrderService orderService;
public int createOrder(Order order){
before(order);
orderService.createOrder(order);
after();
return 0;
}
private void before(Order order){
//增加业务1:切换数据源
Long time = order.getCreateTime();
Integer dbRouter = Integer.valueOf(sdf.format(new Date(time)));
System.out.println("动态切换数据源");
DynamcieDataSourceEntry.set(dbRouter);
}
private void after(){
//增加业务2...
}
}
客户端
public class Client{
public static void main(String[] args){
//创建order实体
Order order = new Order();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("2021-09-19");
order.setCreateTime(date.getTime());
//静态代理对象执行业务
OrderService proxy = new OrderServiceStaticProxy();
proxy.createOrder(order);
}
}
2.2 动态代理实现无感知动态切换数据源
public class OrderServiceDynamicPorxy implements InvocationHandler {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
private Object target;
public Object getInstance(Object target){
this.target = target;
Class<?> clazz = target.getClass();
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;
}
public void before(Object target){
try{
Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
Integer dbRouter = Integer.valueOf(sdf.format(new Date(time)));
System.out.println("动态切换数据源");
DynamcieDataSourceEntry.set(dbRouter);
}catch(Exception e){
e.printStackTrace();
}
}
public void after(){
System.out.println("Proxy after method.");
}
}
//客户端
public class Client{
public static void main(String[] args){
//创建order实体
Order order = new Order();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("2021-09-19");
order.setCreateTime(date.getTime());
//动态代理对象执行业务
@Autowired
OrderService orderService;
OrderServiceDynamicProxy dynamicProxy = new OrderServiceDynamicProxy();
OrderService proxy = dynamicProxy.getInstance(orderService);
proxy.createOrder(order);
}
}
使用动态代理实现之后,不仅能实现Order的数据源动态路由,还可以实现其他任何类的数据源路由。当然,有一个比较重要的约定,必须实现getCreateTime()方法,因为路由规则是根据时间来运算的。
代理模式的扩展
静态代理和动态代理的区别
1)静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
2)动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
3)若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无须修改代理类的代码。
代理模式的优点
1)代理模式能将代理对象与真实被调用目标对象分离。
2)在一定程度上降低了系统的耦合性,扩展性好。
3)可以起到保护目标对象的作用。
4)可以增强目标对象的功能。
代理模式的缺点
1)代理模式会造成系统设计中类的数量增加。
2)代理模式会造成系统设计中类的数量增加。
3)增加了系统的复杂度。