Spring AOP基础:代理模式

代理模式

代理模式:在对已有方法进行改进时,不直接修改原有方法或代码,而是提供第三方。该第三方含有代理角色的引用,并根据需求增加相应的功能。

代理模式在程序设计中有非常重要的应用,AOP就是针对代理的一种应用。在生活中代理也无处不在,比如翻墙,比如开代理打韩服等等。
学习代理模式,先从简单的代码开始。

先定义一个简单的接口
public interface Hello{
     void sayHello();
}
再实现一个基本的Hello实现
public class HelloImpl implements Hello{
    @override
    public void sayHello{
        System.out.println("Hello");
    }
}

如果要在println方法前分别需要一些处理逻辑,该怎么么做呢?如果是以前的我,我会直接将处理逻辑写死在在HelloImpl的sayHello方法中。但是这样的写法在大多数情况下是不妥的,就好像没学面向对象以前,将所有的方法逻辑写在一个类里面。这样的写法,会使得一个方法越来越长,处理逻辑的实现也会将类变得越来越庞大,而且这样处理逻辑可能与该类的其他方法无关。所以最好的方法是将这些处理逻辑交于代理处理。

对于我们的HelloImpl类,我们写一个HelloProxy类,让该类调用HelloImpl的sayHello方法,并在调用的前后进行相应的逻辑处理。
public class HelloProxy implements Hello{
        Hello helloImpl;
        HelloProxy(){
            helloImpl=new HelloImpl();
        }

        @Override
        public void sayHello(){
            before();
            helloImpl.sayHello();
            after();
        }
        private void before(){
            System.out.println("before");
        }
        public void after(){
            System.out.println("after");
        }
}

在这段代码中,我们用HelloProxy实现Hello接口(和HelloImpl实现相同的接口),并且在构造方法中new出HelloImpl实例,则我们可以在HelloProxy的sayHello()方法中调用HelloImpl的sayHello()方法。这样的话,我们就可以在调用前后添加相应的before和after方法,并在这两个方法中去实现相应的前后处理逻辑。

我们再用main方法测试一下
    public static void main(String args[]){
            HelloProxy helloProxy=new HelloProxy();
            helloProxy.sayHello();
    }
结果如下
before
hello
after

这个HelloProxy就是简单的代理。这样的代理称为静态代理。所谓静态,就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
这样的静态代理几个缺点:
1. 如果要代理的方法很多,那么静态代理类的规模也势必很大。
2. 如果接口增加一个方法,那么所有代理类也必须实现此方法,如此就增加了代码维护的复杂度

为了解决静态代理的问题,我们使用JDK提供的动态代理方案DynamicProxy:

public class DynamicProxy  implements InvocationHandler{

    private Object object;
    public DynamicProxy(Object object) {
        this.object=object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
        before();
        Object result=method.invoke(object, args);
        after();
        return result;
    }
    private void before(){
        System.out.println("before");
    }
    private void after(){
        System.out.println("after");
    }
}

DynamicProxy实现了InvocationHandler接口,那么必须实现invoke方法,在invoke方法中,直接通过反射去调用被包装类的方法,在调用前后实现 分别处理before和after,最后将result返回。

在main中测试:
public static void main(String args[]){
        Hello hello=new HelloImpl();
        DynamicProxy dynamicProxy=new DynamicProxy(hello);
        Hello helloProxy=(Hello) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), dynamicProxy);
        helloProxy.sayHello();
    }
结果如下:
before
hello
after

在上述代码中,我们用这个通用的DynamicProxy类去包装HelloImpl实例,然后调用JDK给我们提供的Proxy类工厂方法newProxyInstance去动态的创建一个Hello接口的代理类,最后调用这个代理类的sayHello方法。结果和静态代理结果一样。
其实,动态代理就是动态生成XxxProxy。和静态代理相比,动态代理类的源码是在程序运行期间由JVM根据反射等机制动态生成的。
代理类和委托类的关系是在程序运行时确定的,所有如果需要在不同类的不同方法增加相同的处理逻辑,我们只需要一个动态代理类就可以实现,这就是动态代理的优点。
在main方法中我们可以看到,Proxy.newProxyInstance方法要有三个参数:
1. ClassLoader;
2. 该实现类的所有接口
3. 动态代理对象

在调用结束后还需要类型的强制转换。如果我们的动态代理需要代理多个对象,那么上述代码需要重复多次。

为了避免Proxy.newInstance方法出现多次(减少代码量和简化动态代理类的使用),我们将动态代理对象实例化部分代码封装起来(使用泛型):
    @SuppressWarnings("unchecked")
    public <T> T getProxy(){        
        return (T)Proxy.newProxyInstance(object.getClass().getClassLoader(), 
                object.getClass().getInterfaces(), this);
    }
如此一来,我们的DynamicProxy的使用将更加方便,代码也看起来简洁清晰多了:
public static void main(String args[]){
        DynamicProxy dynamicProxy=new DynamicProxy(new HelloImpl());
        Hello helloProxy=dynamicProxy.getProxy();
        helloProxy.sayHello();
    }

除了减少代理类这个优点,相对于静态代理,动态代理在接口变化的时候,代理类不需要变化,而静态代理类则需要相对应的改动。
但是,我们发现,上述JDK动态代理只能代理有接口的类,如果需要代理的类没有接口,那么JDK动态代理就无用武之地。

为了解决代理没有接口的类的问题,我们使用开源的CGLIB类库。
public class CGLIBProxy implements  MethodInterceptor{
    private static CGLIBProxy instance=new CGLIBProxy();
    private CGLIBProxy() {
    }

    public static CGLIBProxy getInstance(){
        return instance;
    }
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T>cls){
        return (T)Enhancer.create(cls, this);
    }
    @Override
    public Object intercept(Object target, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        before();
        Object result=proxy.invokeSuper(target, args);
        after();
        return result;
    }
    private void before(){
        System.out.println("before");
    }
    private void after(){
        System.out.println("after");
    }
}

从上述代码可以看出,CGLIB类库的使用和JDK动态代理类似。但是CGLIB类库可以代理没有接口的类,且速度相较于JDK DynamicProxy更快。

测试使用及运行结果如下:
public class Greet {
    public void say(String str){
        System.out.println("hello"+str);
    }
    public static void main(String args[]){
        Greet greetProxy=CGLIBProxy.getInstance().getProxy(Greet.class);
        greetProxy.say("shan");
    }
}
运行结果:
before
helloshan
after
至此,代理模式基本内容也就介绍完毕。AOP是基于JDK DynamicProxy和CGLIB代理,但是又不仅限于这些。要学好spring,AOP是重中之重。而代理又是AOP的基石,所以对代理理解并学以致用才是王道。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值