23种设计模式之代理模式(三种)

代理模式的应用场景

在生活中,我们经常见到这样的场景,如:租房中介、售票黄牛、婚介、经纪人、快递、事务代理、非侵入式日志监听等,这些都是代理模式的实际体现。代理模式(ProxyPattern)的定义也非常简单,是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客服端和目标对象之间起到中介作用,代理模式属于结构型设计模式。使用代理模式主要有两个目的:一保护目标对象,二增强目标对象。

案例一

package test1;
public interface Subject {
    void request();
}
package test1;
/**
 * 被代理对象(真实对象)
 */
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("real service is called.");
    }
}
package test1;

/**
 * 代理对象
 */
public class Proxy implements Subject {

    private Subject subject;

    public Proxy(Subject subject){
        this.subject = subject;
    }

    @Override
    public void request() {
        before();
        subject.request();
        after();
    }

    public void before(){
        System.out.println("called before request().");
    }

    public void after(){
        System.out.println("called after request().");
    }
}
package test1;
/**
 * 为其它对象提供一种代理,以控制对这个对象的访问
 */
public class Client {
    public static void main(String[] args) {
        Proxy proxy = new Proxy(new RealSubject());
        proxy.request();
    }
}

Subject 是顶层接口,RealSubject是真实对象(被代理对象),Proxy是代理对象,代理对象持有被代理对象的引用,客户端调用代理对象方法,同时也调用被代理对象的方法,但是在代理对象前后增加一些处理。在代码中,我们想到代理,就会理解为是代码增强,其实就是在原本逻辑前后增加一些逻辑,而调用者无感知。代理模式属于结构型模式,有静态代理和动态代理。

静态代理

举个例子:人到了适婚年龄,父母总是迫不及待希望早点抱孙子。而现在社会的人在各种压力之下,都选择晚婚晚育。于是着急的父母就开始到处为自己的子女相亲,比子女自己还着急。这个相亲的过程,就是一种我们人人都有份的代理。来看代码实现:

案例一

package test2;
/**
 * 人有很多行为,要谈恋爱,要住房子,要购物,要工作
 */
public interface Person {
    public void findLove();
}

儿子要找对象,实现Son类:

package test2;

public class Son implements Person {
    @Override
    public void findLove() {
        //我没有时间
        //工作忙
        System.out.println("儿子要求:肤白貌美大长腿");
    }
}

父亲要帮儿子相亲,实现 Father 类:

package test2;

public class Father {
    private Son son;

    public Father(Son son){
        this.son = son;
    }
    //目标对象的引用给拿到
    public void findLove(){
        System.out.println("父母物色对象");
        son.findLove();
        System.out.println("双方同意交往,确立关系");
    }
}

测试代码:

    public static void main(String[] args) {
        Father father = new Father(new Son());
        father.findLove();
    }

执行结果:
在这里插入图片描述

案例二

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

先创建 Order 订单实体:

扫描二维码关注公众号,回复: 8631340 查看本文章
package test3;

/**
 * 订单
 */
public class Order {
    private Object orderInfo;
    private Long createTime;
    private String id;

    public Object getOrderInfo() {
        return orderInfo;
    }

    public void setOrderInfo(Object orderInfo) {
        this.orderInfo = orderInfo;
    }

    public Long getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Long createTime) {
        this.createTime = createTime;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

创建 OrderDao 持久层操作类:

package test3;
public class OrderDao {
    public int insert(Order order){
        System.out.println("OrderDao 创建 Order 成功!");
        return 1;
    }
}

创建 IOrderService 接口:

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

创建 OrderService 实现类:

package test3;
public class OrderService implements IOrderService {
    private OrderDao orderDao;

    public OrderService(){
        //如果使用 Spring 应该是自动注入的
        //我们为了使用方便,在构造方法中将 orderDao直接初始化了
        orderDao = new OrderDao();
    }

    @Override
    public int createOrder(Order order) {
        System.out.println("OrderService 调用 orderDao 创建订单");
        return orderDao.insert(order);
    }
}

接下来使用静态代理,主要完成的功能是,根据订单创建时间自动按年进行分库。根据开闭原则,原来写好的逻辑我们不去修改,通过代理对象来完成。先创建数据源路由对象,我们使用 ThreadLocal 的单例实现DynamicDataSourceEntry 类:

package test3;
/**
 * 动态数据源切换
 */
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();
    }

    /**
     * 获取当前正在使用的数据源名字
     * @return
     */
    public static String get(){
       return local.get();
    }

    /**
     * 还原当前切面的数据源
     */
    public static void restore(){
        local.set(DEFAULT_SOURCE);
    }

    /**
     * 设置已知名字的数据源
     * @param source
     */
    public static void set(String source){
        local.set(source);
    }

    /**
     * 根据年份动态设置数据源
     * @param year
     */
    public static void set(int year){
        local.set("DB_"+year);
    }
}

创建切换数据源的代理 OrderServiceSaticProxy 类:

