您的当前位置:首页正文

Java笔记 —— 动态代理

2024-11-28 来源:个人技术集锦

先来看一段代码,模拟一下数据库的增删改查功能
定义一个接口,这个接口是数据库的功能列表

package test.dynamicagent;

public interface User {
    public abstract void add();  //增
    public abstract void delete(); //删
    public abstract void update(); //改
    public abstract void select(); //查
}

实现类

package test.dynamicagent;

public class UserImpl1 implements User {
    @Override
    public void add() {
        System.out.println("模拟数据库的增加功能");
    }

    @Override
    public void delete() {
        System.out.println("模拟数据库的删除功能");
    }

    @Override
    public void update() {
        System.out.println("模拟数据库的更新功能");
    }

    @Override
    public void select() {
        System.out.println("模拟数据库的查询功能");
    }
}

这里用四条输出语句取代一下数据库的操作语句
写一个测试类,运行一下

package test.dynamicagent;

public class UserDemo1 {
    public static void main(String[] args) {
        UserImpl1 user1 = new UserImpl1();
        user1.add();
        user1.delete();
        user1.update();
        user1.select();
    }
}

那么怎么样才能在不改变原有代码的前提下,增加权限验证和日志记录功能呢。
不能修改原有的代码,我们可以考虑写一个代理对象,代理对象将在原先对象的基础上实现更多的代码功能。即代理对象 = 原对象 + 新代码

这里就要引入静态代理的概念,为上面的UserImpl1具体类创建一个代理类,这个代理类也将实现User接口。然后将UserImpl1的对象传入代理类里面,代理类通过这个对象调用增删改查的方法,然后在前后加上实现权限验证和日志记录功能的语句。

静态代理类

package test.dynamicagent;

public class UserImpl2 implements User {
    public User user1;
    public UserImpl2(UserImpl1 userImpl1){
        this.user1 = userImpl1;
    }
    @Override
    public void add() {
        System.out.println("模拟数据库的权限验证");
        user1.add();
        System.out.println("模拟数据库的日志记录");
        System.out.println("--------------------------");
    }

    @Override
    public void delete() {
        System.out.println("模拟数据库的权限验证");
        user1.delete();
        System.out.println("模拟数据库的日志记录");
        System.out.println("--------------------------");
    }

    @Override
    public void update() {
        System.out.println("模拟数据库的权限验证");
        user1.update();
        System.out.println("模拟数据库的日志记录");
        System.out.println("--------------------------");
    }

    @Override
    public void select() {
        System.out.println("模拟数据库的权限验证");
        user1.select();
        System.out.println("模拟数据库的日志记录");
        System.out.println("--------------------------");
    }
}

package test.dynamicagent;

public class UserDemo1 {
    public static void main(String[] args) {
        UserImpl1 user1 = new UserImpl1();

        UserImpl2 user2 = new UserImpl2(user1);
        user2.add();
        user2.delete();
        user2.update();
        user2.select();
    }
}

动态代理的引入

但是静态代理有一个很明显的缺点,一个静态代理类只能给一个类服务,如果我们有多个类需要代理,就需要写多个代理类。这样是在太过麻烦,于是我们就会想,能不能不编写代理类,但是获取代理类的对象呢?

事实上,对象是根据Class对象而创建的,每个类只会有一个Class对象,这个Class对象会获取这个类的所有信息,包括方法,变量等。因此,我们不需要专门写一个代理类,只要能获取一个代理类的Class对象,就可以根据这个Class对象来创建代理类对象。那么,我们又该去哪里获取这个Class对象呢。这时可以看上面的例子,例子中代理类和具体类都实现同一个接口,之前也说了,代理类相当于给具体类增加了新的代码和功能,所以我们要给一个具体类创建对应的代理类,就可以从它们共同的接口下手。

但是这里又有一个新的问题,Class对象中包含类的所有信息,自然是包含构造器的,有了构造器才能创建对象。但是接口是没有构造器的,也就是说即使获取了接口的Class对象,也无法创建对象。

对于这种情况,Java为代理类专门提供了java.lang.reflect.Proxy类 和 java.lang.reflect.InvocationHandler接口。这一个类和一个接口是动态代理的核心内容。

Proxy类里面有一个方法,public static 类<?> getProxyClass(ClassLoader loader,类<?>… interfaces)。

这个getProxyClass方法的作用是,传入一个类加载器和一组接口,然后返回一个代理类的Class对象。通过这种方式获得的Class对象有接口的全部类信息,和构造器。因此这个Class对象是可以用来创建代理类对象的。

这个构造器是 Proxy(InvocationHandler h) {},根据这个构造器将创建一个Proxy代理类对象。但是我们发现这个构造器需要传入一个参数InvocationHandler h,这又是一个什么呢?

这里光说概念,理解起来比较费劲,现在来看一下动态代理的例子

使用创建Class对象的方式来创建代理类对象

先创建一个学生接口,学生要完成的事情是考试

package test.dynamicagent.agent;

public interface Student {
    public abstract void examination();
}

然后是具体的实现类StudentImpl,也就是 被代理类

package test.dynamicagent.agent;

public class StudentImpl implements Student {

    @Override
    public void examination() {
        System.out.println("学生考试");
    }
}

然后是实现InvocationHandler接口的具体类

package test.dynamicagent.agent;

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

