代理设计模式与方法增强
来看一个基本的需求,有如一个类和一个接口:
public interface Sleep {
public void noonNap();
public void nightNap();
}
public class Person implements Sleep{
private String name;
public Person(String name) {
this.name = name;
}
public void noonNap(){
System.out.println("正在午睡");
}
public void nightNap(){
System.out.println("正在睡觉");
}
}
Sleep表示“睡觉”这一行为,Person类实现了这个接口。
现在如果告诉你在午睡和晚上睡觉之前都需要“脱衣”,在这之后需要“穿衣”,你会怎么做?
public class Person implements Sleep{
private String name;
public Person(String name) {
this.name = name;
}
public void noonNap(){
System.out.println("脱衣");
System.out.println("正在午睡");
System.out.println("穿衣");
}
public void nightNap(){
System.out.println("脱衣");
System.out.println("正在睡觉");
System.out.println("穿衣");
}
}
这便是对noonNap()和nightNap()方法的增强。
这样做虽然简单,但并不是每次都可行的,比如:
- Sleep接口中只有两个方法,如果接口中的方法很多,难道需要在每个方法体中都做修改?
- 如果类已经被编译并打包了,我们就无法对这个方法进行修改了。
为了解决这个问题,就需要用到代理设计模式:
public class PersonProxy {
private Person target;
public PersonProxy(Person target) {
this.target = target;
}
public void noonNap(){
System.out.println("脱衣");
target.noonNap();
System.out.println("穿衣");
}
public void nightNap(){
System.out.println("脱衣");
target.nightNap();
System.out.println("穿衣");
}
}
当使用Person对象时,将这个对象传入代理对象,通过调用代理对象实现增强。
这样做被称为静态代理,可以发现静态代理解决了第二个问题,对于第一个问题任然没有很好的解决,为了同时解决这两个问题就需要使用动态代理。
动态代理
Java提供了内置的动态代理可以帮我们解决上面这两个问题,解决步骤需要分为两步:
实现InvocationHandler接口
public class SleepHandler implements InvocationHandler {
private Person target;
public SleepHandler(Person target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("脱衣");
method.invoke(target,args);
System.out.println("穿衣");
return null;
}
}
这样做相当于告诉程序增强哪个对象,如何增强。它并不知道需要增强的是哪些方法。
Proxy创建代理实例
public class Test {
public static void main(String[] args) {
Person person = new Person("小明");
SleepHandler sleepHandler = new SleepHandler(person);
//注意需要转型
Sleep proxyInstance = (Sleep)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Sleep.class}, sleepHandler);
proxyInstance.nightNap();
proxyInstance.noonNap();
}
}
调用Proxy的静态方法,newProxyInstance(),其中第二个参数指定需要增强的接口。
这样一来就解决了上面提到的两个问题,但是这并不是最强大的解决方法,比如它增强的粒度只能精确到接口,并不能方便地增强某个不属于任何接口地方法。
CGLib
CGLib就可以解决上面的问题,下面来看以下对Person这个类做一些修改:
public class Person{
public void noonNap(){
System.out.println("正在午睡");
}
public void nightNap(){
System.out.println("正在睡觉");
}
}
Person这个类现在没有实现任何的接口,要想增强noonNap()和nightNap()使用Java的动态代理显然是做不到的。
为了使用CGLib,先要引入CGLib的第三方jar包:
!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
CGLib使用采用底层字节码技术,为目标类创建子类,在子类中采用方法拦截技术,拦截所有父类方法并织入增强:
public class CGLibProxy implements MethodInterceptor {
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;
}
}
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallback(new CGLibProxy());
Person person = (Person)enhancer.create();
person.nightNap();
person.noonNap();
}
}
总结
动态代理与CGLib各有优劣,CGLib执行效率较高,但创建代理对象比较消耗资源,Java的动态的代理则相反。在使用中根据需求择优使用,值得一提的是SpringAOP正式基于这两个技术实现的。
参考
《疯狂Java讲义》18.5
《精通spring4.x企业应用开发实战》第七章