JavaEE笔记(二)动态代理(Dynamic Proxy)

JavaEE笔记(二)动态代理(Dynamic Proxy)

A dynamic proxy class is a class that implements a list of interfaces specified at runtime when the class is created, with behavior as described below. A proxy interface is such an interface that is implemented by a proxy class. A proxy instance is an instance of a proxy class. Each proxy instance has an associated invocation handler object, which implements the interface InvocationHandler. A method invocation on a proxy instance through one of its proxy interfaces will be dispatched to the invoke method of the instance’s invocation handler, passing the proxy instance, a java.lang.reflect.Method object identifying the method that was invoked, and an array of type Object containing the arguments. The invocation handler processes the encoded method invocation as appropriate and the result that it returns will be returned as the result of the method invocation on the proxy instance.

当一个类创建时,一个动态代理类在运行时会实现一系列特定的接口。动态代理类具有以下特征:
1) 一个代理接口由被代理类实现;一个代理实例对应一个被代理类的实例。每个代理实例拥有一个与之相关的调用处理器对象,这个对象实现了调用处理器接口。
2) 通过一个代理实例的其中一个代理接口调用的方法将被分配到这个实例的调用处理器的相应的调用方法上,并将被代理对象、一个java.lang.reflect.Method对象(用来明确那个方法被调用)以及一个含有调用方法所需的参数数组作为参数传入。
3) 调用处理器会适当处理重新组织的方法调用并将返回的结果作为代理实例方法调用的结果返回。

以上是Java定义的动态代理类的描述。


事实上,简而言之,动态代理是基于代理模式上的代理机制。开发人员不用手工编写代理类和调用的方法,只要简单指定一组几口及委托代理类,便可以动态地获得代理。代理实例所调用的方法,如果在代理类中的调用处理器被重新处理,则最后返回的结果将是处理的结果,否则也不影响被代理类其他方法的调用。


1. 代理设计模式

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。[1]
这里写图片描述

代理模式的实现:
1) 代理类与被代理类要实现同一接口;
2) 在代理类中持有被代理类的对象;
3) 在代理类中调用被代理的行为。


2. 动态代理相关类和接口

动态代理主要有两种实现方式:
1)通过Proxy类来实现,这种方式就是Java定义的通过接口方式实现代理,因此被代理类必须实现接口。也就是说,这种方法只能为接口做代理。Java动态代理的主类位于java.lang.reflect.Proxy下,它提供了一组静态方法动态生成代理类和对象。
2) 通过cglib实现。
本文中主要介绍第一种方式实现动态代理并在最后给出一组示例。


Proxy类的静态方法

// 方法1: 返回指定代理实例的调用处理器对象。 
static InvocationHandler   getInvocationHandler(Object proxy)

// 方法2: 返回一个动态代理类对象,由给定的类加载器对象和接口数组确定
static Class<?>   getProxyClass(ClassLoader loader, Class<?>... interfaces)

// 方法3: 当指定类是由getProxyClass方法或者newProxyInstance方法动态生成的代理类返回true.
static boolean  isProxyClass(Class<?> cl)

// 方法4: 生成动态代理实例,给定被代理类类加载器、接口类对象以及调用处理器
static Object   newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

ClassLoader是被代理类类加载器对象,关于类加载器,可以见博主的另一篇笔记 《JavaEE笔记(一)类加载器(ClassLoader)》

java.lang.reflect.InvocationHandler 是一个接口类由动态代理类的调用处理器实现。其核心方法为invoke() 返回值将会传递给动态代理实例调用的方法返回值。如果接口方法申明的返回值类型是原始类型,那么invoke()方法返回的必须是原始类型的包装类。实现InvocationHandler接口时invoke()方法需重写,它有三个参数:

// proxy为被代理实例,一般不用
// method为被代理类接口中的方法对象,在动态代理类实例被调用时传入
// args为method调用时需要的参数数组
Object    invoke(Object proxy, Method method, Object[] args)

一个动态代理实例的创建一般使用方法4,在传参时通过匿名内部类创建继承InvocationHandler接口的对象并重写invoke()方法。在invoke()方法中明确那些接口方法需要增强,那些维持原有被代理类的执行行为。这样通过重写invoke()方法可以动态地对一个接口实现类中的任意数量任意方法进行增强。


3. 应用示例

3.1 自定义连接池中的应用

编写自定义连接池需实现javax.sql.DataSource接口。DataSource接口中定义了两个重载的getConnection()方法:

Connection getConnection()
Connection getConnection(String username, String password)

