设计模式--超详细 手动实现 jdk动态代理 原理(1)

前言

jdk动态代理想必大家都不陌生,它是面向切面编程(AOP)的理论基础,可以说当我们彻底搞清了jdk动态代理后,对aop的一些实际应用会有一个更深层次的理解,比如spring中的事务、日志管理、权限管理等。

我会通过几篇文章来由浅入深的讲解动态代理的实现,它是为了解决什么实际问题而产生的,其他方式(譬如静态代理)有什么缺陷和不足。通过不断的提出问题和对问题的完善,来一步步的模拟实现jdk的动态代理,而不是一上来就告诉大家jdk的Proxy是如何使用。通过这种方式,我们可以站在一个设计者的角度去一步步解决实际遇到的问题,从而找出一个最优雅的解决方式,这样也能加深我们对设计模式的理解,而不是背很多的UML类图。

由于后边这一系列文章中的代码是由简单到复杂慢慢演化的,因此我希望你在看过之后能按照我的思路自己敲一遍。当你能坚持看到最后你会发现,虽然大家都说动态代理是最难的设计模式,但其实不过如此。

学习jdk动态代理这个系列时,你需要掌握:

  • java面向对象的思想(特别是多态)
  • 会使用java.io对文件进行读写
  • 简单了解classLoader(知道是干什么的即可)
  • java反射(了解、会使用即可)

本篇是第一篇,主要是讲解代理的概念,以及最简单的代理方式---静态代理,通过了解静态代理的缺陷和不足从而引出动态代理的必要性。

 

静态代理

在学习动态代理之前,我们先要了解最简单的静态代理,那什么又是静态代理?

我们先从什么是代理说起,顾名思义,所谓代理就是委托人(即被代理人)请代理人去做一些自己并不擅长的事情。比如说我想卖房子的时候我会找房产代理去为我搞定一切的手续,虽然我自己也能做,但是毕竟人家是专业的而我不是,所以我要付出更多的成本,那不如交给更专业的房产代理来做,而我去敲自己的代码;再比如说当下非常火的微商,大家知道这些货从工厂生产出来会给到代理商手里,然后代理商便会通过微信朋友圈去卖货,并不是说工厂不能自己卖,但如果自己既要生产又要维护微信朋友圈,要付出的精力实在太大,因此不如将专业的事情交给专业的人去做,这样大家各司其职,从而提高整体的效率。

上述的例子在生活中比比皆是,当然在程序中也会存在这样的问题,比如我们在写Service中的业务逻辑时,如何去控制事务的提交和回滚,如何去控制日志的输出。其实这些东西和我们的业务逻辑并没有什么关系,它应该可以单独的应用于每个Service中,那么根据封装的思想它就应该被抽出来,但这些东西怎么实现并不是开发人员首要关心的,我们更关心的是符合什么规则的情况下才能给用户退款(业务逻辑),即上面所说的让专业的人做专业的事,大家各司其职。

举一个具体的例子:

/**
 * 所有实现了此接口的类都可以移动。
 */
public interface Movable {
	void move();
}
/**
 * 实现了Movable接口的汽车
 */
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();
		}
	}
}

上面我们编写了一个汽车的类,并且实现了汽车的业务逻辑,如果我想在不改变的原有业务逻辑的情况下,计算Car的move()方法的执行时间应该怎么做呢?

很简单,可以通过继承的方式来实现:

/**
 * 调用父类Car的move方法,在调用前后加上记录时间的逻辑。
 */
public class TimeCar extends Car {
	@Override
	public void move() {
		long start = System.currentTimeMillis();
		super.move();
		long end = System.currentTimeMillis();
		System.out.println("move time is " + (end - start) + "ms");
	}
}

另外,想在上面的基础上,在方法前后记录日志,因此我们要继续继承TimeCar:

/**
 * 和记录时间的原理一样,我们在父类的调用前后加上记录日志的逻辑
 */
public class LoggerCar extends TimeCar{
	@Override
	public void move() {
		System.out.println("logger start!");
		super.move();
		System.out.println("logger end!!!");
	}
}

至此,我们完成了对Car原始move方法的两个增强,记录时间和记录日志,通过Main测试结果:

public class Main {
	public static void main(String[] args) {
		Movable powerfulCar = new LoggerCar();
		powerfulCar.move();
	}
}
logger start!
Car is moving....
move time is 9930ms
logger end!!!

