暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

设计模式之代理模式

Serenest Person 2020-04-09
170

今天聊下代理模式。

类图:

百度:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。


优点

1、隔离作用:客户类不想或者不能直接引用目标类,可以通过代理类来间接引用到目标类,起到隔离的作用。

2、可以拓展功能:目标类只能完成特定的功能,通过代理类引用目标类可以增加一些功能,符合开闭原则。


代理模式分为静态代理和动态代理两大类。



静态代理

/**
* 接口类
*/
public interface ITarget {
void eat();
}
/**
* 目标类
*/
public class Target implements ITarget {
@Override
public void eat() {
System.out.println("吃饭");
}
}


/**
 * 代理类 增加了洗手和擦嘴的操作
*/
public class Poxy implements ITarget {
private ITarget target;


public Poxy(ITarget target) {
this.target = target;
}


@Override
public void eat() {
System.out.println("洗手");
target.eat();
System.out.println("擦嘴");
}
}
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
ITarget target=new Target();
Poxy poxy = new Poxy(target);
poxy.eat();
}
}

输出结果:

"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.4\lib\idea_rt.jar=53809:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;E:\java_study\mavenProject\selfmybaitis\target\classes;E:\java_study\repository\repository\log4j\log4j\1.2.12\log4j-1.2.12.jar;E:\java_study\repository\repository\dom4j\dom4j\1.6.1\dom4j-1.6.1.jar;E:\java_study\repository\repository\xml-apis\xml-apis\1.0.b2\xml-apis-1.0.b2.jar;E:\java_study\repository\repository\mysql\mysql-connector-java\5.1.6\mysql-connector-java-5.1.6.jar;E:\java_study\repository\repository\jaxen\jaxen\1.1.6\jaxen-1.1.6.jar;E:\java_study\repository\repository\cglib\cglib\2.2.2\cglib-2.2.2.jar;E:\java_study\repository\repository\asm\asm\3.3.1\asm-3.3.1.jar" org.example.jingtaipoxy.dy.Test
洗手
吃饭
擦嘴

可以看出,通过静态代理类,为目标类增加拓展了功能,而没有改变目标类。符合开闭原则。但是,缺点是代理类和目标类都要实现接口,如果接口中增加方法,需要去维护目标类和代理类,维护成本较高。通过动态代理可以解决这个问题。


动态代理

在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JVM再运行时为我们动态的来创建。

接口类和目标类不变。增加了TargetHandler,修改了Test,新增的FactoryPoxy为了创建poxy对象的工厂方法,不用他,直接创建也是可以的。

/**
* 动态处理器
*/
public class TargetHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("洗手");
method.invoke(target,args);
System.out.println("擦嘴");
return null;
}
}
/**
* 修改后的test
*/
public class Test {
public static void main(String[] args) {
ITarget iTarget=new Target();
        //通过工厂方法创建动态代理对象,
        //在上面的invoke通过反射执行目标类的方法,增加操作
ITarget poxy = (ITarget) FactoryPoxy.getPoxy(iTarget);
        poxy.eat();
}
}
/**
* poxy工厂类
*/
public class FactoryPoxy {
public static Object getPoxy(Object target){
TargetHandler targetHandler= new TargetHandler();
targetHandler.setTarget(target);
/**
* 参数1:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
* 参数2:指定目标对象实现的接口的类型,使用泛型方式确认类型
* 参数3:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
*/
Object poxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetHandler);
return poxy;
}
}

输出结果:

"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.4\lib\idea_rt.jar=53809:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;E:\java_study\mavenProject\selfmybaitis\target\classes;E:\java_study\repository\repository\log4j\log4j\1.2.12\log4j-1.2.12.jar;E:\java_study\repository\repository\dom4j\dom4j\1.6.1\dom4j-1.6.1.jar;E:\java_study\repository\repository\xml-apis\xml-apis\1.0.b2\xml-apis-1.0.b2.jar;E:\java_study\repository\repository\mysql\mysql-connector-java\5.1.6\mysql-connector-java-5.1.6.jar;E:\java_study\repository\repository\jaxen\jaxen\1.1.6\jaxen-1.1.6.jar;E:\java_study\repository\repository\cglib\cglib\2.2.2\cglib-2.2.2.jar;E:\java_study\repository\repository\asm\asm\3.3.1\asm-3.3.1.jar" org.example.jingtaipoxy.dy.Test
洗手
吃饭
擦嘴

使用jvm的动态代理,我们没有显式的创建代理对象,而是创建了一个处理器对象,通过反射拿到目标对象的方法,在目标对象方法执行的时候,触发处理器对象中的方法。

使用jvm的动态代理,不用实现目标接口,当目标接口增加的时候,也就减少了对代理对象的维护,达到了低耦合的效果。但是,还有一点不足,那就是目标对象必须要实现接口,否则不能使用动态代理。

那么没有实现接口的类,怎么实现动态代理呢?


Cglib代理

其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,也叫作子类代理。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

public class Test {
public static void main(String[] args) {
//实例化一个增强器,也就是cglib中的一个class generator
Enhancer enhancer = new Enhancer();
//设置目标类
enhancer.setSuperclass(Target.class);
//设置拦截对象,这里直接使用匿名内部类写法
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
String extrastr = null;
switch (method.getName()){
case "eat":
extrastr="好好吃饭";
break;
case "run":
extrastr="好好锻炼";
break;
case "study":
extrastr="好好学习";
break;
}
System.out.println(extrastr);
methodProxy.invokeSuper(o,objects);
return null;
}
});
Target target = (Target) enhancer.create();
target.eat();
target.run();
target.study();
}
}

输出结果:

"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.4\lib\idea_rt.jar=54102:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;E:\java_study\mavenProject\selfmybaitis\target\classes;E:\java_study\repository\repository\log4j\log4j\1.2.12\log4j-1.2.12.jar;E:\java_study\repository\repository\dom4j\dom4j\1.6.1\dom4j-1.6.1.jar;E:\java_study\repository\repository\xml-apis\xml-apis\1.0.b2\xml-apis-1.0.b2.jar;E:\java_study\repository\repository\mysql\mysql-connector-java\5.1.6\mysql-connector-java-5.1.6.jar;E:\java_study\repository\repository\jaxen\jaxen\1.1.6\jaxen-1.1.6.jar;E:\java_study\repository\repository\cglib\cglib\2.2.2\cglib-2.2.2.jar;E:\java_study\repository\repository\asm\asm\3.3.1\asm-3.3.1.jar" org.example.jingtaipoxy.cglib.Test
好好吃饭
吃饭
好好锻炼
跑步
好好学习
学习


CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

在Spring的AOP编程中:

如果加入容器的目标对象有实现接口,用JDK代理

如果目标对象没有实现接口,用Cglib代理。



本文参考:

https://www.cnblogs.com/daniels/p/8242592.html

https://www.cnblogs.com/jie-y/p/10732347.html

文章转载自Serenest Person,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论