Java代理的初步理解

代理的作用

在实际的项目中,往往有一些通用的功能需要穿插在项目功能代码的各个角落,比如很常见的log的记录,还有接口访问的权限检查。总结下来,这类的代码耦合,一种是预处理(信息过滤、权限校验),用来控制访问;另一种是其他通用功能的织入用于功能扩展。

使用代理的目的,就是解耦。把琐碎的事情交给代理类去做,让核心业务代码专注于业务,而无须关注那些通用功能的实现,且那些通用功能的代码也不会污染了核心的业务代码。经典的Spring Aop实际上就是代理的巧妙运用。

另外EasyMock这类方便unit test的工具,也是基于代理。也正是对EasyMock的好奇,才来了解一下java的代理。

java代理的实现方法

  1. 静态代理

  2. 动态代理

    • 静态代理的实现思路

      一个目标类(被代理类),一个代理类,这两个是核心,还有一个抽象接口,其实如果只是代理这个功能来说,抽象接口的意义是不明显的。使用了抽象接口,主要在于在面向接口编程时,代理类可以很优雅地取代目标类。

      首先说不使用抽象接口的。实现机制就是代理类持有目标类的对象,因为现在代理类持有了这个对象,就可以不直接使用目标类对象来invoke方法,而是调用代理类的某个方法,然后代理类方法间接地去invoke目标方法,这样的间接过程,就可以在目标方法的执行前后追加其他业务逻辑。 原本的Target.invoke()也变成了Proxy.invoke();

      代码:

//目标类
public class Real implements IService{
    @Override
    public void execute(){
        //do something
    }
}
  //代理类
  public class Proxy implements IService{
        private Real realObj;
        public Proxy(Real realObj){
            this.realObj = realObj;
        }
        @Override
        public void execute(){
            //do pre
            realObj.execute();
            //do after
        }
} 
//抽象类
public Interface IService{
  public void execute();
}

实际使用是将实体类传给代理的构造器,调用代理类的方法,就起到代理的作用。

public static void main(String []agrs){
  IService proxy = new Proxy(new Real());
  proxy.execute();  
}

  • 动态代理

JDK提供了动态代理的方法。动态代理的所谓动态,动的是代理类。动态代理和静态代理的区别,就是动态生成了代理类,免去了手工编写代理类的无趣。与静态相比,可以少写代码偷点懒。

动态代理的实现思路和静态是一致的。简要记录一下api的使用。

public static void main(String[] args) {
        IService real = new RealService();
        InvocationHandler handler = new MyInvocationHandler(real);
        IService proxy = (IService)Proxy.newProxyInstance(real.getClass().getClassLoader(),real.getClass().getInterfaces(),handler);
        proxy.execute();
    
    }

InvocationHandler是JDK定义的接口,是一种Callback接口,通过它传人的回调函数,代理类对象上的的所有方法调用都回到这个Callback中的可执行代码。使用InvocationHandler方法既可以创建类实现InvocationHandler接口,也可以使用匿名内部类。

动态代理的主要优点是,减少代码量,并且实现的Callback类可重用,但是此方法的局限是只能代理实现了接口的类,因为在Proxy.newProxyInstance()方法中,使用类加载器和目标类的接口在运行时生成了代理类。在编译后实际上可以发现一个包含*$*的class文件。

  • cglib——JDK动态代理的补全方案

cglib可以代理未实现接口的类。

CGLIB原理是动态生成一个目标类的子类,子类重写目标类的所有不是final的方法(因为final方法无法重写),在方法体中织入了回调函数的调用。

其使用方法是基于Enhancer类,Enhancer设置继承的父类(即为要代理的类),设置回调函数。现有的回调函数有:

  • net.sf.cglib.proxy.FixedValue, all method calls return a fixed value which is generated by the anonymous FixedValue implementation.
Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyClass.class);
        // FixedValue is a intercepter
        enhancer.setCallback(new FixedValue() {

            @Override
            public Object loadObject() throws Exception {
                System.out.println("FixedValue");
                return "hello,cglib";
            }

        });

        MyClass proxy = (MyClass) enhancer.create();
        proxy.dosome();
        proxy.execute();
        System.out.println(proxy.getClass());  //final method
//author said:
/*The anonymous subclass of FixedValue would become hardly referenced from the enhanced SampleClass such that neither the anonymous FixedValue instance or the class holding the @Test method would ever be garbage collected. This can introduce nasty memory leaks in your applications. Therefore, do not use non-static inner classes with cglib. (I only use them in this overview for keeping the examples short.)
*/
  • net.sf.cglib.proxy.InvocationHandler,和jdk中同款,它拦截所有方法到invoke(),和FixedValue类似,但是比前者要更灵活一点。在其内部处理要注意无限递归调用。
Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(MyClass.class);
    // net.sf.cglib.proxy.InvocationHandler is a intercepter
    enhancer.setCallback(new net.sf.cglib.proxy.InvocationHandler() {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("InvocationHandler");
            return null;
        }

    });

    MyClass proxy = (MyClass) enhancer.create();
    proxy.dosome();
    proxy.execute();
  • net.sf.cglib.proxy.MethodInterceptor,The MethodInterceptor allows full control over the intercepted method and offers some utilities for calling the method of the enhanced class in their original state.它的回调方法里proxy.invokeSuper(obj, args);可以通过父类调用其它方法,而不必全部都指向回调函数。
Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyClass.class);
        // MethodInterceptor
        enhancer.setCallback(new MethodInterceptor() {

            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
                    return "Hello cglib!";
                  } else {
                    return proxy.invokeSuper(method, args);
                  }
            }
        });

        MyClass proxy = (MyClass) enhancer.create();
        proxy.dosome();
        proxy.execute();
        proxy.hashCode();
        System.out.println(proxy.getClass());

这些都是既有的回调函数,需要更加的自定义,可以使用Enhancer#setCallbackFilter(CallbackHelper callbackHelper)方法自定义回调函数,callbackHelper返回回调函数对象,至于返回那种,就是可以在callbackHelper中自定义了,如对不同的方法,指定不同的回调函数。

Enhancer enhancer = new Enhancer();
CallbackHelper callbackHelper = new CallbackHelper(SampleClass.class, new Class[0]) {
    @Override
    protected Object getCallback(Method method) {
      if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return new FixedValue() {
          @Override
          public Object loadObject() throws Exception {
            return "Hello cglib!";
          };
        }
      } else {
        return NoOp.INSTANCE; // A singleton provided by NoOp.
      }
    }
  };
  enhancer.setSuperclass(MyClass.class);
  enhancer.setCallbackFilter(callbackHelper);
  enhancer.setCallbacks(callbackHelper.getCallbacks());

//code snippet via https://github.com/cglib/cglib/wiki/Tutorial

summary

java代理的实现机制,大概就是这两步:(1)创建目标类的可替代类;(2)织入回调方法。

回头去看静态代理,实际上就是代理实现的基础与核心。后面的动态代理,无论是JDK自带的还是CGLIB扩展的,无非就是在寻找方法取代手工编写可替代类的方法,并将整个过程“接口化”。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容