先来看一段代码,模拟一下数据库的增删改查功能
定义一个接口,这个接口是数据库的功能列表
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,这又是一个什么呢?
这里光说概念,理解起来比较费劲,现在来看一下动态代理的例子
先创建一个学生接口,学生要完成的事情是考试
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("检查答案,上交试卷"); //代理类的语句
其实跟静态代理很像,也是用代理类新增的语句一前一后夹住,被代理类的方法执行语句
只不过动态代理不需要再写一个代理类
事实上,上面那种创建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();
}
}