专业的JAVA编程教程与资源

网站首页 > java教程 正文

关于JAVA的静态代理和动态代理的原理和实现,一篇文章搞定

temp10 2024-10-04 12:33:00 java教程 10 ℃ 0 评论

什么是代理:

代理模式:,是常用的设计模式。特征是,代理类与委托类有相同的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类。以及事后处理消息。 代理类和委托类,存在着关联关系。代理类的对象本身并不真正实现服务,知识通过调用委托类的对象的相关方法。

为什么要使用代理模式

个人理解:实现同一个接口的代理类和委托类(接口实现类)在平常的功能当中完全没必要去使用代理类,但是在某些方面,不如说在执行接口实现类的方法的之前或之后要执行另外的方法。这个时候需要执行的方法不能写在接口的实现类中(我们调用该类的时候是注入service而不是Impl),所以代理类就发挥了作用。

关于JAVA的静态代理和动态代理的原理和实现,一篇文章搞定


其他:一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用, 其特征是代理类与委托类有同样的接口; 现在有了进一步的认识。代理类不仅仅是一个隔离客户端和委托类的中介。我们还可以借助代理来在增加一些功能,而不需要修改原有代码,严重的复合开闭原则哦。 代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 就是这样,真正的业务功能还是有委托类来实现,但是在实现业务类之前的一些公共服务。例如在项目开发中我们没有加入缓冲,日志这些功能,后期想加入,我们就可以使用代理来实现,而没有必要打开已经封装好的委托类。

分类:

  • 静态代理
  • 动态代理

静态代理:

代理类是由程序员创建,或由工具生成的代码 编译成的。在程序运行前,代理类的 *.class文件已经存在了。直接就可以运行 。

静态代理的简单实现:

假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,

班长就是学生的代理。

首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。

/**
 * 创建Person接口
 * @author Gonjan
 */
public interface Person {
 //上交班费
 void giveMoney();
}
 

Student类实现Person接口。Student可以具体实施上交班费的动作。

public class Student implements Person {
 private String name;
 public Student(String name) {
 this.name = name;
 }
 
 @Override
 public void giveMoney() {
 System.out.println(name + "上交班费50元");
 }
}

StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。

/

**
 * 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
 * @author Gonjan
 *
 */
public class StudentsProxy implements Person{
 //被代理的学生
 Student stu;
 
 public StudentsProxy(Person stu) {
 // 只代理学生对象
 if(stu.getClass() == Student.class) {
 this.stu = (Student)stu;
 }
 }
 
 //代理上交班费,调用被代理学生的上交班费行为
 public void giveMoney() {
 stu.giveMoney();
 }
}

下面测试一下,看如何使用代理模式:

public class StaticProxyTest {
 public static void main(String[] args) {
 //被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
 Person zhangsan = new Student("张三");
 
 //生成代理对象,并将张三传给代理对象
 Person monitor = new StudentsProxy(zhangsan);
 
 //班长代理上交班费
 monitor.giveMoney();
 }
}

这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。

代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:

public class StudentsProxy implements Person{
 //被代理的学生
 Student stu;
 
 public StudentsProxy(Person stu) {
 // 只代理学生对象
 if(stu.getClass() == Student.class) {
 this.stu = (Student)stu;
 }
 }
 
 //代理上交班费,调用被代理学生的上交班费行为
 public void giveMoney() {
 System.out.println("张三最近学习有进步!");
 stu.giveMoney();
 }
}

动态代理:

动态代理的代理类。没有直接由源代码生成。动态代理类的对象是在程序运行时由JAVA反射机制动态生成,不需要手工编写源代码。从而提高了软件的可扩展性。JAVA反射机制可以生成任意类型的动态代理类。

动态代理简单实现

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

创建一个动态代理对象步骤,具体代码见后面:

  • 创建一个InvocationHandler对象
//创建一个与代理对象相关联的InvocationHandler
 InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
  • 使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
 Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
  • 获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
  • 通过构造器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);

到这里肯定都会很疑惑,这动态代理到底是如何执行的,是如何通过代理对象来执行被代理对象的方法的,先不急,我们先看看一个简单的完整的动态代理的例子。还是上面静态代理的例子,班长需要帮学生代交班费。

首先是定义一个Person接口:

/**
 * 创建Person接口
 * @author Gonjan
 */
public interface Person {
 //上交班费
 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方法。

再再invoke方法中执行被代理对象target的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。

public class StuInvocationHandler<T> implements InvocationHandler {
 //invocationHandler持有的被代理对象
 T target;
 
 public StuInvocationHandler(T target) {
 this.target = target;
 }
 
 /**
 * proxy:代表动态代理对象
 * method:代表正在执行的方法
 * args:代表调用目标方法时传入的实参
 */
 @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 ProxyTest {
 public static void main(String[] args) {
 
 //创建一个实例对象,这个对象是被代理的对象
 Person zhangsan = new Student("张三");
 
 //创建一个与代理对象相关联的InvocationHandler
 InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
 
 //创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
 Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
 //代理执行上交班费的方法
 stuProxy.giveMoney();
 }
}

我们执行这个ProxyTest类,先想一下,我们创建了一个需要被代理的学生张三,将zhangsan对象传给了stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。

运行结果:

动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,弄清楚其中缘由。

赘述:

以上就是对静态代理和动态代理概念的阐述,第一次发文章,内容排版可能不太好,欢迎大家支持和关注。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表