设计模式之代理模式

设计模式之代理模式

代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。代理模式属于结构性设计模式。

代理类的作用类似于租房的中介、售票黄牛、快递小哥、经纪人、事务代理、非入侵式的日志监控。

代理模式包括静态代理和动态代理。

静态代理

静态代理是指代理类B对服务类A进行代理,代理A去提供服务,并对A提供的服务进行增强,而且,代理类B只能代理服务类A。下面用两个例子来讲解静态代理。

例子一

比如儿子要找对象,要求是肤白貌美大长腿,父亲帮儿子找。
父亲和儿子都是人,所以我们先写一个人的接口,他有一个方法,找对象。

public interface Person {
	int findLove();
}

接下来写儿子类,他要实现人的接口,同时实现找对象方法。

public class Son implements Person {
	public int findLove() {
		System.out.println("儿子要求:肤白貌美大长腿");
		return 0;
	}
	public void findJob() {
	}
	public void eat() {
	}
}

然后是父亲类,他有一个自己儿子的引用,并且实现了找对象方法,他帮他儿子找对象。

public class Father implements Person {
	private Son person;
	
	public Father(Son person) {
		this.person = person;
	}
	public int findLove() {
		System.out.println("父亲物色对象");
		this.person.findLove();
		System.out.println("双方父母同意,确立关系");
		return 0;
	}
	public void findJob() {
	}
}

这种就是典型的静态代理,儿子要找对象,父亲帮儿子找对象,而且只给自己的儿子找对象,这也是
静态代理的局限性,只给特定的对象做代理。下面我们写一个测试类看看运行结果。

public class FatherProxyTest {

	public static void main(String[] args) {
		Father father = new Father(new Son());
		father.findLove();
		// 输出:
		// 父亲物色对象
		// 儿子要求:肤白貌美大长腿
		// 双方父母同意,确立关系
	}
}

结果输出了“父亲物色对象,儿子要求:肤白貌美大长腿,双方父母同意,确立关系”,我们能看出,儿子并没有去找对象,是父亲去找对象的,而且父亲在儿子找对象之前和之后做了其他的一些操作,对儿子找对象进行了增强,这种自己的事情A不去做,交给其他人B去做,这个其他人B能对A要做的事情进行增强,并且只能专门为A做增强的情况,就是静态代理。

例子二

接下来进行一个数据源动态切换的场景,比如一个订单系统,要完成下单的操作,要求能根据订单时间进行分库。

首先是订单实体:

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;
	}
}

然后是生成订单的DAO:

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

然后是提供生成订单的service接口和实现:

public interface IOrderService {
	int createOrder(Order order);
}
public class OrderService implements IOrderService {
	private OrderDao orderDao;

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

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

然后我们写一个动态数据源实体:

public class DynamicDataSourceEntity {

	public final static String DEFAULE_SOURCE = null;

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

	private DynamicDataSourceEntity() {
	}

	public static String get() {
		return local.get();
	}

	public static void restore() {
		local.set(DEFAULE_SOURCE);
	}

	// DB_2018
	// DB_2019
	public static void set(String source) {
		local.set(source);
	}

	public static void set(int year) {
		local.set("DB_" + year);
	}

}

这个数据源实体用了单例模式的ThreadLocal写法,以保证线程里获取到的数据源实例是同一个。

然后我们对这个数据源进行代理:

public class OrderServiceStaticProxy implements IOrderService {
	private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

	private IOrderService orderService;

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

	public int createOrder(Order order) {
		Long time = order.getCreateTime();
		Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
		System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
		DynamicDataSourceEntity.set(dbRouter);

		this.orderService.createOrder(order);
		DynamicDataSourceEntity.restore();

		return 0;
	}
}

这样我们就能对不同的订单根据订单时间来进行分库查询,根据订单时间获取到数据源名称,然后用订单代理类调用数据源实体的方法实时切换数据源。
这也是静态代理,订单代理类对订单服务进行代理,代替订单服务去进行创建订单,并对订单服务进行增强,根据订单时间进行分库。

动态代理

动态代理相对于静态代理来说更加灵活,动态代理同样是代理类代替服务类去提供服务,同时对服务类提供的服务进行增强,但是动态代理不仅仅只能对一种服务进行增强,他对要增强的服务没有要求。比如上面的例子父亲给儿子找对象,他只能给儿子找对象,要是他相帮儿子找工作的话,父亲类就得增加找工作的方法,儿子要干什么,父亲也得有相应的方法才行,儿子增加了方法,父亲类也得修改,这不符合开闭原则。

动态代理的实现有两种,分别是JDK的动态代理,和cglib提供的动态代理。

JDK动态代理

回到找对象的例子,现在有个女孩要找对象,她不自己去找,她通过媒婆去找,媒婆是可以给任何人找对象的,他可以给女孩找,也可以给儿子找,等等,下面用JDK的动态代理实现这个过程。
首先是女孩类,实现人的接口,同样有找对象方法:

public class Girl implements Person {
	public int findLove() {
		System.out.println("高富帅");
		System.out.println("身高180cm");
		System.out.println("有6块腹肌");
		return 0;
	}
}

然后是媒婆类,实现了JDK反射包下面的InvocationHandler接口:

public class JDKMeipo implements InvocationHandler {

	private Object target;

	public Object getInstance(Object person) throws Exception {
		this.target = person;
		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 obj = method.invoke(this.target, args);
		after();
		return obj;
	}

