文章目录
代理定义初步了解
定义
代理模式是一种结构型设计模式,其目的是在访问对象时引入一个代理对象,这个代理对象可以控制对原始对象的访问,并且可以在访问前后执行一些额外的操作。代理模式通常用于在不改变原始对象的情况下,为其提供额外的功能,或者对其访问进行控制,以满足特定需求。
比如:当拍电影的时候,演员寻找替身演员,就是一个代理模式。对于观众来说,只是看到了电影中的危险动作的完成,不会区分演员和替身演员。
//该接口为目标对象和代理对象都需要实现的接口
//客户端只是面向接口编程
public interface 表演接口{
void 完成危险动作();
}
public class 演员 implements 表演接口{
void 完成危险动作(){
}
}
public class 替身演员 implements 表演接口{
void 完成危险动作(){
}
}
在java中使用代理模式的作用
1、当一个对象需要受到保护时,可以使用代理模式完成某个行为。
2、需要给某个对象的功能进行功能增强的时候,可以找一个代理实现功能增强。
3、A和B对象无法直接交互时,可以使用代理模式沟通。
代理模式中的角色
代理对象:替身演员
目标对象:演员
公共接口:危险动作
静态代理
形象理解静态代理
1、既然替身演员和真实演员之间是完成危险动作的合作,那么就必须有DangerousMove这个接口。
package staticProxyMethod;
/**
* @author Zonda
* @version 1.0
*/
public interface DangerousMove {
//这是三个危险动作:跳、滚、打。
void jump();
void roll();
void fight();
}
2、最后想要电影完成,真实演员起码要完成除了危险动作之外的情节
package staticProxyMethod;
/**
* @author Zonda
* @version 1.0
*/
public class RealActor implements DangerousMove{
//这是真实演员,只负责完成三个高危动作前后的片段
@Override
public void jump() {
System.out.println("真实演员演了jump片段前的内容");
}
@Override
public void roll() {
System.out.println("真实演员演了roll片段前的内容");
}
@Override
public void fight() {
System.out.println("真实演员演了fight片段前的内容");
}
}
3、替身演员需要完成危险动作
package staticProxyMethod;
/**
* @author Zonda
* @version 1.0
*/
public class RepresentActor_Proxy implements DangerousMove{
//这是替身演员,也是代替完成三个高危动作的代理
//为了形成完整的片段,还需要引入真实演员的片段,和高危片段剪辑才能形成最后的电影。
private RealActor realActor;
//通过构造器引入替身演员对象。
public RepresentActor_Proxy(RealActor realActor) {
this.realActor = realActor;
}
@Override
public void jump() {
realActor.jump();
System.out.println("替身演员完成jump");
}
@Override
public void roll() {
realActor.roll();
System.out.println("替身演员完成roll");
}
@Override
public void fight() {
realActor.fight();
System.out.println("替身演员完成fight");
}
}
4、对于观众而言看电影就好啦
package staticProxyMethod;
/**
* @author Zonda
* @version 1.0
*/
public class Audience {
public static void main(String[] args) {
watchMovie();
}
public static void watchMovie(){
RealActor realActor = new RealActor();
RepresentActor_Proxy representActor_proxy = new RepresentActor_Proxy(realActor);
representActor_proxy.jump();
representActor_proxy.roll();
representActor_proxy.fight();
}
}
5、执行代码,观众看电影,电影完美呈现给观众了!
静态代理的优缺点
优点:符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。
缺点:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。
解释一下就是:一部电影事实上除了要找特技演员,还要找导演、剧组、赞助商等等和演员配合完成电影创作。导演、剧组、赞助商都需要写一个代理类代表他们的职能,每一个职能对应着一个接口。这样会导致需要定义的职能太多了。
解决方法:动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用。
解释一下就是:找一个电影公司也就是我们的动态代理,为每一步电影量身打造导演、剧组、赞助商、特技演员等等,一下子全给解决了。不用搞那么多职能了,演员只要和电影公司签约就行了。
动态代理
定义
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类(不同电影公司,好莱坞、宝莱坞、东京热等等(此处战术打码)的技术常见的包括:
1、JDK动态代理技术:只能代理接口。
2、CGLIB动态代理技术:既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
3、Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。
JDK动态代理
形象理解JDK动态代理
在以上的拍电影的例子中,可以使用动态代理动态生成RepresentActor_Proxy,这个类不需要写,我们直接写观众(客户端)的程序就行了。
以下代码就是观众(客户端)的代码,
OrderService orderServiceProxy = Proxy.newProxyInstance(realActor.getClass().getClassLoader(), realActor.getClass().getInterfaces(),调用处理器对象);直接动态生成了代理类。这个过程代码做了两件事儿:
1、第一件事:在内存中生成了代理类的字节码,也就是电影公司为某部电影量身联系了一位特技演员。
2、第二件事:创建代理对象,电影公司聘请了这位特技演员出演这部电影。
Proxy类全名:java.lang.reflect.Proxy。这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。
其中newProxyInstance()方法有三个参数:
1、第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载(类加载器加载的是目标对象的类的字节码,也就是谁需要这个代理类,当然是真实演员)。
2、第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。(通过接口指明真实演员需要特技演员演的动作有:jump、roll、fight)
3、第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。需要调用处理器也就是电影公司出面去请特技演员了。
这三者都具备了才能生成一个代理类(特技演员)。
package staticProxyMethod;
import java.lang.reflect.Proxy;
/**
* @author Zonda
* @version 1.0
*/
public class AudienceProxy {
public static void main(String[] args) {
}
public static void watchMovie(){
RealActor realActor = new RealActor();
//DangerousMoveInvocationHandler为调用处理器对象
Proxy.newProxyInstance(realActor.getClass().getClassLoader(),
realActor.getClass().getInterfaces(),DangerousMoveInvocationHandler)
}
}
**调用处理器对象的作用:**我们在创建动态代理的时候,还没有具体地去调用jump、roll、fight中的某一个方法,当动态代理创建了才会去调用方法,这个InvocationHandler参数就是用于让代理类知道要调用哪一个方法以及对这个要调用的方法进行增强。
调用处理器内有一个invoke方法,在调用代理对象中的方法的时候被调用,这个invoke方法上有三个参数:
1、第一个参数:Object proxy。代理对象,指明是哪一个特技演员。
2、第二个参数:Method method。目标方法,指明演jump()、roll()、fight()哪个动作。
3、第三个参数:Object[] args。目标方法调用时要传的参数。如果jump()、roll()、fight()方法中有参数会传入进去。但是这里没有。
通俗讲就是:演电影的过程中,特技演员在电影的不同拍摄阶段要知道自己具体做什么特技动作。
至于调用处理器对象的细节是啥样的,往下面看。
package staticProxyMethod;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author Zonda
* @version 1.0
*/
public class DangerousMoveInvocationHandler implements InvocationHandler {
//通过target接受目标类对象,也就是我们的RealActor的对象,因为我们要用里面的方法,还有可能需要对它进行增强。
private Object target;
public DangerousMoveInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里的method就是接口里的方法,因为目标类和代理类都实现了同一接口,因此方法名相同。这里其实就是让真正的演员先演特技前的情节。
method.invoke(target,args);
//特技演员完成危险动作,增强方法。
System.out.println("替身演员完成"+method.getName());
return null;
}
}
使用代理之后的观众(客户端)的代码,在这部分代码中只需要创建代理对象对象并调用方法就行了。电影公司提供了特技演员,本来就有真正的演员,两两合作,拍就完事儿了。
package staticProxyMethod;
import java.lang.reflect.Proxy;
/**
* @author Zonda
* @version 1.0
*/
public class AudienceProxy {
public static void main(String[] args) {
watchMovie();
}
public static void watchMovie(){
1、真实演员先演出
RealActor realActor = new RealActor();
2、特技演员再根据真实演员演的内容准备
Object o = Proxy.newProxyInstance(realActor.getClass().getClassLoader(),
realActor.getClass().getInterfaces(),
new DangerousMoveInvocationHandler(realActor));
//因为都是实现的DangerousMove接口,需要向下转型DangerousMove类型
DangerousMove dangerousMove = (DangerousMove)o;
3、分别演三个特技,这个时候才调用的invoke
//三个方法都没有传入参数。因此invoke方法中的args参数为空。
//invoke方法中的Object proxy为dangerousMove
//invoke方法中的Method method为jump()、roll()、fight()
dangerousMove.jump();
dangerousMove.roll();
dangerousMove.fight();
}
}
最后打印出来结果为,和静态代理机制一样:
既然和静态代理机制一样,为什么还要忙活半天去写动态代理机制的代码呢?
动态代理的好处
如果又有一个新的电影,这个电影的主演需要做其他的特技,那么这个主演只需要演好自己的戏,剩下来的交给电影公司,找一个代理特技演员去完成就行了,拍电影的时候工作量直接减少一半。也就是说,代码的复用性得到增强了,降低了代码的耦合度。
封装代理对象创建过程
在拍电影的时候才想起来请特技演员显然不是一个明智的选择。
RealActor realActor = new RealActor();
Proxy.newProxyInstance(realActor.getClass().getClassLoader(),
realActor.getClass().getInterfaces(),DangerousMoveInvocationHandler)
因此要提前请好,这样需要一个封装类中的方法专门用来提前请好请特技演员。
public class ProxyUtil {
public static Object returnProxy(RealActor realActor) {
return Proxy.newProxyInstance(realActor.getClass().getClassLoader(),
realActor.getClass().getInterfaces(),
new DangerousMoveInvocationHandler(realActor));
}
}
请好之后再拍电影给观众看。
package staticProxyMethod;
import java.lang.reflect.Proxy;
/**
* @author Zonda
* @version 1.0
*/
public class AudienceProxy {
public static void main(String[] args) {
watchMovie();
}
public static void watchMovie(){
RealActor realActor = new RealActor();
Object o = ProxyUtil.returnProxy(realActor);
DangerousMove dangerousMove = (DangerousMove)o;
dangerousMove.jump();
dangerousMove.roll();
dangerousMove.fight();
}
}