今天聊下代理模式。
类图:

百度:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
优点:
1、隔离作用:客户类不想或者不能直接引用目标类,可以通过代理类来间接引用到目标类,起到隔离的作用。
2、可以拓展功能:目标类只能完成特定的功能,通过代理类引用目标类可以增加一些功能,符合开闭原则。
代理模式分为静态代理和动态代理两大类。

静态代理
/*** 接口类*/public interface ITarget {void eat();}/*** 目标类*/public class Target implements ITarget {@Overridepublic void eat() {System.out.println("吃饭");}}/*** 代理类 增加了洗手和擦嘴的操作*/public class Poxy implements ITarget {private ITarget target;public Poxy(ITarget target) {this.target = target;}@Overridepublic 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;}@Overridepublic 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 generatorEnhancer enhancer = new Enhancer();//设置目标类enhancer.setSuperclass(Target.class);//设置拦截对象,这里直接使用匿名内部类写法enhancer.setCallback(new MethodInterceptor() {@Overridepublic 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