3.1.1 实现DataSource接口,并实现连接池功能的步骤:
  • 在DataSource构造函数中批量创建与数据库的连接,并把创建的连接保存到一个集合对象中
  • 此处我们重写DataSource中第一个无参方法,getConnection()方法每次调用时,从集合对象中取一个Connection返回给用户。由于是自定义连接池的测试类,我们假定连接池中维护连接总数为10个。
  • 当用户使用完Connection,调用Connection.close()方法时,Connection对象应保证将自己返回到连接池的集合对象中,而不要把con销毁。

JdbcUtil工具类读取properties文件获取连接数据库所需的参数并返回连接对象connection

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ResourceBundle;

public class JdbcUtils {

    private static final String DRIVERCLASS;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;

    static {
        DRIVERCLASS = ResourceBundle.getBundle("jdbc").getString("driverClass");
        URL = ResourceBundle.getBundle("jdbc").getString("url");
        USERNAME = ResourceBundle.getBundle("jdbc").getString("username");
        PASSWORD = ResourceBundle.getBundle("jdbc").getString("password");
    }

    static {
        try {
            // 将加载驱动操作,放置在静态代码块中.这样就保证了只加载一次.
            Class.forName(DRIVERCLASS);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        // 通过MySQL驱动获取connection对象。
        Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        return con;
    }

自定义连接池类中利用动态代理对象捕获被代理Connection对象的close()方法,对其进行行为改造,将其重新放回连接池的集合中。对于其他方法的调用,利用反射维持原有方法的执行行为。

import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;
import javax.sql.DataSource;
import cn.itcast.utils.JdbcUtils;

public class MyDataSource implements DataSource {

    private LinkedList<Connection> ll; // 用于装Connection对象的容器。   
    public MyDataSource() throws SQLException {
        ll = new LinkedList<Connection>();      
        // 当创建MyDateSource对象时,会向ll中装入10个Connection对象。
        for (int i = 0; i < 10; i++) {
            Connection con = JdbcUtils.getConnection();
            ll.add(con);
        }
    }

    // 利用工具类获取连接方法
    public Connection getConnection() throws SQLException {     

        final Connection con = ll.removeFirst();
        Connection proxyCon = (Connection) Proxy.newProxyInstance(con
                .getClass().getClassLoader(), con.getClass().getInterfaces(),
                new InvocationHandler() {

                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        if ("close".equals(method.getName())) {
                            // 这代表是close方法,它要做的事情是将con对象重新装入到集合中.
                            ll.add(con);
                            System.out.println("重新将连接对象装入到集合中");
                            return null;
                        } else {
                            return method.invoke(con, args);// 其它方法执行原来操作
                        }
                    }
                });

        return proxyCon;
    }

    public Connection getConnection(String username, String password)
            throws SQLException {
        return null;
    }

    // 省略还要实现的若干DataSource接口的父接口中的方法......
}

3.2 过滤器中的应用
  • 假设这样一个场景,页面提交的表单参数含有中文,如果对http请求对象没有编码过滤,那么服务器端拿到的参数必然出现乱码。
  • 因此我们有这样的需求:要求全局设置过滤器对所有的表单提交参数进行编码控制。
  • 服务器端通过Request.getParameter()得到表单提交的信息,因此我们在过滤器中需要为Request对象创建动态代理。RequestHttpServletRequest的接口实现类,适用于动态代理。当调用的方法为getParameter()时对Request的请求参数进行utf-8的重新编码,这样就可以全局解决服务器端的数据乱码问题。
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class EncodingFilter implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        // 1.强转
        final HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 2.操作
        // 创建一个req对象的代理对象reqProxy
        HttpServletRequest reqProxy = (HttpServletRequest) Proxy
                .newProxyInstance(req.getClass().getClassLoader(), req
                        .getClass().getInterfaces(), new InvocationHandler() {

                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {

                        // 1.得到方法名称
                        String methodName = method.getName();
                        if ("getParameter".equals(methodName)) {
                            String param = req.getParameter((String) (args[0]));

                            return new String(param.getBytes("iso8859-1"),
                                    "utf-8");

                        } else {
                            // 不是getParameter方法,就执行其原来操作.
                            return method.invoke(req, args);
                        }
                    }
                });

        // 3.放行
        chain.doFilter(reqProxy, resp);
    }

    public void destroy() {}
}

4. 总结

动态代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。[1]
文中给出了2个简单的应用,通过对被代理对象的指定方法的行为改写,可以对接口继承对象进行增强,相比装饰和继承的方法,动态代理显得尤为高效和简洁。


以上

© 著作权归作者所有

引用:
[1] Java 动态代理机制分析及扩展 http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值