public class StudentHandler implements InvocationHandler {
    private Object obj;

    public StudentHandler(){}

    //传入被代理类的对象,并放入obj里面
    public StudentHandler(Object obj){
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //增加代理类要完成的新功能
        System.out.println("检查文具,领取试卷");
        Object invoke = method.invoke(obj, args);
        //增加代理类要完成的新功能
        System.out.println("检查答案,上交试卷");
        return invoke;
    }
}

最后是测试类,主方法来运行代理类

package test.dynamicagent.agent;

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

public class StudentDemo {
    public static void main(String[] args) throws Exception {
        //被代理的对象
        StudentImpl s1 = new StudentImpl();
        //获取代理类Class对象
        Class proxyStudent = Proxy.getProxyClass(s1.getClass().getClassLoader(),s1.getClass().getInterfaces());
        //根据Class对象获取构造方法
        Constructor cons = proxyStudent.getConstructor(InvocationHandler.class);
        //创建InvocationHandler接口的对象
        StudentHandler handler = new StudentHandler(s1);
        //根据获取的构造方法创建代理类对象,并且传入参数handler
        Student proxy = (Student)cons.newInstance(handler);
        //利用代理类对象执行方法
        proxy.examination();
    }
}

结果为

结果是正确的,原先的被代理类这是打印一句学生考试,这里通过代理类,实现了在学生考试之前,要先检查文具,领取试卷,考试之后要检查答案,上交试卷的功能。

当我们通过代理类对象调用方法时,proxy . examination();
前面说过InvocationHandler接口是proxy代理类对象实现调用处理程序的一个接口,实现这个接口,其实是给代理类对象一个调用处理程序,也就是例子里面的handler。这个调用处理程序,会在代理类对象调用examination方法时,转到实现InvocationHandler接口的具体类的invoke方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //增加代理类要完成的新功能
        System.out.println("检查文具,领取试卷");
        Object invoke = method.invoke(obj, args);
        //增加代理类要完成的新功能
        System.out.println("检查答案,上交试卷");
        return invoke;
    }

而这个invoke方法里面,实际上是使用了反射机制,即通过Method对象来直接调用类里面的成员方法。具体的Method对象是什么,取决于代理类对象此时调用的是哪个方法。

这里代理类对象调用的是examination()方法,因此Method对象对应的就是examination()方法,通过invoke调用的也就是obj对象里面的成员方法examination。这里的obj对象又是谁呢?是之前传入接口的具体实现类StudentImpl类(也就是被代理类)的对象。

所以说实际上调用的是被代理类对象 s1 的examination方法,而不是代理类对象proxy的

因此就可以看出来,实际上执行了三条语句

System.out.println("检查文具,领取试卷");  //代理类的语句
Object invoke = method.invoke(obj, args); //调用被代理类的方法
 System.out.println("检查答案,上交试卷"); //代理类的语句

其实跟静态代理很像,也是用代理类新增的语句一前一后夹住,被代理类的方法执行语句
只不过动态代理不需要再写一个代理类

通过newProxyInstance直接创建代理类对象

事实上,上面那种创建Class对象,然后再用Class对象创建代理类对象的方式依旧有些繁琐,Java提供了newProxyInstance方法,使用这个方法可以隐藏创建Class对象的过程,直接得到一个代理类的对象。
还是用上面的例子,学生接口,被代理类,InvocationHandler接口的实现类的代码都不用修改,只需要修改main方法

package test.dynamicagent.agent;

import java.lang.reflect.Proxy;

public class StudentDemo1 {
    public static void main(String[] args) {
        //创建被代理类的对象
        StudentImpl student = new StudentImpl();
        //创建StudentHandler接口的具体实现类
        StudentHandler handler = new StudentHandler(student);

        /*
            通过Proxy.newProxyInstance方法来创建代理类对象,下面说一下括号里各个参数的含义
            student.getClass().getClassLoader()是获取类加载器来加载代理对象
            student.getClass().getInterfaces()让代理对象和被代理对象拥有相同的接口,
                                 这样代理对象就可以像被代理对象那样调用接口里面的方法
            handler 是上面创建的实现InvocationHandler接口的类的对象
         */
        Student proxy = (Student) Proxy.newProxyInstance(student.getClass().getClassLoader()
                ,student.getClass().getInterfaces()
                ,handler);
        proxy.examination();
    }
}

结果是一样的

匿名内部类

像上面那样写还是麻烦了一点,因为还需要新建一个StudentHandler类来实现InvocationHandler接口,这里可以用匿名内部类的形式来实现InvocationHandler接口

下面是完整的代码

package test.dynamicagent.agent;

public interface Student {
    public abstract void examination();
}

package test.dynamicagent.agent;

public class StudentImpl implements Student {

    @Override
    public void examination() {
        System.out.println("学生考试");
    }
}

package test.dynamicagent.agent;

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

public class StudentDemo2 {
    public static void main(String[] args) {
        StudentImpl student = new StudentImpl();

        Student proxy = (Student) Proxy.newProxyInstance(student.getClass().getClassLoader()
                , student.getClass().getInterfaces()
                , new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("检查文具,领取试卷");
                        Object invoke = method.invoke(student, args);
                        //增加代理类要完成的新功能
                        System.out.println("检查答案,上交试卷");
                        return invoke;
                    }
                });

        proxy.examination();
    }
}

显示全文