上面一切都看起来很顺利,但是此时需求变更了,要求我们先记录日志,再记录时间。如果依然使用这种继承的方式,我们就要先创建LoggerCar去继承Car,然后再创建TimeCar去继承LoggerCar。

目前继承链只有2层,改起来好像并不复杂,但是我们试想一下,如果有100层那简直就是噩梦。因此通过继承的方式来实现对原有方法的增强就被我们否定了,这个例子也很好的解释了在设计模式中有一条原则是少用继承,因为继承的扩展性极差,对于需求变更较多的地方,一定要慎用继承来进行设计。

说到这里,各位可能已经想到了,既然继承无法灵活的满足需求,那我们可以用组合。没错,这也是上边说到的设计模式其中一个重要原则的下半句:少用继承,多用组合

我们使用组合的方式对上述代码进行重构:

/**
 * description:可以对任意实现了Movable接口的类代理日志的记录。
 */
public class MovableLogProxy implements Movable {
	
	private Movable movableThing;
	
    /*
     * 我们在这里聚合一个可移动的东西,不管它是汽车或者火车,
     * 只要其可移动(即实现了Moveable接口)便可以聚合进来。
     */
	public MovableLogProxy(Movable movableThing) {
		this.movableThing = movableThing;
	}
	
	/*
	 * 在我们实现本类的move方法的时候,重点不在于我们去如何实现move的细节,这不是我们要做的,
	 * 我们在这里是直接使用聚合进来的那个可移动的东西的move方法的实现。
	 * 而我们要做的是在它的move方法前后加上日志的记录,这就是所谓代理的意思。
	 */
	@Override
	public void move() {
		System.out.println("logger is start!");
		movableThing.move();
		System.out.println("logger is end!");
	}
}
/**
 * description: 可以对任意实现了Movable接口的类代理时间的记录。
 */
public class MovableTimeProxy implements Movable{ 
	
	private Movable moveableThing;
	
	public TimeProxyTank(Movable moveableThing) {
		this.moveableThing = moveableThing;

	@Override
	public void move() {
		long start = System.currentTimeMillis();
		moveableThing.move();
		long end = System.currentTimeMillis();
		System.out.println("move time is: " + (end - start) + "ms");
	}
}

重构完成,通过Main方法测试:

public class Main {
	public static void main(String[] args) {

		Car car = new Car();
		
		// 1. 先记录日志再记录时间
//		MovableLogProxy logCar = new MovableLogProxy(car);
//		MovableTimeProxy timeCar = new MovableTimeProxy(logCar);
//		timeCar.move();
		
		// 2. 先记录时间再记录日志
		MovableTimeProxy timeCar = new MovableTimeProxy(car);
		MovableLogProxy logCar = new MovableLogProxy(timeCar);
		logCar.move();
	}
}

运行结果:

// 1.先记录日志再记录时间
logger is start!
car is moving....
logger is end!
move time is: 9859ms

// 2.先记录时间再记录日志
logger is start!
car is moving....
move time is: 5720ms
logger is end!

好了,这种实现方式是不是比继承的方式灵活了很多,同时我们也通过这个例子在不经意间讲完了静态代理。

静态代理其实很简单,它就是通过组合的方式对某一类型的接口(这里是Movable)的方法进行增强,让专业的类在不改变原有逻辑的基础上去做自己专业的事(LogProxy在原有的方法之上进行日志记录、TimeProxy在原有的方法上进行时间的记录),而我们做自己擅长的业务逻辑(即move方法的具体实现)。

但是静态代理依然有不尽如人意的地方,现实中需求不可能像例子这么简单,那么现在我们面临最棘手的问题是,例子中我们仅能对Movable类型进行代理,而现实中一个简单的项目就可能有成百上千个不同的接口,可能还有Flyable、Runable、Jumpable我们怎么对这么多的类型都实现代理?按照上面说的静态代理的实现方式,我们要分别实现FlyableLogProxy、FlyableTimeProxy、RunableLogProxy、RunableTimeProxy?这显然是不现实的,我们发现其实不管Movable也好、Flyable也好,对于记录时间的代码是一样的,既然如此,我们就要考虑能不能通过某种通用的方式来实现对所有类型进行代理,而不仅仅是Movable。

下一篇,我们正式进入动态代理。这里说明一下,如果了解设计模式的小伙伴肯定发现了,静态代理和装饰模式很像,这里我们讲静态代理只是为了引出动态代理,它俩的区别不必深究,因为某些时候设计模式更多的是语义上的区别而不是语法。

你可能感兴趣的:(java,设计模式)