为了防止被“杀”了祭天,学点设计模式,并总结下还是有必要的。
一:理解
- 代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。——《Head First设计模式》
- 代理模式可以使得服务提供方只需关注业务逻辑本身,通过代理对业务逻辑进行增强。
二:例子
你是个富二代。
你开了很多厂。至于为什么开厂不开公司,因为你享受别人喊你厂长这个称谓。
你手下有很多员工,为了方便,这些员工只有work一个方法,用Staff接口进行约束。
// 员工接口
public interface Staff {
void work();
}
其中汽车厂员工日常工作的内容为:
- 售前服务!
- 生产汽车!
- 售后服务!
代码如下:
public class CarFactoryStaff implements Staff {
@Override
public void work() {
System.out.println("售前服务!");
System.out.println("生产汽车!");
System.out.println("售后服务");
}
}
你发现,汽车厂员工都比较擅长生产汽车,不是很擅长售前和售后服务,导致汽车厂的效率不是很高。
不只是汽车厂,其他厂的员工也都是全能人才,需要负责销售和生产。
你苦于如何才能提高工厂的效率。
于是,你叫来了程序员小菜帮忙。
1. 静态代理
小菜觉得你其实已经意识到了问题所在。
员工的日常工作分为销售和生产两块,如果可以找来专业的销售人员,工厂员工专注于生产汽车,工厂的效率肯定会提高。
小菜上来就是一顿敲,抽象出销售人员类:
@Data
public class CarSalesman implements Staff {
private CarFactoryStaff carFactoryStaff;
public CarSalesman(CarFactoryStaff carFactoryStaff) {
this.carFactoryStaff = carFactoryStaff;
}
@Override
public void work() {
System.out.println("售前服务!");
carFactoryStaff.work();
System.out.println("售后服务!");
}
}
销售人员也是你的员工,所以实现Staff类。
在work方法中,销售人员接管了销售工作,而将生产工作交给生产工人。
该调用通过持有CarFactoryStaff属性完成。
在这里,销售人员就实现了对工厂员工的代理。
可以看出,一个工厂的员工需要配置不同的销售,如汽车厂的员工配合汽车销售,火箭厂的员工配合火箭销售。
该模式称为静态代理模式。
// 火箭厂员工类
public class RocketFactoryStaff implements Staff {
@Override
public void work() {
System.out.println("生产火箭!");
}
}
你每开一个厂,就需要招一批新的销售。
然而,你发现不同的销售做的事情其实差不多,完全可以在多个厂之间流动。
于是,小菜开始重构。
2. 动态代理
小菜想到了动态代理模式,在该模式下,销售人员可以配合任何的工厂员工进行工作。
动态代理在工厂员工基础上动态生成一个类,并在执行work方法时进行增强。
动态代理可分为JDK动态代理和CGLib动态代理。
JDK动态代理
在该模式下,销售人员可以代理任何类,包含的staff属性被申明为Object类。
public class Salesman implements InvocationHandler {
private Object staff;
public Object getProxy(Object target) {
this.staff = target;
//取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("售前服务!");
result = method.invoke(staff, args);
System.out.println("售后服务!");
return result;
}
}
在getProxy方法中,参数为需要被代理的类的对象。
通过被代理类的ClassLoader,按照被代理类的接口,动态生成一个类。
newProxyInstance方法中的三个参数的含义分别是:
- 用这个ClassLoader加载生成的类。
- 按照这些接口生成类。
- 生成的类加入这些增强。
动态生成的类的方法在被调用时候,会调用invoke方法。
该方法的参数包括原调用方法的方法名和参数,通过反射对原方法进行调用,并在前后做增强。
JDK动态代理需要根据被代理类的接口动态生成新的类,所有被代理类必须实现接口。
测试代码:
public class ClientV2 {
public static void main(String[] args) {
Salesman salesman = new Salesman();
Staff staff = (Staff) salesman.getProxy(new CarFactoryStaff());
staff.work();
Staff staff1 = (Staff) salesman.getProxy(new RocketFactoryStaff());
staff1.work();
}
}
输入/输出:
售前服务!
生产汽车!
售后服务!
售前服务!
生产火箭!
售后服务!
CGLib动态代理
为了解决JDK动态代理必须实现接口的限制。
CGLib通过字节码技术,生成委托类(被代理类)的子类。
小菜又是一顿敲:
public class SalesmanV2 implements MethodInterceptor {
public Object getProxy(Class clazz) {
Enhancer enhancer = new Enhancer();
// 设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 通过字节码技术动态创建子类实例
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("售前服务!");
// 通过代理类调用父类中的方法
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("售后服务!");
return result;
}
}
在CGLib动态代理模式中,动态生成的类的方法在被调用时候,会调用intercept方法,其参数和JDK动态代理模式中的invoke方法类似。
通过动态代理模式,销售人员可以在多个工厂之间轮岗了。
你旗下工厂的效率也变高了,需要的销售人员数量也随之减少。
于是,你炒了一批销售。
并且,考虑在程序员中也使用代理模式,炒掉一部分程序员。
小菜感到略绝望。
三:再理解
- Spring中大量用到了动态代理模式,保证服务提供者只需要关注业务逻辑。
- 动态代理依赖于反射技术,在知道方法名和参数的情况下,对方法进行调用。
- 两种动态代理模式都略有缺陷,JDK代理模式需要被代理类实现接口,CGLib通过生成被代理类的子类来实现代理,代理类不能被申明为final。