package test3;

import java.text.SimpleDateFormat;
import java.util.Date;

public class OrderServiceStaticProxy implements IOrderService {

    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    private IOrderService orderService;

    public OrderServiceStaticProxy(IOrderService orderService){
       this.orderService = orderService;
    }

    @Override
    public int createOrder(Order order) {
        before();
        Long time = order.getCreateTime();
        Integer dbRouter = Integer.valueOf(yearFormat.format(new Date()));
        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.");
    }
}

测试代码:

package test3;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Test3Main {
    public static void main(String[] args) throws Exception {
        Order order = new Order();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse("2017/02/01");
        order.setCreateTime(date.getTime());
        IOrderService orderService = new OrderServiceStaticProxy(new OrderService());
        orderService.createOrder(order);
    }
}

测试结果:
在这里插入图片描述

动态代理

动态代理和静态对比基本思路是一致的,只不过动态代理功能更加强大,随着业务的扩展适应性更强。如果还以找对象为例,使用动态代理相当于是能够适应复杂的业务场景。不仅仅只是父亲给儿子找对象,如果找对象这项业务发展成了一个产业,进而出现了媒婆、婚介所等这样的形式。那么,此时用静态代理成本就更大了,需要一个更加通用的解决方案,要满足任何单身人士找对象的需求。

JDK动态代理

创建媒婆(婚介)JDKMeipo 类:

package meipo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKMeipo implements InvocationHandler {

    private Object target;

    public Object getInstance(Object target) throws Exception{
        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();
        Object obj = method.invoke(this.target,args);
        after();
        return obj;
    }
    public void before(){
        System.out.println("我是媒婆:我要给你找对象,现在已经拿到你的需求");
        System.out.println("开始物色");
    }
    public void after(){
        System.out.println("如果合适的话,就准备办事");
    }
}

创建单身客户Customer 类:

package meipo;
import test2.Person;
public class Customer implements Person {

    @Override
    public void findLove() {
        System.out.println("高富帅");
        System.out.println("身高 180cm");
        System.out.println("胸大,6 块腹肌");
    }
}

测试代码:

 public static void main(String[] args) throws Exception {
      Person obj = (Person) new JDKMeipo().getInstance(new Customer());
      obj.findLove();
    }

测试结果:
在这里插入图片描述

JDK改造动态路由案例

创建动态代理的类 OrderServiceDynamicProxy,代码如下:

package daymicRoute;
import sun.dc.pr.PRError;
import test3.DynamicDataSourceEntry;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
public class OrderServiceDynamicProxy 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(time));
            System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据。");
            DynamicDataSourceEntry.set(dbRouter);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

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

测试代码:

package daymicRoute;
import test3.IOrderService;
import test3.Order;
import test3.OrderService;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DaymicRouteTest {
    public static void main(String[] args) throws Exception {
        Order order = new Order();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse("2018/02/01");
        order.setCreateTime(date.getTime());
        IOrderService orderService = (IOrderService)new OrderServiceDynamicProxy().getInstance(new
                OrderService());
        orderService.createOrder(order);
    }
}

测试结果:
在这里插入图片描述
依然能够达到相同运行效果。但是,动态代理实现之后,我们不仅能实现 Order 的数据源动态路由,还可以实现其他任何类的数据源路由。当然,有比较重要的约定,必须要求实现 getCreateTime()方法,因为路由规则是根据时间来运算的。当然,我们可以通过接口规范来达到约束的目的,在此就不再举例。

Cglib代理

package test4;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MeiPoCgLibProxy implements MethodInterceptor {
    /**
     *业务类对象,供代理方法中进行真正的业务方法调用
     */
    private Object target;

    public Object getInstance(Object target){
        //给业务对象赋值
        this.target = target;
        //创建加强器,用来创建动态代理类
        Enhancer enhancer = new Enhancer();
        //为加强器指定要代理的业务类
        enhancer.setSuperclass(this.target.getClass());
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack
        enhancer.setCallback(this);
        //创建动态代理类对象并返回
        return enhancer.create();
    }

    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("业务处理前-----------");
        methodProxy.invokeSuper(object,args);
        System.out.println("业务处理后-----------");
        return null;
    }
}
package test4;
public class PersonImpl {
    public void hello(){
        System.out.println("hello 您好!");
    }
}

测试代码:

package test4;
public class TestCglib {
    public static void main(String[] args) {
        PersonImpl bookCglib=(PersonImpl)new MeiPoCgLibProxy().getInstance(new PersonImpl());
        bookCglib.hello();
    }
}

执行结果:
在这里插入图片描述

  • 当Bean实现接口时,Spring就会用JDK的动态代理。
  • Bean没有实现接口时,Spring使用CGlib是实现。
  • 可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。
发布了60 篇原创文章 · 获赞 1 · 访问量 3331

猜你喜欢

转载自blog.csdn.net/qq_16438883/article/details/103580599