一、代理模式
代理模式是Java常用的设计模式,其特征是代理类与委托类(被代理类)有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类、以及事后处理消息等。代理类和委托类之间会存在关联关系,代理类的对象本身并不真正实现服务,而是通过调用代理类对象特定的方法来提供服务。简单的说,我们访问实际对象的通过代理对象来访问的,代理模式在访问对象时引入一定程度的间接性,就是因为这种间接性,所以可以附加多种用途!
举个例子:
有一个班级,每个成员有不同的能力,比如绘画、摄影、主持节目等,这个团队有一个队长,队长负责与外界合作,人们寻找能主持节目的人直接找队长就行,队长本身并不提供主持节目的服务,队长是让团队会主持节目的成员提供服务,但是队长可以在提供主持节目服务的前前后后进行一些相关事宜。那么这里的队长就是代理类,各个成员就是委托类;
代理分类:
静态代理和动态代理;
二、静态代理
1、静态代理
是由程序员创建或特定工具生成源代码,也就是在编译时就已经将接口、委托类、代理类等确定下来,在程序运行之前。代理类的.class文件就已经生成。
2、静态代理的简单实现
根据上面的例子,我们将能主持节目的人称为支持人,我们将队长称为队长,主持人就是委托类,队长就是代理类;
第一步:我们创建一个接口Person
此接口是主持人(委托类)和队长(代理类)的公共接口,他们都有主持节目的行为(只是队长主持节目的行为也是从主持人哪里借来的,只是作为主持人的代理,这种主持节目的行为被附加在了队长身上)
package com.zibo.design_mode.agency;
//这是代理类和被代理类的公共接口
public interface Person {
//主持节目
void host_a_program();
}
第二步:创建主持人类Compere
实现Person接口,具体实现主持节目的方法;
package com.zibo.design_mode.agency;
//主持人
public class Compere implements Person {
@Override
public void host_a_program() {
System.out.println("主持人主持了节目!");
}
}
第三步:创建队长类Captain
实现Persion接口,其具体实现主持节目的方法是调用主持人主持节目的方法;
package com.zibo.design_mode.agency;
public class Captain implements Person {
private Person compere;
public Captain(Person compere) {
//只代理主持人
if(compere.getClass() == Compere.class){
this.compere = compere;
}
}
@Override
public void host_a_program() {
//队长调用主持人主持节目的行为
compere.host_a_program();
}
}
静态代理测试类StaticProxyTest:
package com.zibo.design_mode.agency;
public class StaticProxyTest {
public static void main(String[] args) {
//创建主持人
Person compere = new Compere();
//创建队长
Person captain = new Captain(compere);
//队长调用主持人主持节目的行为
captain.host_a_program();//主持人主持了节目!
}
}
3、综述
这里并没有通过主持人直接跟人们对接,去提供主持节目的服务,而是由队长作为中间人(也就是主持人的代理人)去促进双方的合作,队长的作用是使双方的合作过程更加顺畅,对合作事宜进行优化,比如主持人在与人们达成合作之前需要队长对其进行宣传介绍等工作,在合作之后有需要队长处理一些事宜,也就是代理类对委托类的一些功能进行增强!
三、动态代理
1、动态代理
代理类在程序运行时被创建的方式成为动态代理。我们静态代理的例子代理类是自己定义好的,在程序运行之前就已经便已完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便地对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。比如说,我们想要在每个代理类的前面都增加一个方法:
public void host_a_program() {
//在每个代理类前面都增加一个方法
beforeMethod();
//队长调用主持人主持节目的行为
compere.host_a_program();
}
这里只有一个comere.host_a_program()方法,但是如果有很多方法,就要每个方法前面都加上前置的方法,就会很麻烦;所以我们就可以使用动态代理的方式来解决这个问题;
2、动态代理的简单实现
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象;
创建一个动态代理对象的步骤:
第一步:创建一个InvocationHandler对象:
//创建一个与被代理对象相关联的InvocationHandler
InvocationHandler compereHandler = new ComInvocationHandler(compere);
第二步:使用Proxy类的getProxyClass静态方法生成一个动态代理类comProxyClass:
Class<?> comProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
第三步:获得comProxyClass中一个带InvocationHandler参数的构造器constructor:
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
第四步:通过构造器constructor来创建一个动态实例stuProxy:
Person stuProxy = (Person) cons.newInstance(stuHandler);
简化步骤:
可以通过Proxy类的newProxyInstances方法来简化:
//创建一个与被代理对象相关联的InvocationHandler
InvocationHandler comHandler = new MyInvocationHandler<Person>(com);
//创建一个代理对象comProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person comProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, comHandler);
3、简单案例
主持人类Compere:
package com.zibo.design_mode.agency;
//主持人
public class Compere implements Person {
@Override
public void host_a_program() {
try {
//假设支持节目花了1秒时间
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("主持人主持了节目!");
}
}
计时工具类MonitorUtil:
package com.zibo.design_mode.agency;
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");
}
}
公共接口Person:
package com.zibo.design_mode.agency;
//这是代理类和被代理类的公共接口
public interface Person {
//主持节目
void host_a_program();
}
ComInvocationHandler类:
package com.zibo.design_mode.agency;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ComInvocationHandler implements InvocationHandler {
//InvocationHandler持有的被代理对象
Object target;
//带参构造函数
public ComInvocationHandler(Object target) {
this.target = target;
}
/**
*
* @param proxy 动态代理的对象
* @param method 正在执行的方法
* @param 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;
}
}
测试类ProxyTest:
package com.zibo.design_mode.agency;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//创建一个主持人对象——被代理对象
Person compere = new Compere();
//创建一个与被代理对象相关联的InvocationHandler
InvocationHandler compereHandler = new ComInvocationHandler(compere);
//创建一个队长来代理主持人,代理对象执行的每个方法都会替换执行Invocation中的invoke方法
Person captain = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),new Class<?>[]{Person.class},compereHandler);
//代理执行方法
captain.host_a_program();
}
}
运行结果:
代理执行host_a_program方法
主持人主持了节目!
host_a_program方法耗时1001ms
说明:
上面说的动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。
动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,弄清楚其中缘由。
四、动态代理原理分析
见https://www.cnblogs.com/gonjan-blog/p/6685611.html