Java基础——动态代理
1、动态代理
动态代理的目标就是在运行时改变目标类,对目标类的参数或者方法做扩展
当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强
Java中常用的动态代理的实现方式:一种是利用JDK反射机制生成代理,另外一种是使用CGLIB代理
JDK代理必须要提供接口,而CGLIB则不需要,可以直接代理类
反射机制:程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法
动态代理使用场景:Spring 的 AOP
JavaBean和jsp之间调用
Struts的 FormBean 和页面之间调用
JDBC 的 classForName()
Hibernate的 find(Class clazz)
................
2、JDK反射机制
JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现
Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多
JDK Proxy 是通过拦截器加反射的方式实现的
JDK Proxy 只能代理继承接口的类
JDK Proxy 实现和调用起来比较简单
JDK反射实现:实现 InvocationHandler 接口,重写 invoke() 方法
1 //定义方法接口 2 public interface IA { 3 public void A1(String s); 4 public void A2(String s); 5 } 6 7 //实现IA接口 8 public class IATest implements IA{ 9 @Override 10 public void A1(String s) { 11 System.out.println("this is IATest implements IA method A1() &&"+s); 12 } 13 14 @Override 15 public void A2(String s) { 16 17 System.out.println("this is IATest implements IA method A2() &&"+s); 18 } 19 } 20 21 //重写 Invocation 接口的invoke()方法 22 public class IBTest implements InvocationHandler { 23 private Object object;//代理对象 24 25 // 构造方法,给我们要代理的真实对象赋初值 26 public Object IBTest(Object object){ 27 this.object = object; 28 //获得代理对象 29 Object o = Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this); 30 return o; 31 } 32 @Override 33 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 34 Object o ; 35 // 在代理真实对象前我们可以添加一些自己的操作 36 System.out.println("Method:" + method.getName()); 37 // 如果方法是 A1 38 if (method.getName().equals("A1")) { 39 String r = "B1"; 40 o = method.invoke(object, r); 41 System.out.println("IB after IA.A1"); 42 43 } else { 44 // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 45 String r = "B2"; 46 o = method.invoke(object, r); 47 System.out.println("IB after IA.A2"); 48 49 } 50 System.out.println("IB is end"); 51 return o; 52 } 53 } 54 55 //测试代码 56 public static void main (String[] args){ 57 IBTest ibTest = new IBTest(); 58 IA ia = (IA)ibTest.IBTest(new IATest()); 59 ia.A1("A1"); 60 ia.A2("A2"); 61 }
运行结果:
Method:A1
this is IATest implements IA method A1() &&B1
IB after IA.A1
IB is end
Method:A2
this is IATest implements IA method A2() &&B2
IB after IA.A2
IB is end
//InvocationHandler 接口源码 public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } 在动态代理中有一个重要的角色——代理器,它用于统一管理被代理的对象,InvocationHandler 就是这个代理器,而 invoke() 方法则是触发代理的执行方法,我们通过实现 Invocation 接口来拥有动态代理的能力
3、CGLIB代理
CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高
CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的
//先引入第三方辅助 <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
package com.lagou.interview; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CGLibExample { static class Car { public void running() { System.out.println("The car is running."); } } /** * CGLib 代理类 */ static class CGLibProxy implements MethodInterceptor { private Object target; // 代理对象 public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); // 设置父类为实例类 enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("方法调用前业务处理."); Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用 return result; } } // 执行 CGLib 的方法调用 public static void main(String[] args) { // 创建 CGLib 代理类 CGLibProxy proxy = new CGLibProxy(); // 初始化代理对象 Car car = (Car) proxy.getInstance(new Car()); // 执行方法 car.running();
CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败
4、其他
静态代理其实就是事先写好代理类,可以手工编写也可以使用工具生成,但它的缺点是每个业务类都要对应一个代理类,特别不灵活也不方便,于是就有了动态代理
Spring 框架中同时使用了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,我们也可以在配置中指定强制使用 CGLib,只需要在 Spring 配置中添加 <aop:aspectj-autoproxy proxy-target-class="true"/>