静态代理


该博文非原创,大部分抄录自zhihu用户:bravo1988的原创文章。主要用于本人学习梳理。

主要内容:

  • 一个小需求:给原有方法添加日志打印
  • 静态代理实现日志打印
  • 静态代理的缺陷

1.一个小需求:给原有方法添加日志打印功能

假设现在我们有一个类Calculator,代表一个计算器,他而已进行加减乘除的操作。
现在有一个小需求:在每个方法执行前后打印日志。你有什么方案吗?

public class Calculator {

	//加
	public int add(int a, int b) {
		int result = a + b;
		return result;
	}

	//减
	public int subtract(int a, int b) {
		int result = a - b;
		return result;
	}

	//乘法、除法...
}
  • 1.直接修改

    最直观的想法就是直接修改Calculator类:

public class Calculator {

	//加
	public int add(int a, int b) {
		System.out.println("add方法开始...");
		int result = a + b;
		System.out.println("add方法结束...");
		return result;
	}

	//减
	public int subtract(int a, int b) {
		System.out.println("subtract方法开始...");
		int result = a - b;
		System.out.println("subtract方法结束...");
		return result;
	}

	//乘法、除法...
}

这种方案的缺点:

1.直接修改源程序,不符合开闭原则。应该对扩展开放,对修改关闭。
2.如果Calculator有几十个,上百个方法,修改量太大了。
3.存在大量重复代码(都在核心代码前后打印日志)。
4.日志打印硬编码在代理类中,不利于后期修改维护:比如你花了一上午终于写完了,组长告诉你这个功能取消,于是你又要打开Calculator花十分钟删除日志打印的代码。

所以,此方案PASS。

2.静态代理实现日志打印

静态代理这四个字包含了两个概念:静态、代理。我们先来了解什么叫“代理”,至于何为“静态”,需要和“动态”对比着讲。

  • 代理:代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,如前拦截,后拦截等,以满足自身的业务需求。

  • 即在访问目标对象时,会先被代理对象拦截到,先访问代理对象,然后在此处可以做一些额外的功能。之后在通过代理对象访问目标对象,执行目标对象中原本的操作,并返回结果。在将结果返回给访问者时,又会被代理对象拦截到,我们可以对这个结果进行再加工,或者增加一些其他操作,最后一并返回给访问者。
    在这里插入图片描述

  • 常用的代理方式可以粗分为:静态代理和动态代理。

    静态代理的实现比较简单:编写一个代理类,实现与目标对象相同的接口,并在内部维护一个目标对象的引用作为代理对象的属性。通过有参构造方法,初始化这个属性。然后在通过这个属性可以访问到目标对象中的属性,方法。并可以在这个操作的前后添加所需的业务功能。

    代理类要和目标类实现同一个接口,所以对上面的Calculator进行修改:

    • 将Calculator抽取为接口
    • 创建目标类CalculatorImpl实现Calculator
    • 创建代理类CalculatorProxy实现Calculator
  • 1.Calculator接口

/**
 * Calculator接口
 */
public interface Calculator {
	int add(int a, int b);
	int subtract(int a, int b);
}
  • 2.CalculatorImpl实现类:目标对象实现类
/**
 * 目标对象实现类,实现Calculator接口
 */
public class CalculatorImpl implements Calculator {

	//加
	public int add(int a, int b) {
		int result = a + b;
		return result;
	}

	//减
	public int subtract(int a, int b) {
		int result = a - b;
		return result;
	}

	//乘法、除法...
}
  • 3.CalculatorProxy:代理对象实现类
/**
 * 代理对象实现类,实现Calculator接口
 */
public class CalculatorProxy implements Calculator {
        //代理对象内部维护一个目标对象引用
	private Calculator target;
        
        //构造方法,传入目标对象
	public CalculatorProxy(Calculator target) {
		this.target = target;
	}

        //调用目标对象的add,并在前后打印日志
	@Override
	public int add(int a, int b) {
		System.out.println("add方法开始...");
		int result = target.add(a, b);
		System.out.println("add方法结束...");
		return result;
	}

        //调用目标对象的subtract,并在前后打印日志
	@Override
	public int subtract(int a, int b) {
		System.out.println("subtract方法开始...");
		int result = target.subtract(a, b);
		System.out.println("subtract方法结束...");
		return result;
	}

	//乘法、除法...
}
  • 4.使用代理对象完成加减乘除,并且打印日志

注意要通过目标对象的实例对象作为参数,来构建一个代理对象。

public class Test {
	public static void main(String[] args) {
		//把目标对象通过构造器塞入代理对象
		Calculator calculator = new CalculatorProxy(new CalculatorImpl());
		//代理对象调用目标对象方法完成计算,并在前后打印日志
		calculator.add(1, 2);
		calculator.subtract(2, 1);
	}
}  

3.静态代理的缺陷

  • 静态代理的优点:可以在不修改目标对象源码的前提下,对目标对象进行功能的扩展和拦截。但是它也仅仅解决了方案一的这一个缺点

    • 1.直接修改源程序,不符合开闭原则。应该对扩展开放,对修改关闭。✔
    • 2.如果Calculator有几十个,上百个方法,修改量太大了。✖
    • 3.存在大量重复代码(都在核心代码前后打印日志)。✖
    • 4.日志打印硬编码在代理类中,不利于后期修改维护。✖
  • 静态代理的缺陷:

    上面案例中,代理类是我们事先编写的,而且要和目标对象类实现相同的接口。我们编写的静态代理类,由于它维系着一个特定的引用属性private Calculator target;,且构造方法要传一个目标对象Calculator的实现类。

    所以这个代理类CalculatorProxy只能代理这一个接口Calculator,它们绑定死了。

    如果现在我们系统需要全面改造,给其他的类都添加一个相同的功能,那么如果用静态代理的话,就要为所有的接口都各自写一份代理类…

  • 我们想要的。

    仔细想一想,我们想要的并不是代理类,而是代理对象。那么有没有一个方法,或者抽取出来一个方法。我们传入接口,它就自动返回代理对象呢?
    在这里插入图片描述

    那么我们就需要看一下JDK的动态代理机制了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值