JAVA静态代理和动态代理

2018-05-31  本文已影响89人  hu1991die

一、概述

代理是一种模式,提供了对目标对象的间接访问方式。通常是用户可能出于某些原因不能直接访问目标对象,而此时通过在用户和目标对象之间建立一个桥梁(即代理)从而达到了间接访问目标对象的目的。一般是由用户先去访问代理对象,而代理对象再去访问目标对象这样的一种模式,我们称作代理模式。

代理模式.png

二、应用场景

比如:玩游戏时的代理服务器,翻墙用的VPN等等,都是代理模式的一种应用体现。

三、分类

代理有正向代理和反向代理,其实本质是一样的,只是使用者站在的角度不同,而其本身在概念上来讲是相对的,比如客户端(client)通过代理服务器(proxy)去访问服务器(server),我们即可以说这个代理服务器(proxy)属于正向代理,也可以说它是属于反向代理。为什么呢?因为站在客户端来看,proxy对于client来说就属于正向代理,而站在服务端来看,proxy对于server来讲就属于反向代理了。

四、JAVA中的代理

上面是站在使用的角度说明的,而通常在JAVA中则是站在实现的方式来分类的,一般分为:静态代理动态代理。动态代理又可以进一步划分为JDK动态代理CGLIB动态代理。而我们众所周知的Spring框架其强大的AOP功能就是基于这两种动态代理方式实现的。

有了代理,那么对于一个JAVA程序来说,很多功能就可以很方便地实现了,比如在目标实现的基础上增加额外的功能操作,比如:做前后拦截日志记录调用耗时事务控制等等,以满足自身的业务需求,同时代理模式的便于扩展目标对象功能的特点也使得它越来越被多数人所接受使用。

4.1、静态代理

静态代理的实现相对而言比较简单,代理类通过实现与目标对象相同的接口或者继承自相同的父类,而先前由于直接调用目标类改为调用代理类,然后在代理类中去调用目标对象,进而就可以在代理类中做一些额外的增强操作以达到代理的目的。

ok, show me the code.

Hello.java接口

package com.feizi.demo;

/**
 * Created by feizi on 2018/1/11.
 */
public interface Hello {
    String sayHello(String str);
}

HelloImpl.java接口实现类:

package com.feizi.demo.impl;

import com.feizi.demo.Hello;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by feizi on 2018/1/11.
 */
public class HelloImpl implements Hello {
    private static final Logger LOGGER = LoggerFactory.getLogger(HelloImpl.class);

    @Override
    public String sayHello(String str) {
        //真正执行的目标方法
        LOGGER.info("execute real method...");
        return "HelloImpl: " + str;
    }
}

StaticProxyHello.java静态代理方式:

package com.feizi.proxy.staticed;

import com.feizi.demo.Hello;
import com.feizi.demo.impl.HelloImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 静态代理方式
 * Created by feizi on 2018/1/11.
 */
public class StaticProxyHello implements Hello{
    private static final Logger LOGGER = LoggerFactory.getLogger(StaticProxyHello.class);

    private Hello hello = new HelloImpl();

    @Override
    public String sayHello(String str) {
        LOGGER.info("You said: " + str);

        //调用前
        LOGGER.info("before invoking staticProxy...");
        String result = hello.sayHello(str);
        //调用后
        LOGGER.info("after invoking staticProxy...");

        return result;
    }
}

testStaticedProxy()测试静态代理:

public class App {
    private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

    public static void main( String[] args ){
        testStaticedProxy();
        //  testDynamicProxy();
        //  testCglibProxy();
    }

    /**
    * 测试静态代理
    */
    public static void testStaticedProxy(){
        StaticProxyHello staticProxyHello = new StaticProxyHello();
        String content = staticProxyHello.sayHello("feizi");

        LOGGER.info("content: " + content);
    }
}

控制输出结果:

2018-05-31 16:48:32.749 [main] INFO  com.feizi.proxy.staticed.StaticProxyHello - You said: feizi
2018-05-31 16:48:32.751 [main] INFO  com.feizi.proxy.staticed.StaticProxyHello - before invoking staticProxy...
2018-05-31 16:48:32.751 [main] INFO  com.feizi.demo.impl.HelloImpl - execute real method...
2018-05-31 16:48:32.751 [main] INFO  com.feizi.proxy.staticed.StaticProxyHello - after invoking staticProxy...
2018-05-31 16:48:32.751 [main] INFO  com.feizi.App - content: HelloImpl: feizi

Process finished with exit code 0

静态代理的总结:

  1. 优点:可以做到不修改目标对象的情况下,对目标对象进行一些功能的扩展和拦截
  2. 缺点:不够灵活,因为代理对象,需要实现与目标对象一样的接口,会导致代理类十分繁多,不易维护,同时一旦接口增加方法,则目标对象和代理类都需要维护。

4.2、JDK动态代理

顾名思义,JDK动态代理是JDK本身提供的一种代理扩展,其思维模式同静态代理类似,创建代理类将具体类隐藏解耦,不同之处在于代理类的创建时机不同,动态代理需要在运行时根据需要而动态的创建。其实现方式需要我们实现InvocationHandler接口,之后通过使用Proxy.newProxyInstance()方法来产生代理对象。

LogInvocationHandler.java核心类:

package com.feizi.proxy.dynamic;

import com.feizi.demo.Hello;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * JAVA动态代理
 * Created by feizi on 2018/1/11.
 */
public class LogInvocationHandler implements InvocationHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogInvocationHandler.class);

    private Hello hello;

    public LogInvocationHandler(Hello hello) {
        this.hello = hello;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1. 首先实现一个InvocationHandler,方法调用会被转发到该类的invoke()方法。
        if("sayHello".equals(method.getName())){
            LOGGER.info("You said: " + Arrays.toString(args));
        }

        //调用前
        LOGGER.info("before invoking dynamicProxy...");
        Object object = method.invoke(hello, args);
        //调用后
        LOGGER.info("after invoking dynamicProxy...");

        return object;
    }
}

