代理是一种设计模式,会控制目标对象的访问,提供了目标对象另外的访问方式。即通过代理对象访问目标对象。
这样做的好处是,可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
1. 静态代理
代理模式的作用
为其他对象提供一种代理以控制这个对象的访问。
模式结构
-
抽象角色
声明真实对象和代理对象的共同接口
-
代理角色
代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于真实对象进行封装
-
真实角色
代理角色所代表的真实对象,是我们最终要引入的对象
1 package 静态代理; 2 3 // 抽象角色 4 public abstract class Subject { 5 public void request() {}; 6 }
1 package 静态代理; 2 3 /** 4 *真实的角色 5 * */ 6 public class RealSubject extends Subject { 7 @Override 8 public void request() { 9 System.out.println("执行 --> 真实角色"); 10 } 11 }
1 package 静态代理; 2 3 /** 4 * 静态代理,对具体真实对象直接引用 5 * 代理角色,代理角色需要有对真实角色的引用 6 * 代理做真实角色想做的事情 7 * */ 8 public class ProxySubject extends Subject { 9 private RealSubject realSubject = null; 10 11 /** 12 * 除了代理真实角色做该做的事情,代理角色也可以提供附加操作 13 * 如:preRequest() 和 postRequest() 14 * */ 15 @Override 16 public void request() { 17 preRequest(); // 真实角色操作前的附加操作 18 19 if(realSubject == null) { 20 realSubject = new RealSubject(); 21 } 22 realSubject.request(); 23 24 postRequest(); // 真实⻆⾊操作后的附加操作 25 } 26 27 /** 28 * 真实角色操作前的附加操作 29 * */ 30 private void preRequest() { 31 System.out.println("执行 --> 真实角色操作前的附加操作"); 32 } 33 34 /** 35 * 真实角色操作后的附加操作 36 * */ 37 private void postRequest() { 38 System.out.println("执行 --> 真实角色操作后的附加操作"); 39 } 40 }
1 package 静态代理; 2 3 /** 4 * 客户端调用 5 * */ 6 public class main { 7 public static void main(String[] args) { 8 Subject subject = new ProxySubject(); 9 subject.request(); // 代理者代替真实做的事情 10 } 11 }
静态代理的优缺点
优点
可以做到在不修改目标对象的功能前提下,对目标功能扩展
缺点
每一个代理类都必须实现一遍委托类(也就是 RealSubject )的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。
2. jdk 动态代理
动态代理解决了静态代理中代理类接口过多的问题,通过反射来实现的,借助 java 自带的 java.lang.reflect.Proxy,通过固定的规则生成。
步骤编写如下:
-
编写一个委托类接口,即静态代理的(
Subject接口) -
实现一个真正的委托类,即静态代理的(
RealSubject接口) -
创建一个动态代理类,实现
InvocationHandler接口,并重写该invoke方法 -
在测试类中,生成动态代理的对象。
第一、二步骤和静态代理一样:
1 package 动态代理; 2 3 // 委托类接口 4 interface Subject { 5 void request(); 6 }
1 package 动态代理; 2 3 // 委托类接口的实现类 4 public class RealSubject implements Subject { 5 @Override 6 public void request() { 7 System.out.println("执行 --> 实现委托"); 8 } 9 }
第三步:
1 package 动态代理; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 // 创建一个动态代理类,实现 InvocationHandler 接口 7 // 并重写 invoke 方法 8 public class DynamicProxy implements InvocationHandler { 9 private Object object; 10 11 public DynamicProxy(Object object) { 12 this.object = object; 13 14 } 15 @Override 16 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 17 Object result = method.invoke(object, args); 18 return result; 19 } 20 }
第四步,创建动态代理的对象
1 package 动态代理; 2 3 import java.lang.reflect.Proxy; 4 5 /** 6 * jdk 动态代理主要是实现 InvocationHandler 7 * 使用: 8 * 在使用 jdk 动态代理之前,还是如静态代理一样,搞一个抽象角色 Subject 和具体的实现类 RealSubject, 9 * 不一样的是,写代理类的时候是实现 InvocationHandler,不需要去扩展原来的抽象类 10 * 实现了 InvocationHandler,在业务调用的时候,通过 Proxy.newProxyInstance(类加载器, 抽象类的类型, proxy) 去使用的, 11 * */ 12 public class main { 13 public static void main(String[] args) { 14 Subject realSubject = new RealSubject(); 15 DynamicProxy proxy = new DynamicProxy(realSubject); 16 ClassLoader classLoader = realSubject.getClass().getClassLoader(); 17 Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new Class[]{Subject.class}, proxy); 18 subject.request(); 19 } 20 } 21 22 /** 23 输出: 24 执行 --> 实现委托 25 */
上述代码的关键是 proxy.newProxyInstance(ClassLoader loader, Class<?> interfaces, InvocationHandler handler) 方法,该方法会根据指定的参数动态创建代理对象。三个参数的意义如下:
-
loader,指定代理对象的类加载器
-
interfaces,代理对象需要实现的接口,可以同时指定多个接口
-
handler,方法调用的实际处理者,代理对象的方法调用都会转发到这里。
结论:
jdk动态代理是通过反射生成一个代理类,在业务里面调用subject.request();的时候,会调用反射生成的子类,会把代理方法放在前后,真实方法放在中间。也就是说,
jdk动态代理其实就是使用InvocationHandler和反射生成了代理的匿名类,当调用具体的方法前,会调用InvocationHandler来处理,这个时候就实现了对接口的代理。
3. cglib 代理
加入我们有一个没有实现任何接口的类 HelloConcrete
1 package cglib; 2 3 public class HelloConcrete { 4 public String sayHello(String str) { 5 return "HelloConcrete:" + str; 6 } 7 }
1 package cglib; 2 3 import java.lang.reflect.Method; 4 import java.util.Arrays; 5 6 /** 7 * CGLIB 动态代理 8 * 1. 首先实现一个 MethodInterceptor,方法调用会被转发到该类的 intercept() 方法 9 */ 10 public class MyMethodInterceptor implements MethodInterceptor { 11 // TODO SOMETHINGS 12 @Override 13 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 14 logger.info("You say:" + Arrays.toString(args)); 15 return proxy.invokeSuper(obj, args); 16 } 17 }
1 package cglib; 2 3 /** 4 * 2. 然后在需要使⽤HelloConcrete的时候,通过 CGLIB 动态代理获取代理对象 5 */ 6 public class main { 7 public static void main(String[] args) { 8 Enhancer enhancer = new Enhancer(); 9 enhancer.setSuperclass(HelloConcrete.class); 10 enhancer.setCallback(new MyMethodInterceptor()); 11 12 HelloConcrete hello = (HelloConcrete) enhancer.create(); 13 System.out.println(hello.sayHello("I LOVE YOU!")); 14 15 } 16 }
通过 CGLIB 的 Enhancer 来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用 create() 方法得到代理对象,对这个对象所有非 final 方法的调用都会转发给 MethodInterceptor.intercept() 方法,在 intercept() 方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;通过调用 MethodProxy.invokeSuper() 方法,我们将调用转发给原始对象,具体到本例,就是 HelloConcrete 的具体方法。
对于从 Object 中继承的方法,CGLIB 代理也会进行代理,如 hasCode() 、equals() 、toString() 等,但是 getClass() 、wait() 等方法不会,因为它是 final 方法,CGLIB 无法代理。
原理
CGLIB 是一个强大的高性能的代码生成包,底层是通过使用一个小而快的字节码处理框架 ASM ,它可以在运行期扩展 Java 类与实现 Java 接口。
Enhancer 是 CGLIB 的字节码增强器,可以很方便的对类进行扩展
创建代理对象的几个步骤:
-
生成代理类的二进制字节码文件
-
加载二进制字节码,生成 Class 对象(例如使用
Class.forName()方法) -
通过反射机制获得实例构造,并创建代理类对象
简单来说,
cglib是通过代码生成包,底层是ASM,在运行期间,会扩展我们的 Java 类和实现 Java 接口,用Enhancer这个cglib字节码的增强器,会对类进行扩展,cglib其实是通过ASM开源包在字节上去操作,生成一个子类去覆盖我们原有的方法,因为cglib是使用的继承,目标类没有实现接口也可以用cglib,但是由于是使用的继承,对 final 方法或者 final 类是没办法继承的,也没办法增强。
jdk 代理与 cglib 代理的区别
-
在实现上,
CGLIB是实现MethodInterceptor类,jdk代理是实现InvocationHandler类。 -
jdk动态代理,目标对象必须实现了接口,如果目标对象没有实现接口,只能使用cglib代理。
总结
JDK 动态代理
利用拦截器(拦截器必须实现 InvocationHanlder )加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。只能对实现了接口的类生成代理生成代理。
CGLIB 代理
利用 ASM 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,对于 final 类或方法,是无法继承的。
选择
-
如果目标对象实现了接口,默认情况下会采用
JDK的动态代理实现AOP。 -
如果目标对象实现了接口,可以强制使用
CGLIB实现AOP -
如果目标对象没有实现接口,必须采用
CGLIB库,Spring 会自动在JDK动态代理和CGLIB之间转换。
浙公网安备 33010602011771号