Java代理模式详解
博客专区 > 商者 的博客 > 博客详情
Java代理模式详解
商者 发表于1年前
Java代理模式详解
  • 发表于 1年前
  • 阅读 3
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: Java代理模式详解

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被为拖累执行后的后续处理。

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

按照代理的创建时期,代理类可以分为两种:
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成

下面是两个例子说明了静态代理和动态代理的使用方法:

静态代理模式

1
2
3
4
5
6
7
8
9
10
11
12
13
package Proxy;

/**
 * Created by benjamin on 1/15/16.
 */
public interface UserManager {

    void addUser(String userId, String userName);
    void deleteUser(String userId);
    String findUser(String userId);
    void modifyUser(String userId, String userName);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package Proxy;

/**
 * Created by benjamin on 1/15/16.
 */
public class UserManagerImpl implements UserManager {

    public void addUser(String userId, String userName) {
        System.out.println("UserManagerImpl.addUser: " + userId + ": " + userName);
    }

    public void deleteUser(String userId) {
        System.out.println("UserManagerImpl.deleteUser");
    }

    public String findUser(String userId) {
        System.out.println("UserManagerImpl.findUser");
        return "benjamin";
    }

    public void modifyUser(String userId, String userName) {
        System.out.println("UserManagerImpl.modifyUser");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package Proxy;

/**
 * Created by benjamin on 1/15/16.
 */
public class UserManagerProxy implements UserManager {

    private UserManager object;

    // 构造函数传入目标对象
    public UserManagerProxy(UserManager object) {
        this.object = object;
    }

    public void addUser(String userId, String userName) {
        try {

            // 添加基本的日志打印功能
            System.out.println("开始addUser");
            object.addUser(userId, userName);
            System.out.println("成功addUser");

        } catch (Exception e) {
            System.err.println("error addUser");
        }
    }

    public void deleteUser(String userId) {
        object.deleteUser(userId);
    }

    public String findUser(String userId) {
        return object.findUser(userId);
    }

    public void modifyUser(String userId, String userName) {
        object.modifyUser(userId, userName);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Proxy;

/**
 * Created by benjamin on 1/15/16.
 */
public class Client {

    public static void main(String[] args) {
        UserManager userManager = new UserManagerProxy(new UserManagerImpl());
        userManager.addUser("1111", "张三");

        /**
         * 开始addUser
           UserManagerImpl.addUser: 1111: 张三
           成功addUser
         */
    }
}

相信有一点基础的同学都可以看出静态代理的缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码只是为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。

这样我们必须要引入动态代理:
在上面的示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象

动态代理模式

原来的接口和实现类都不变,这里就不再重复写了,只是代理类使用了动态代理来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package Proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created by benjamin on 1/15/16.
 */
public class UserManagerProxy implements InvocationHandler {

    // 目标对象
    private Object targetObject;

    public Object newProxyInstance(Object targetObject) {
        this.targetObject = targetObject;

        // 第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
        // 第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
        // 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
        // 根据传入的目标返回一个代理对象
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
    }

    /**
     * 关联的这个实现类的方法被调用时将被执行
     * @param proxy 代理
     * @param method    原对象被调用的方法
     * @param args  方法的参数
     * @return  原方法的返回值
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = null;

        try {

            System.out.println("start invoke-->");

            // 调用目标方法
            ret = method.invoke(targetObject, args);

            System.out.println("success invoke-->");

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("error-->");
            throw e;
        }
        return ret;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Proxy;

/**
 * Created by benjamin on 1/15/16.
 */
public class Client {

    public static void main(String[] args) {
        UserManager userManager = (UserManager)new UserManagerProxy().newProxyInstance(new UserManagerImpl());
        userManager.addUser("1111", "张三");

        /**
         * start invoke-->
           UserManagerImpl.addUser: 1111: 张三
           success invoke-->
         */
    }
}

AOP模式分析

AOP(AspectOrientedProgramming):将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码—解耦。
正是因为在所有的类里,核心代码之前的操作和核心代码之后的操作都做的是同样的逻辑,因此我们需要将它们提取出来,单独分析,设计和编码,这就是我们的AOP思想。一句话说,AOP只是在对OOP的基础上进行进一步抽象,使我们的类的职责更加单一。

动态代理的优点:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强

cglib动态代理实现


JDK自从1.3版本开始,就引入了动态代理,JDK的动态代理用起来非常简单,但是它有一个限制,就是使用动态代理的对象必须实现一个或多个接口 。如果想代理没有实现接口的类可以使用CGLIB包。
CGLIB是一个强大的高性能的代码生成包。它被许多AOP的框架(例如Spring AOP)使用,为他们提供方法的interception(拦截)。Hibernate也使用CGLIB来代理单端single-ended(多对一和一对一)关联。EasyMock通过使用模仿(moke)对象来测试java代码的包。它们都通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。
  CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
下面通过一个实例来讲解cglib:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.benjamin;

/**
 * 定义一个HelloWorld类
 * 
 * @author benjamin 
 * 
 */
public class HelloWorld {
    public void sayHelloWorld() {
        System.out.println("HelloWorld!");
    }
}


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.benjamin;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * 通过Cglib实现在方法调用前后向控制台输出两句字符串
 * 
 * @author benjamin
 *
 */
public class CglibProxy implements MethodInterceptor {
    //要代理的原始对象
    private Object obj;
    
    public Object createProxy(Object target) {
        this.obj = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.obj.getClass());// 设置代理目标
        enhancer.setCallback(this);// 设置回调
        enhancer.setClassLoader(target.getClass().getClassLoader());
        return enhancer.create();
    }


    /**
     * 在代理实例上处理方法调用并返回结果
     * 
     * @param proxy
     *            代理类
     * @param method
     *            被代理的方法
     * @param params
     *            该方法的参数数组
     * @param methodProxy
     */
    public Object intercept(Object proxy, Method method, Object[] params,
            MethodProxy methodProxy) throws Throwable {
        Object result = null;
        // 调用之前
        doBefore();
        // 调用原始对象的方法
        result = methodProxy.invokeSuper(proxy, params);
        // 调用之后
        doAfter();
        return result;
    }

    private void doBefore() {
        System.out.println("before method invoke");
    }

    private void doAfter() {
        System.out.println("after method invoke");
    }

}


 

1
2
3
4
5
6
7
8
9
10
11
package com.benjamin;

public class HelloWorldTest {

    public static void main(String[] args) {
        HelloWorld helloWorld=new HelloWorld();
        CglibProxy cglibProxy=new CglibProxy();
        HelloWorld hw=(HelloWorld)cglibProxy.createProxy(helloWorld);
        hw.sayHelloWorld();
    }
}



运行结果为:
 

1
2
3
before method invoke
HelloWorld!
after method invoke


 

远程代理的实现

远程代理分为服务端和客户端,都必须存在接口定义的文件。这样客户端只需要通过远程代理就可以拥有存在服务端方法一样的感觉。

共同的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package remoteProxy;

import java.io.Serializable;

/**
 * Created by piqiu on 1/15/16.
 */
public class Call implements Serializable {

    private static final long serialVersionUID = 4924678505695074146L;

    private String className;
    private String methodName;
    private Class[] paramTypes;
    private Object[] params;
    private Object result;

    public Call() {}

    public Call(String className, String methodName, Class[] paramTypes, Object[] params) {
        this.className = className;
        this.methodName = methodName;
        this.paramTypes = paramTypes;
        this.params = params;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("{className=" + className).append(",methodName=" + methodName).append(",result=" + result).append("}");
        return sb.toString();
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }

    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package remoteProxy;

import java.rmi.RemoteException;
import java.util.Date;

/**
 * Created by benjamin on 1/15/16.
 */
public interface IHelloService {

    String echo(String msg) throws RemoteException;
    Date getTime() throws RemoteException;
}

服务端代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package remoteProxy.Server;

import remoteProxy.IHelloService;

import java.util.Date;

/**
 * Created by benjamin on 1/15/16.
 */
public class HelloServiceImpl implements IHelloService {

    public String echo(String msg) {
        System.out.println("HelloServiceImpl.echo: " + msg);
        return msg;
    }

    public Date getTime() {
        System.out.println("HelloServiceImpl.getTime: " + new Date());
        return new Date();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package remoteProxy.Server;

import remoteProxy.Call;

import java.io.*;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by benjamin on 1/15/16.
 */
public class SimpleServer {
    private Map remoteObjects = new HashMap();

    /** 把一个远程对象放到缓存中 **/
    public void register(String className, Object remoteObject) {
        remoteObjects.put(className, remoteObject);
    }

    public void service() throws Exception {
        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;
        Socket socket = null;
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            System.out.println("服务器启动......");
            while (true) {
                socket = serverSocket.accept();

                ois = new ObjectInputStream(socket.getInputStream());  // 读取对象
                oos = new ObjectOutputStream(socket.getOutputStream());    // 得到输出流

                Call call = (Call)ois.readObject(); // 接收客户端发送的Call对象

                call = invoke(call);    // 执行完操作拿回结果
                oos.writeObject(call);  // 发送结果

            }
        } finally {
            // 关闭流
            if (ois != null) ois.close();
            if (oos != null) oos.close();
            if (socket != null) socket.close();
        }

    }

    public Call invoke(Call call) {
        Object result = null;
        try {

            String className = call.getClassName();
            String methodName = call.getMethodName();
            Object[] params = call.getParams();
            Class[] paramTypes = call.getParamTypes();
            Class classType = Class.forName(className);

            Method method = classType.getMethod(methodName, paramTypes);
            Object remoteObject = remoteObjects.get(className); // 从缓存中读取相关的缓存对象

            if (remoteObject == null) {
                throw new Exception(className + "的远程对象不存在");
            } else {
                result = method.invoke(remoteObject, params);
            }

        } catch (Exception e) {
            result = e;
        }
        call.setResult(result);
        return call;
    }

    public static void main(String[] args) throws Exception {
        SimpleServer server = new SimpleServer();
        server.register("remoteProxy.IHelloService", new HelloServiceImpl());
        // 启动服务
        server.service();
    }
}

客户端代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package remoteProxy.Client;

import java.io.*;
import java.net.Socket;

/**
 * Created by Benjamin on 1/15/16.
 * Connector 类负责建立与远程服务器的连接,以及接收和发送Socket对象
 */
public class Connector {

    private String host;
    private int port;
    private Socket skt;
    private InputStream is;
    private ObjectInputStream ois;
    private OutputStream os;
    private ObjectOutputStream oos;

    public Connector(String host, int port) throws Exception {
        this.host = host;
        this.port = port;
        connect(host, port);
    }

    /** 发送对象 **/
    public void send(Object obj) throws IOException {
        oos.writeObject(obj);
    }

    /** 接收对象 **/
    public Object receive() throws IOException, ClassNotFoundException {
        return ois.readObject();
    }

    /** 建立与远程服务器的连接 **/
    public void connect() throws Exception {
        connect(host, port);
    }

    public void connect(String host, int port) throws Exception {
        skt = new Socket(host, port);
        os = skt.getOutputStream();
        oos = new ObjectOutputStream(os);
        is = skt.getInputStream();
        ois = new ObjectInputStream(is);
    }

    public void close() { // 关闭连接
        try {
        } finally {
            try {
                ois.close();
                oos.close();
                skt.close();
            } catch (Exception e) {
                System.out.println("Connector.close: " + e);
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package remoteProxy.Client;

import remoteProxy.Call;
import remoteProxy.IHelloService;

import java.rmi.RemoteException;
import java.util.Date;

/**
 * Created by piqiu on 1/15/16.
 */
public class HelloServiceProxy implements IHelloService {

    private String host;
    private int port;

    public HelloServiceProxy(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public String echo(String msg) throws RemoteException {
        Connector connector = null;
        try {

            connector = new Connector(host, port);
            Call call = new Call("remoteProxy.IHelloService", "echo", new Class[]{String.class}, new Object[]{msg});
            connector.send(call);
            call = (Call)connector.receive();
            Object result = call.getResult();
            if (result instanceof Throwable) {
                throw new RemoteException("", (Throwable)result);
            } else {
                return (String)result;
            }
        } catch (Exception e) {
            throw new RemoteException("", e);
        } finally {
            if (connector != null) connector.close();
        }
    }

    public Date getTime() throws RemoteException {
        Connector connector = null;
        try {

            connector = new Connector(host, port);
            Call call = new Call("remoteProxy.IHelloService", "getTime", new Class[]{}, new Object[]{});
            connector.send(call);
            call = (Call)connector.receive();
            Object result = call.getResult();
            if (result instanceof Throwable) {
                throw new RemoteException("", (Throwable)result);
            } else {
                return (Date)result;
            }
        } catch (Exception e) {
            throw new RemoteException("", e);
        } finally {
            if (connector != null) connector.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package remoteProxy.Client;

import remoteProxy.Call;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;

/**
 * Created by piqiu on 1/15/16.
 */
public class ProxyFactory {

    public static Object getProxy(final Class classType, final String host, final int port) {
        InvocationHandler handler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Connector connector = null;
                try {
                    connector = new Connector(host, port);
                    Call call = new Call(classType.getName(), method.getName(),
                            method.getParameterTypes(), args);
                    connector.send(call);
                    call = (Call) connector.receive();
                    Object result = call.getResult();
                    if (result instanceof Throwable)
                        throw new RemoteException("",(Throwable) result); // 把异常都转换为RemoteException
                    else
                        return result;
                } finally {
                    if (connector != null)
                        connector.close();
                }
            }
        };
        return Proxy.newProxyInstance(classType.getClassLoader(), new Class[]{classType}, handler);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package remoteProxy.Client;

import remoteProxy.IHelloService;

import java.rmi.RemoteException;

/**
 * Created by piqiu on 1/15/16.
 */
public class SimpleClient {

    public static void main(String[] args) throws RemoteException {
        IHelloService helloService = new HelloServiceProxy("localhost", 8888);
        System.out.println(helloService.echo("hello"));
        System.out.println(helloService.getTime());

        IHelloService helloService1 = (IHelloService)ProxyFactory.getProxy(IHelloService.class, "localhost", 8888);
        System.out.println(helloService1.echo("hello2"));
        System.out.println(helloService.getTime());
    }
}

运行时候先运行服务端,再运行客户端。结果为:

服务端:

1
2
3
4
5
服务器启动......
HelloServiceImpl.echo: hello
HelloServiceImpl.getTime: Fri Jan 15 14:32:52 CST 2016
HelloServiceImpl.echo: hello2
HelloServiceImpl.getTime: Fri Jan 15 14:32:52 CST 2016

客户端:

1
2
3
4
hello
Fri Jan 15 14:32:52 CST 2016
hello2
Fri Jan 15 14:32:52 CST 2016

虚拟代理的实现

根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。

安全代理的实现

用来控制真是对象访问时的权限

智能指引

当调用真实的对象时,代理处理另外一些事。如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它。或当第一次引用一个持久对象时,将它装入内存。或在访问一个实例对象前,检查是否已经锁定它,以确保其他对象不能改变它。他们都是通过代理在访问一个对象时附加一些内务处理。

共有 人打赏支持
商者
粉丝 33
博文 131
码字总数 34228
×
商者
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: