代理
在Java中没有委托的语言特性,只有通过代理设计模式来实现委托。
在代理设计模式中代理类和委托类都实现同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
Subject类,定义RealSubject和Proxy的共用接口,这样就可以在任何使用RealSubject的地方都可以使用Proxy。
RealSubject类:定义Proxy所代表的真实实体。
Proxy类:保存一个引用使得代理可以访问实体并提供一个与Subject的接口相同的接口,这样代理就可以用来替换实体。
静态代理
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。
首先,我们创建一个IPerson接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
public interface IPerson {
// 上交班费
void giveMoney();
}
Student类实现IPerson接口。Student可以具体实施上交班费的动作。
public class Student implements IPerson{
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}
接下来是班长类,也就是学生的代理类,它也实现了IPerson接口,所以它持有学生对象,他就可以代理学生类来执行上交班费的行为。
public class StudentProxy implements IPerson{
Student student;
public StudentProxy(IPerson student) {
if (student.getClass() == Student.class){
this.student = (Student) student;
}
}
@Override
public void giveMoney() {
student.giveMoney();
}
}
下面测试一下,看如何使用代理模式:
public class Main {
public static void main(String[] args) {
// /被代理的学生legend,他的班费上交有代理对象monitor(班长)完成
IPerson legend = new Student("Legend");
//生成代理对象,并将legend传给代理对象
StudentProxy studentProxy = new StudentProxy(legend);
//班长代理上交班费
studentProxy.giveMoney();
}
}
代理模式最主要的就是有一个公共接口(IPerson),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。
代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:
public class StudentProxy implements IPerson{
Student student;
public StudentProxy(IPerson student) {
if (student.getClass() == Student.class){
this.student = (Student) student;
}
}
@Override
public void giveMoney() {
System.out.println(student.getName() + "最近学习有进步!");
student.giveMoney();
}
}
在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。
相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
比如说,想要在每个代理的方法前都加上一个处理方法:
public void giveMoney() {
//调用被代理方法前加入处理方法
beforeMethod();
stu.giveMoney();
}
假设有很多个giveMoney方法,如果每个都修改代码则很麻烦,使用动态代理的话则可以无污染无侵入的对方法进行加强操作。
动态代理实现
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
创建一个动态代理对象步骤,具体代码见后面:
1、创建一个InvocationHandler对象
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
2、使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
3、获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
4、通过构造器constructor来创建一个动态实例stuProxy
Person stuProxy = (Person) cons.newInstance(stuHandler);
一个动态代理对象就创建完毕,当然,上面三个步骤可以通过Proxy类的newProxyInstances方法来简化:
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
下面基于上面的静态代理的实例进行修改,班长需要帮学生代交班费:
public interface IPerson {
// 上交班费
void giveMoney();
}
创建需要被代理的实际类:
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
try {
//假设数钱花了一秒时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "上交班费50元");
}
}
再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start方法,执行后调用finsh方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。
public class MonitorUtil {
private static ThreadLocal<Long> tl = new ThreadLocal<>();
public static void start() {
tl.set(System.currentTimeMillis());
}
//结束时打印耗时
public static void finish(String methodName) {
long finishTime = System.currentTimeMillis();
System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
}
}
创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。
public class StudentInvocationHandler<T> implements InvocationHandler {
T target;
public StudentInvocationHandler(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
//代理过程中插入监测方法,计算该方法耗时
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
}
做完上面的工作后,我们就可以具体来创建动态代理对象:
public class Main {
public static void main(String[] args) {
//创建一个实例对象,这个对象是被代理的对象
IPerson legend = new Student("legend");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler invocationHandler = new StudentInvocationHandler<>(legend);
//创建一个代理对象stuProxy来代理legend,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
IPerson studentProxy = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(),
new Class<?>[]{IPerson.class}, invocationHandler);
//代理执行上交班费的方法
studentProxy.giveMoney();
}
}
所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。
注意:java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。