	private void before() {
		System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求");
		System.out.println("开始物色");
	}

	private void after() {
		System.out.println("OK的话,准备办事");
	}
}

到这里先不讲媒婆类里的方法都是干什么用的,先写个测试类运行看下结果:

public class JDKProxyTest {
	public static void main(String[] args) {
		try {
			Object obj = new JDKMeipo().getInstance(new Girl());
			Method method = obj.getClass().getMethod("findLove", null);
			method.invoke(obj);
			// Person obj = (Person) new JDKMeipo().getInstance(new Girl());
			// obj.findLove();

			// $Proxy0
			// byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
			// FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
			// os.write(bytes);
			// os.close();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

一运行,神奇的事情发生了,结果输出:

我是媒婆,我要给你找对象,现在已经确认你的需求
开始物色
高富帅
身高180cm
有6块腹肌
OK的话,准备办事

Object obj = new JDKMeipo().getInstance(new Girl());这行代码生成的obj是什么?如果它是媒婆的话,为什么他会有findLove方法?如果它是个Girl的话,它调用findLove方法为什么会输出“我是媒婆XXX”这些?
重点来了,在我们打断点想看看这个obj是什么的时候看到:
JDK动态代理生成的类
这个obj是个 $Proxy0,我们没有一个叫做 $Proxy0的类啊,它为什么会有findLove方法,而且前后多输出了东西?接下来我们把这个 $Proxy0这个对象从内存中输出出来,写到磁盘上,变成一个class文件,再反编译出来,看看它到底是什么鬼。

		byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
		FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
		os.write(bytes);
		os.close();

再执行完注释掉的这段代码后,E盘生成了一个叫$Proxy0的class文件,反编译一下,看看里面是什么:

public final class $Proxy0 extends Proxy
  implements Person
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;

  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int findLove()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m3, null)).intValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.lmj.study.pattern.proxy.Person").getMethod("findLove", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

我们能看到,这个 $Proxy0继承了Proxy,又实现了Person接口,它为什么要实现Person接口呢,就是为了重写findLove方法,然后在findLove里执行了h.invoke方法,这个h对象就是媒婆实现的InvocationHandler 接口的对象,这个媒婆是从那里传进来的呢,是从媒婆的getInstance方法里把this传进去的,Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this)这个方法把this赋值给了h,然后就执行了媒婆的invoke方法,媒婆的invoke方法里调用了before和after方法。

往下看 $Proxy0这个类的话会发现,它下面有一个静态代码块,通过反射,把代理目标对象的方法全部扫描出来,上面findLove方法第二个参数传的是m3,h.invoke(this, m3, null),m3就是Girl的findLove方法,然后在媒婆的invoke方法里调用这个Girl的findLove方法,至此一切真相大白。

总结起来就是,媒婆类并不是真正做代理的代理类,媒婆类通过Proxy.newProxyInstance生成了一个新的类,然后用类加载器动态加载到JVM中,这个才是真正的代理类,$Proxy0这个代理类继承了JDK的Proxy类,又实现了引用的被代理类实现的接口,调用被代理类的方法时,实际上是调用了 $Proxy0里的相对应的方法,里面调用了h.invoke方法,h.invoke就是代理工具类里的invoke方法。

  1. 拿到被代理类的引用,并且获取到它的所有接口(反射获取);
  2. JDK的Proxy类重新生成一个新的类,实现了被代理类的所有接口的方法;
  3. 动态生成JAVA代码,把增强逻辑加到新生成的代码中;
  4. 编译生成新的JAVA代码的class文件;
  5. 加载并重新运行新的class,得到的类就是新的代理类。

CgLib动态代理

CgLib的动态代理和JDK动态代理的思想一样,都是通过生成字节码文件,重新生成一个类。他们二者的区别是:

  1. CgLib采用继承的方式,覆盖父类的方法,JDK Proxy采用实现接口的方式,要求被代理的目标对象必须实现一个接口;
  2. JDK Proxy对用户而言,依赖性更强,调用也更复杂,CgLib对目标类没有任何要求;
  3. CgLib效率更高,性能也更高,底层没有用到反射,JDK Proxy生成逻辑较为简单,每次都要用反射,执行效率低;
  4. CgLib有个坑,目标代理类不能有final方法,会忽略final修饰的方法;

例子:

import java.lang.reflect.Method;

import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

//Cglib动态代理,实现MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
    private Object target;//需要代理的目标对象
    
    //重写拦截方法
    @Override
    public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
        System.out.println("Cglib动态代理,监听开始!");
        Object invoke = method.invoke(target, arr);//方法执行,参数:target 目标对象 arr参数数组
        System.out.println("Cglib动态代理,监听结束!");
        return invoke;
    }
    //定义获取代理对象方法
    public Object getCglibProxy(Object objectTarget){
        //为目标对象target赋值
        this.target = objectTarget;
        Enhancer enhancer = new Enhancer();
        //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(objectTarget.getClass());
        enhancer.setCallback(this);// 设置回调 
        Object result = enhancer.create();//创建并返回代理对象
        return result;
    }
    
    public static void main(String[] args) {
        CglibProxy cglib = new CglibProxy();//实例化CglibProxy对象
        UserManager user =  (UserManager) cglib.getCglibProxy(new UserManagerImpl());//获取代理对象
        user.delUser("admin");//执行删除方法
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值