testDynamicProxy()测试JDK动态代理,注意,这里需要我们调用Proxy.newProxyInstance()方法先new一个代理实例(通过反射实现)。

public class App {
    private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

    public static void main( String[] args ){
        //testStaticedProxy();
        testDynamicProxy();
//        testCglibProxy();
    }

    /**
     * 测试Java动态代理
     */
    public static void testDynamicProxy(){
        //2. 然后在需要使用Hello的时候,通过JDK动态代理获取Hello的代理对象。
        Hello hello = (Hello) Proxy.newProxyInstance(
                //1、类加载器
                App.class.getClassLoader(),
                //2、代理需要实现的接口,可以有多个
                new Class[]{Hello.class},
                //3、方法调用的实际处理者
                new LogInvocationHandler(new HelloImpl()));

        String content = hello.sayHello("feizi");
        LOGGER.info(content);
    }
}

控制台输出结果:

2018-05-31 17:00:04.275 [main] INFO  com.feizi.proxy.dynamic.LogInvocationHandler - You said: [feizi]
2018-05-31 17:00:04.277 [main] INFO  com.feizi.proxy.dynamic.LogInvocationHandler - before invoking dynamicProxy...
2018-05-31 17:00:04.278 [main] INFO  com.feizi.demo.impl.HelloImpl - execute real method...
2018-05-31 17:00:04.278 [main] INFO  com.feizi.proxy.dynamic.LogInvocationHandler - after invoking dynamicProxy...
2018-05-31 17:00:04.278 [main] INFO  com.feizi.App - HelloImpl: feizi

JDK动态代理的总结:

  1. 优点:比较灵活,可以在运行时根据需要动态的创建代理对象,比较容易扩展
  2. 缺点:针对接口实现,有其局限性,代理对象不需要实现接口,但是目标对象一定要实现接口,否则无法使用JDK动态代理

4.3、Cglib动态代理

因为上述已经说了,JDK动态代理拥有局限性,要求必须面向接口编程,如果没有接口就无法实现代理,这样一来我们想实现代理还需要强行定义一些毫无意义的接口,没有必要。这时我们就可以使用Cglib代理,这种依靠继承来实现动态代理的方式,不再要求我们必须要有接口。Cglib代理要求需要实现MethodInterceptor接口,并且需要指定继承的目标类,之后设置回调,通过enchancer.create()来创建代理对象。

HelloConcrete.java类:

package com.feizi.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by feizi on 2018/1/11.
 */
public class HelloConcrete {
    private static final Logger LOGGER = LoggerFactory.getLogger(HelloConcrete.class);

    public String sayHello(String str){
        LOGGER.info("execute real method...");
        return "HelloConcrete: " + str;
    }
}

MyMethodInterceptor.java类:

package com.feizi.proxy.cglib;

import org.assertj.core.internal.cglib.proxy.MethodInterceptor;
import org.assertj.core.internal.cglib.proxy.MethodProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * CGLIB动态代理
 * Created by feizi on 2018/1/11.
 */
public class MyMethodInterceptor implements MethodInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyMethodInterceptor.class);

    /**
     * 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
     * @param obj
     * @param method
     * @param args
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        LOGGER.info("You said: " + Arrays.toString(args));

        //调用前
        LOGGER.info("before invoking cglibProxy...");
        Object object = methodProxy.invokeSuper(obj, args);
        //调用后
        LOGGER.info("after invoking cglibProxy...");

        return object;
    }
}

testCglibProxy()测试Cglib动态代理:

package com.feizi;

import com.feizi.demo.Hello;
import com.feizi.demo.HelloConcrete;
import com.feizi.demo.impl.HelloImpl;
import com.feizi.proxy.cglib.MyMethodInterceptor;
import com.feizi.proxy.dynamic.LogInvocationHandler;
import com.feizi.proxy.staticed.StaticProxyHello;
import org.assertj.core.internal.cglib.proxy.Enhancer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Proxy;

/**
 * Hello world!
 *
 */
public class App {
    private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

    public static void main( String[] args ){
//        testStaticedProxy();
//        testDynamicProxy();
        testCglibProxy();
    }

    /**
     * 测试CGLIB动态代理
     */
    public static void testCglibProxy(){
        //2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象。
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloConcrete.class);
        enhancer.setCallback(new MyMethodInterceptor());

        HelloConcrete helloConcrete = (HelloConcrete) enhancer.create();
        String content = helloConcrete.sayHello("feizi");
        LOGGER.info(content);
    }
}

控制台输出结果:

2018-05-31 17:08:41.277 [main] INFO  com.feizi.proxy.cglib.MyMethodInterceptor - You said: [feizi]
2018-05-31 17:08:41.279 [main] INFO  com.feizi.proxy.cglib.MyMethodInterceptor - before invoking cglibProxy...
2018-05-31 17:08:41.288 [main] INFO  com.feizi.demo.HelloConcrete - execute real method...
2018-05-31 17:08:41.288 [main] INFO  com.feizi.proxy.cglib.MyMethodInterceptor - after invoking cglibProxy...
2018-05-31 17:08:41.288 [main] INFO  com.feizi.App - HelloConcrete: feizi

Process finished with exit code 0

Cglib动态代理总结:

  1. 优点:同上面的JDK动态代理,比较灵活,可以在运行时根据需要动态的创建代理对象,比较容易扩展
  2. 缺点:通过继承实现。针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。
上一篇下一篇

猜你喜欢

热点阅读