设计模式(三):代理模式(静态代理、动态代理)

我们都知道SpringAOP是用代理模式实现,那么到底是如何实现的呢?我们来探一探究竟,并手写部分代码。

定义

代理模式(Proxy Pattern):是指为其他对象提供一种代理、来控制这个对象的访问。
代理对象在客户端和目标对象之间起到一种中介作用。

目的:

1.保护目标对象:
我们不直接访问被代理对象,而是通过中间媒介去访问。
2.增强目标对象:
参考SpringAop的功能。

场景:

生活中场景:
租房中介、售票黄牛、婚介、经纪人、快递、事务代理、非侵入式日志监听等,这些都是代理模式的实际体现。
如何理解:
租房,房屋拥有者将想出租自己多余的房子,将房子托付给房屋中介,房屋中介就代理了这个房子,我们如果想要租房子就直接去找中介即可。

代理模式主要就是增加一个中间层,来实现代理与被代理之间的组合。

我们来看看类图结构:
在这里插入图片描述
Subject是顶层接口,RealSubject是真实的对象(被代理对象),Proxy是代理对象,代理对象拥有被代理对象的引用,客户端调用代理对象方法,同时也调用了被代理对象方法,但是在代理对象前后增加一些处理。在代码编写中,我们想到代理就会理解为是代码增强,其实就是在原本逻辑上增加一些前后逻辑,而调用者感知不到。代理模式属性结构型代理,有静态代理和动态代理

静态代理

这里使用业务场景来帮助理解:
在分布式业务场景中,我们通常会对数据库进行分库分表,分库分表之后使用Java操作时,就可能需要配置多个数据源,我们通过设置数据源路由来动态切换数据源。

1.创建订单实体Order:

@Data
public class Order {
    //订单信息
    private Object orderInfo;
    //订单创建时间
    private Long createTime;
    private String id;
}

2.创建OrderDao

//持久层,这里就直接写实现类了。
public class OrderDao {
    public  int insert(Order order){
        System.out.println("OrderDao 创建Order成功");
        return 1;
    }
}

3.创建IOrderService接口

public interface IOrderService {
    int createOrder(Order order);
}

4.创建OrderService实现类

public class OrderService implements IOrderService{

    private OrderDao orderDao;

    //模拟spring的注入 直接创建对象
    public OrderService(){
        orderDao = new OrderDao();
    }
    @Override
    public int createOrder(Order order) {
        System.out.println("调用OrderDao 创建订单");

        return orderDao.insert(order);
    }
}

咱们使用静态代理来进行切换数据源,依据开闭原则,写好的代理逻辑不去改变,通过代理对象来完成。先创建数据源路由对象,我们使用ThreadLocal的单例实现,DynamicDataSourceEntry类
5.DynamicDataSourceEntry

//动态切换数据源
public class DynamicDataSourceEntry {
    // 默认数据源
    public final static String DEFAULT_SOURCE = null;

    private final static ThreadLocal<String> local = new ThreadLocal<String>();

    private DynamicDataSourceEntry(){}

    //清空数据源
    public static 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); }
}

6.创建代理对象OrderServiceStaticProx 实现被代理的对象。

public class OrderServiceStaticProx  implements IOrderService {

    //日期格式化
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
    //代理对象
    private IOrderService orderService;

    //模拟spring注入
    public OrderServiceStaticProx(IOrderService orderService) {
        this.orderService = orderService;
    }

    public int createOrder(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.createOrder(order);
        after();
        return 0;
    }

    private void before() {
        System.out.println("Proxy before method.");
    }

    private void after() {
        System.out.println("Proxy after method.");
    }

}

测试代码:

 public static void main(String[] args) throws ParseException {
        Order order = new Order();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse("2020/03/11");
        order.setCreateTime(date.getTime());
        IOrderService orderService = new OrderServiceStaticProx(new OrderService());
        orderService.createOrder(order);
    }

测试结果:

Proxy before method.
静态代理类自动分配到【DB_2020】数据源处理数据。
调用OrderDao 创建订单
OrderDao 创建Order成功
Proxy after method.

符合我们预期的效果,通过代理来达到切换数据源的效果,并增加了before和after方法。
我们看一看类图是否符合我们的上文中写到的:
在这里插入图片描述

动态代理

动态代理实际上思路是与静态代理一致的,只不过动态代理的功能更强大,扩展性更强:
我们来看一看JDK动态代理的实现原理:
1、拿到被代理类的引用,并且获取它的所有的接口(反射获取)。
2、JDK Proxy类重新生成一个新的类,实现了被代理类所有 接口的方法。
3、动态生成Java代码,把增强逻辑加入到新生成代码中。
4、编译生成新的Java代码的class文件。
5、加载并重新运行新的class,得到类就是全新类。

我们按照以上思路来对上文静态代理做一些改进:修改OrderServiceStaticProx类

public class OrderServiceDynamicProx implements InvocationHandler {
    private SimpleDateFormat yearFormat = 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;

    }

    private void before(Object target) {
        try {
            System.out.println("Proxy before method.");
            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("Proxy after method.");
    }
}

测试代码:

public static void main(String[] args) {
        try {
            Order order = new Order();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
            Date date = sdf.parse("2020/03/31");
            order.setCreateTime(date.getTime());
            IOrderService orderService = (IOrderService) new OrderServiceDynamicProx().getInstance(new OrderService());
            orderService.createOrder(order);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

测试结果

Proxy before method.
动态代理类自动分配到【DB_2020】数据源处理数据。
调用OrderDao 创建订单
OrderDao 创建Order成功
Proxy after method.

达到了与静态代理一样的功能。但是,动态代理实现之后,我们不仅能实现Order的数据源动态路由,还可以实现其他任何类的数据源路由。

代理模式与Spring

Spring中代理核心类ProxyFactoryBean,核心方法getObject:我们首先来看看源码:

@Override
	@Nullable
	public Object getObject() throws BeansException {
		initializeAdvisorChain();
		if (isSingleton()) {
			return getSingletonInstance();
		}
		else {
			if (this.targetName == null) {
				logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
						"Enable prototype proxies by setting the 'targetName' property.");
			}
			return newPrototypeInstance();
		}
	}

在getObject()方法中,主要调用getSingletonInstance()和newPrototypeInstance(),在Spring的配置中,如果不做任何设置,那么Spring代理生成的Bean都是单例对象。如果修改scope则每次创建一个新的原型对象.
Spring利用动态代理实现AOP有两个非常重要的类,一个是JdkDynamicAopProxy类和CglibAopProxy类,来看一下类图:
在这里插入图片描述
Spring中的代理选择原则
1、当Bean有实现接口时,Spring就会用JDK的动态代理
2、当Bean没有实现接口时,Spring选择CGLib。
3、Spring可以通过配置强制使用CGLib,只需在Spring的配置文件中加入如下代码:

<aop:aspectj-autoproxy proxy-target-class="true"/> 

关与JDK的代理和CGLib我会另外整理单独记录,到时候会及时补上,还请见谅。

代理模式优缺点:
优点:
1、代理模式能将代理对象与真实被调用的目标对象分离。
2、一定程度上降低了系统的耦合度,扩展性好。
3、可以起到保护目标对象的作用。
4、可以对目标对象的功能增强。
缺点:
1、代理模式会造成系统设计中类的数量增加。
2、在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
3、增加了系统的复杂度。

望各位看过的伙伴们如果发现了问题能够及时批评指正,在此感谢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章