Java-动态代理与CGLib

代理设计模式与方法增强

来看一个基本的需求,有如一个类和一个接口:

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企业应用开发实战》第七章

Cglib及其基本使用

猜你喜欢

转载自blog.csdn.net/qq_34935078/article/details/87819600