Difference: The static proxy is created by the programmer or the tool generates the source code of the proxy class, and then compiles the proxy class; the bytecode file of the proxy class already exists before the program is run, and the relationship between the proxy class and the delegate class is determined before running. That’s it. The source code of the dynamic proxy class is dynamically generated by the JVM based on mechanisms such as reflection during program running, so there is no bytecode file for the proxy class.
Related recommendations: " Programming Video Course"
1. Agency Concept
Provide a proxy for an object to control access to the object. The proxy class and the delegate class have a common parent class or parent interface, so that the proxy object can be used wherever the delegate class object is used. The proxy class is responsible for request preprocessing, filtering, assigning requests to the delegate class for processing, and subsequent processing after the delegate class completes the request. Related recommendations: "Java Video Tutorial"
Figure 1: Proxy Mode
As can be seen from the figure, the proxy interface (Subject), proxy class (ProxySubject), and delegation class (RealSubject) form a "Pin" structure.
According to the generation time of the agent class, agents can be divided into two types: static agents and dynamic agents.
The following is a simulation requirement to illustrate static agents and dynamic agents: the delegate class needs to process a long-time task, and the client class needs to print out the time it takes to execute the task. To solve this problem, you need to record the time before task execution and the time after task execution. The difference between the two times is the time consumed by task execution.
2. Static proxy
Created by programmers or tools to generate the source code of the proxy class, and then compile the proxy class . The so-called static means that the bytecode file of the proxy class already exists before the program is run, and the relationship between the proxy class and the delegate class is determined before running.
Listing 1: Proxy interface
/** * 代理接口。处理给定名字的任务。 */ public interface Subject { /** * 执行给定名字的任务。 * @param taskName 任务名 */ public void dealTask(String taskName); }
Listing 2: Delegate class , specifically handle business.
/** * 真正执行任务的类,实现了代理接口。 */ public class RealSubject implements Subject { /** * 执行给定名字的任务。这里打印出任务名,并休眠500ms模拟任务执行了很长时间 * @param taskName */ @Override public void dealTask(String taskName) { System.out.println("正在执行任务:"+taskName); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }
Listing 3: Static proxy class
/** * 代理类,实现了代理接口。 */ public class ProxySubject implements Subject { //代理类持有一个委托类的对象引用 private Subject delegate; public ProxySubject(Subject delegate) { this.delegate = delegate; } /** * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间 * * @param taskName */ @Override public void dealTask(String taskName) { long stime = System.currentTimeMillis(); //将请求分派给委托类处理 delegate.dealTask(taskName); long ftime = System.currentTimeMillis(); System.out.println("执行任务耗时"+(ftime - stime)+"毫秒"); } }
Listing 4: Generating static proxy class factory
public class SubjectStaticFactory { //客户类调用此工厂方法获得代理对象。 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。 public static Subject getInstance(){ return new ProxySubject(new RealSubject()); } }
清单5:客户类
public class Client1 { public static void main(String[] args) { Subject proxy = SubjectStaticFactory.getInstance(); proxy.dealTask("DBQueryTask"); } }
静态代理类优缺点
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
三、动态代理
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
1、先看看与动态代理紧密关联的Java API。
1)java.lang.reflect.Proxy
这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
清单6:Proxy类的静态方法
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器 static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:该方法用于判断指定类对象是否是一个动态代理类 static boolean isProxyClass(Class cl) // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
2)java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
清单7:InvocationHandler的核心方法
// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象 // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行 Object invoke(Object proxy, Method method, Object[] args)
3)java.lang.ClassLoader
这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
每次生成动态代理类对象时都需要指定一个类装载器对象
2、动态代理实现步骤
具体步骤是:
a. 实现InvocationHandler接口创建自己的调用处理器
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
清单8:分步骤实现动态代理
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用 InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); // 通过反射从生成的类对象获得构造函数对象 Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); // 通过构造函数对象创建动态代理类实例 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
Proxy类的静态方法newProxyInstance对上面具体步骤的后三步做了封装,简化了动态代理对象的获取过程。
清单9:简化后的动态代理实现
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 直接创建动态代理类实例 Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );
3、动态代理实现示例
清单10:创建自己的调用处理器
/** * 动态代理类对应的调用处理程序类 */ public class SubjectInvocationHandler implements InvocationHandler { //代理类持有一个委托类的对象引用 private Object delegate; public SubjectInvocationHandler(Object delegate) { this.delegate = delegate; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long stime = System.currentTimeMillis(); //利用反射机制将请求分派给委托类处理。Method的invoke返回Object对象作为方法执行结果。 //因为示例程序没有返回值,所以这里忽略了返回值处理 method.invoke(delegate, args); long ftime = System.currentTimeMillis(); System.out.println("执行任务耗时"+(ftime - stime)+"毫秒"); return null; } }
清单11:生成动态代理对象的工厂,工厂方法列出了如何生成动态代理类对象的步骤。
/** * 生成动态代理对象的工厂. */ public class DynProxyFactory { //客户类调用此工厂方法获得代理对象。 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。 public static Subject getInstance(){ Subject delegate = new RealSubject(); InvocationHandler handler = new SubjectInvocationHandler(delegate); Subject proxy = null; proxy = (Subject)Proxy.newProxyInstance( delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), handler); return proxy; } }
清单12:动态代理客户类
public class Client { public static void main(String[] args) { Subject proxy = DynProxyFactory.getInstance(); proxy.dealTask("DBQueryTask"); } }
4、动态代理机制特点
首先是动态生成的代理类本身的一些特点。1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:该类的继承关系如图:
图2:动态代理类的继承关系
As can be seen from the figure, the Proxy class is its parent class. This rule applies to all dynamic proxy classes created by Proxy. And this class also implements a set of interfaces it proxies, which is the fundamental reason why it can be safely type-cast to an interface it proxies.
Next let’s take a look at some characteristics of proxy class instances. Each instance is associated with an invocation handler object. You can obtain the invocation handler object of the proxy class instance through the static method getInvocationHandler provided by Proxy. When the methods declared in the interface of its proxy are called on the proxy class instance, these methods will eventually be executed by the invoke method of the calling processor. In addition, it is worth noting that there are three in the root class java.lang.Object of the proxy class. The methods will also be dispatched to the invoke method of the calling processor for execution, they are hashCode, equals and toString. The possible reasons are: first, because these methods are public and non-final types and can be overridden by the proxy class; second, because these methods Methods often present certain characteristic attributes of a class and have a certain degree of distinction. Therefore, in order to ensure the external consistency of the proxy class and the delegate class, these three methods should also be assigned to the delegate class for execution. When a set of interfaces of a proxy has a repeatedly declared method and the method is called, the proxy class always obtains the method object from the frontmost interface and dispatches it to the calling handler, regardless of whether the proxy class instance is using that interface. (or a sub-interface inherited from this interface) is externally referenced because its current referenced type cannot be distinguished within the proxy class.
Then let’s take a look at the characteristics of a group of interfaces being proxied. First of all, be careful not to have duplicate interfaces to avoid compilation errors when generating dynamic proxy class code. Secondly, these interfaces must be visible to the class loader, otherwise the class loader will not be able to link them, causing the class definition to fail. Thirdly, all non-public interfaces that need to be proxied must be in the same package, otherwise the proxy class generation will also fail. Finally, the number of interfaces cannot exceed 65535, which is a limit set by the JVM.
Finally, let’s take a look at the characteristics of exception handling. From the method declared by the calling processor interface, we can see that in theory it can throw any type of exception, because all exceptions inherit from the Throwable interface, but is this the case? The answer is no, because we must abide by an inheritance principle: that is, when a subclass overrides a method of a parent class or implements a parent interface, the exception thrown must be within the exception list supported by the original method. So although calling the handler is theoretically possible, in practice it is often restricted unless the method in the parent interface supports throwing Throwable exceptions. So what happens if an exception that is not supported in the interface method declaration does occur in the invoke method? Don't worry, the Java dynamic proxy class has already designed a solution for us: it will throw an UndeclaredThrowableException exception. This exception is a RuntimeException type, so it will not cause compilation errors. Through the exception's getCause method, you can also obtain the original unsupported exception object to facilitate error diagnosis.
5. Advantages and disadvantages of dynamic proxy
Advantages:
Compared with static proxies, the biggest advantage of dynamic proxies is that all methods declared in the interface are transferred to a centralized method of the invocation processor (InvocationHandler.invoke). In this way, when there are a large number of interface methods, we can flexibly handle them without having to transfer each method like a static proxy. It cannot be seen in this example because specific peripheral services are embedded in the invoke method body (recording the time before and after task processing and calculating the time difference). In practice, peripheral services can be configured similar to Spring AOP.
Flaws in the ointment:
It is true that Proxy has been designed very beautifully, but there is still a little regret, that is It has never been able to get rid of the shackles of only supporting interface proxy, because its design is doomed to this regret. Recall the inheritance diagram of those dynamically generated proxy classes. They are destined to have a common parent class called Proxy. Java's inheritance mechanism is destined that these dynamic proxy classes cannot implement dynamic proxy for classes. The reason is that multiple inheritance essentially does not work in Java.
There are many reasons why people can deny the necessity of class proxies, but there are also some reasons to believe that supporting class dynamic proxies would be better. The division between interfaces and classes is not very obvious to begin with. It is only in Java that it becomes so detailed. If we only consider the method declaration and whether it is defined, there is a mixture of the two, and its name is abstract class. I believe that implementing dynamic proxy for abstract classes also has its inherent value. In addition, there are some classes left over from history, which will never be associated with dynamic agents because they do not implement any interfaces. With all this going on, it has to be said that it is a small regret.
If you want to read more related articles, please visit PHP Chinese website! !
The above is the detailed content of What is the difference between static proxy and dynamic proxy?. For more information, please follow other related articles on the PHP Chinese website!