JAVA设计模式之代理模式

代理一般分为静态代理和动态代理,静态代理在平时工作中还是用的比较少的,比较常用的是动态代理。代理模式简单理解就是,外部对象不去访问实际执行的类对象,而去访问他的代理,让代理去访问。例如,超市想要批发某种食品,超市老板不需要去到该食品的工厂商量购买,只需找到该食品的代理商,代理商会去完成这些事,而工厂只需要生产食品就好,这样就消除了超市老板和工厂之间的耦合度,只需要各自关心自己的事情就可以。

UML图如下

从程序的角度来看,由于是通过代理对象去调用实际对象的方法,那么代理对象就可以在实际对象方法调用前后完成一些操作,比如记录被调用的方法耗时。

一、静态代理

写个简单的例子来演示一下。

首先抽象出一个接口,意为“可移动”,仅有一个move方法,然后添加一个实现类Car。

public interface Movable {

	void move();
	
}

public class Car implements Movable{

	@Override
	public void move() {
		System.out.println("car is moving...");
		try {
			// 让汽车开一会
			Thread.sleep(new Random().nextInt(10000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

接着添加一个Time的代理,用于记录Movable对象的move方法耗时。

package com.xc.proxy.staticproxy;

public class TimeMovableProxy implements Movable{

	private Movable movable;
	
	public TimeMovableProxy(Movable movable) {
		this.movable = movable;
	}
	
	@Override
	public void move() {
		System.out.println("start to record time");
		long start = System.currentTimeMillis();
		movable.move();
		long end = System.currentTimeMillis();
		System.out.println("cost time: " + (end - start) + "ms");
	}

}

注意,如果这里的私有属性不使用Movable类型,而是用Car类型,那么他就只能记录Car类型的move方法耗时。使用Movable类型,就可以记录所有Movable实现类的耗时。

同时TimeMovableProxy类也实现了Movable接口,这说明他本身也可以被代理,即代理的代理。比如我们希望在记录时间之前先记录一下日志,那么可以再添加一个Log代理。Log代理的实现,其实跟Time代理是一样的,所以他也可以被代理。

public class LogMovableProxy implements Movable{
	
	private Movable movable;
	
	public LogMovableProxy(Movable movable) {
		this.movable = movable;
	}
	
	@Override
	public void move(){
		System.out.println("start log...");
		movable.move();
		System.out.println("end log...");
	}
	
}

测试一下

public class Main {

	public static void main(String[] args) {
		// 仅仅使用Time代理
		// Movable movable = new TimeMovableProxy(new Car());
		// 使用Time和Log代理,先代理Time再代理Log
		// Movable movable = new TimeMovableProxy(new LogMovableProxy(new Car()));
		// 使用Time和Log代理,先代理Log再代理Time
		Movable movable = new LogMovableProxy(new TimeMovableProxy(new Car()));
		movable.move();
	}

}

二、动态代理

java中动态代理一般使用的是JDK动态代理或CGLIB动态代理,都是通过运行期间生成字节码文件来实现动态代理的目的。

JDK动态代理,优点:不需引入第三方jar包,且速度较快;缺点:只能代理接口或实现了接口的类。

CGLIB动态代理,优点:可以代理任何不是final的类;缺点:需要引入第三方jar包,速度稍慢。

下面,分别来使用这两种动态代理,同样还是使用Movable接口,和一个实现类Car。另外新建一个Train类不实现任何接口,但是也有一个同名的move方法。我们使用JDK动态代理Car,使用CGLIB来代理Train,都用来做计时操作。

public interface Movable {

	void move();
	
}

public class Car implements Movable{

	@Override
	public void move() {
		System.out.println("car is moving...");
	}

}

public class Train {

	public void move() {
		System.out.println("train is moving...");
	}
}

1.JDK动态代理

JDK动态代理需要实现InvocationHandler接口。

public class TimeInvocationHandler implements InvocationHandler {

	private Object obj;
	
	public TimeInvocationHandler(Object obj) {
		this.obj = obj;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("start to record time");
		long start = System.currentTimeMillis();
		Object o = method.invoke(obj, args);
		long end = System.currentTimeMillis();
		System.out.println("cost time: " + (end - start) + "ms");
		return o;
	}

}

2.CGLIB动态代理

首先需要引入CGLIB的包,如果使用的MAVEN的话就直接引入CGLIB的坐标就行。我这里没使用MAVEN就直接导入了两个jar包,cglib-3.1.jar 和 asm-4.2.jar。

CGLIB的方式需要实现MethodInterceptor接口。

public class TimeMethodInterceptor implements MethodInterceptor{

	@Override
	public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
		System.out.println("start to record time");
		long start = System.currentTimeMillis();
		Object o = methodProxy.invokeSuper(obj, objects);
		long end = System.currentTimeMillis();
		System.out.println("cost time: " + (end - start) + "ms");
		return o;
	}

}

然后测试一下。

public class Main {

	public static void main(String[] args) {
		System.out.println("----------jdk proxy----------");
		jdkProxy();
		System.out.println("---------cglib proxy---------");
		cglibProxy();
	}
	
	public static void jdkProxy() {
		Movable m = (Movable)Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(), new TimeInvocationHandler(new Car()));
		m.move();
	}
	
	public static void cglibProxy() {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(Train.class);
		enhancer.setCallback(new TimeMethodInterceptor());
		Train train = (Train)enhancer.create();
		train.move();
	}

}

注意,JDK动态代理的方式是使用 Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(), new TimeInvocationHandler(new Car()));首先需要一个classLoader,然后是一个class数组,最后是一个InvocationHandler的实现。然后将返回值进行一个强制转换。由于JDK的限制,能用于动态代理的必须实现了接口或者本身是接口,所以这里只能强制转换为接口类型而不能转换为具体的类型,当然了也就只能调用接口定义的方法了。

从CGLIB的使用中可以看到一句enhancer.setSuperclass(Train.class);说明最终在生成字节码的时候,其实是生成了被代理类的一个子类,所以在调用enhancer.create()时可以强转为Train。这也说明了为什么使用CGLIB代理的类不能是final的,因为final修饰的类是无法被继承的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值