2021-11-30 12:07:26
代理与装饰器
代理即使替代的意思,可替代所有的功能,即和原来的类实现相同的规范。
代理模式和装饰器模式很像。
基础实现
给你一个咖啡接口
public interface Coffee{
void printMaterial();
}
一个默认的咖啡实现
public class BitterCoffee implents Coffee{
@Override
public void printMaterial(){
System.out.println("咖啡");
}
}
默认的点餐逻辑
public class Main{ public static void main(String[] args){ Coffee coffee = new BitterCoffee(); coffee.printMaterial(); } }
装饰器模式
优雅的服务生把咖啡端上来了,抿了一口,有些苦。
想加点糖,对服务生说:您好,请为我加糖。
/**装饰器模式用来给咖啡加糖*/
public class SugarDecorator implements Coffee{
//持有咖啡对象
private final Coffee coffee;
public SugarDecorator(Coffee coffee){
this.coffee = coffee;
}
@Override
public void printMaterial(){
System.out.println("糖");
this.coffee.printMaterial();
}
}
然后服务生就拿走了咖啡,通过装饰器SugarDecorator加糖,最后端过来。
public class Main { public static void main(String[] args) { Coffee coffee = new BitterCoffee(); coffee = new SugarDecorator(coffee); coffee.printMaterial(); } }
注意这两行
Coffee coffee = new BitterCoffee(); // 点了一杯苦咖啡 coffee = new SugarDecorator(coffee); // 给咖啡加了点糖
装饰器模式适合什么场景,我有一个对象,但是这个对象的功能不能令我满意,我就拿装饰器给他装饰一下。
装饰器的入参和出参都是这个对象。
代理模式
周末了,又抱着ipad来到咖啡馆,准备享受一个宁静的下午。
先生,要喝点什么,一旁服务生礼貌询问。
上次的咖啡太苦了这次直接要个加糖的吧。
public class CoffeeWithSugar implements Coffee{
private final Coffee coffee;
public CoffeeWithSugar(){
this.coffee = new BitterCoffee();
}
@Override
public void printMaterial() {
System.out.println("糖");
this.coffee.printMaterial();
}
}
这是加糖咖啡,其实内部仍然是咖啡,只是加了些配方,产生了新类,一种新的品类。
点咖啡
public class Main { public static void main(String[] args) { Coffee coffee = new CoffeeWithSugar(); coffee.printMaterial(); } }
差别
故事讲完了,两者的区别是什么呢,两者实现的都是对原对象的包装,持有原对象的实例,差别是对外的表现。
装饰器模式:点了咖啡,发现太苦了,不是自己想要的,然后装饰器加点糖。
Coffee coffee = new BitterCoffee(); coffee = new SugarDecorator(coffee);
代理模式:直接点加糖的。
Coffee coffee = new CoffeeWithSugar();
很细微的差别。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
在装饰器模式中,持有的对象不是创建的,而是外部传入的。
AOP
设计模式是思想,代理模式不仅适用于接口便是和aop相关。
aop:Aspect Oriented Programming ,面向切面编程,是对面向对象编程的补充。、
我们会生成切面,即切在某方法之前、之后、前后,而springAop的实现就是代理模式。
场景
以短信验证码的例子。
public interface SMSService { void sendMessage(); } public class SMSServiceImpl implements SMSService { @Override public void sendMessage() { System.out.println("您正在执行重置密码操作,您的验证码为:2333,5分钟内有效,请不要将验证码转发给他人。"); } }
//主函数
public class Main {
public static void main(String[] args) {
SMSService smsService = new SMSServiceImpl();
smsService.sendMessage();
smsService.sendMessage();
}
}
费用统计
老板改了需求,发验证码需要花钱,老板想统计下看看在短信上一共花费多少钱。
正常按Spring
的思路,肯定是声明一个切面,来切发短信的方法,然后在切面内统计短信费用。
只是现在没有框架,也就是这道题:抛开Spring
来说,如何自己实现Spring AOP
?
写框架考虑的自然多些,我上文讲的代理是静态代理,编译期间就决定好的。而框架实现却是动态代理,需要在运行时生成代理对象,因为需要进行类扫描,看看哪些个类有切面需要生成代理对象
jdk动态代理
编写一个统计短信费用的类实现InvocationHandler
接口。
写到这,终于明白为什么每次后台Debug
的时候都会跳转到invoke
方法。
public class MoneyCountInvocationHandler implements InvocationHandler { /** * 被代理的目标 */ private final Object target; /** * 内部存储钱的总数 */ private Double moneyCount; public MoneyCountInvocationHandler(Object target) { this.target = target; this.moneyCount = 0.0; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(target, args); moneyCount += 0.07; System.out.println("发送短信成功,共花了:" + moneyCount + "元"); return result; } }
将主函数里的smsService
替换为使用MoneyCountInvocationHandler
处理的代理对象。
public class Main { public static void main(String[] args) { SMSService smsService = new SMSServiceImpl(); smsService = (SMSService) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{SMSService.class}, new MoneyCountInvocationHandler(smsService)); smsService.sendMessage(); smsService.sendMessage(); } }
根据InvocationHandler
中的invoke
方法动态生成一个类,该类实现SMSService
接口,代理对象,就是用这个类实例化的。
AOP实现
上面的都实现了?写一个AOP
是不是也不是难事?
主函数的代码,应该放在IOC
容器初始化中,扫描包,去看看哪些个类需要生成代理对象,然后构造代理对象到容器中。
然后在invoke
方法里,把统计费用的逻辑改成切面的逻辑不就好了吗?
jkd动态代理智能支持接口代理,通过实现接口,持有接口实现类的引用,动态扩展实现类的功能。
cglib动态代理
自古以来,从来都是时势造英雄,而不是英雄创造了时代。
出现了问题,自然会有英雄出来解决。拯救世界的就是cglib
。
JDK
动态代理解决不了的,统统交给cglib
。
不是使用接口注入的,JDK
动态代理解决不了。cglib
怎么解决的呢?它会根据当前的类,动态生成一个子类,在子类中织入切面逻辑。
然后使用子类对象代理父类对象。这就是为什么我上面说:代理模式,不要拘泥于接口。
所以织入成功的,都是子类能把父类覆盖的方法。
所以cglib
也不是万能的,方法是final
的,子类重写不了,它当然也无计可施了。
cglib是通过集成类,实现类的字类,重新父类的方法来实现静态代理
cglib继承被代理的类,重写方法,织入通知,动态生成字节码并运行,因为是继承所以final类是没有办法动态代理的。