代理模式(ProxyPattern)的定义也非常简单,是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客服端和目标对象之间起到中介作用,代理模式属于结构型设计模式。使用代理模式主要有两个目的:一保护目标对象,二增强目标对象。
代理模式分为静态代理和动态代理,动态代理有2种实现方式,JDK动态代理和Cglib动态代理。
public interface ISubject { public void sayHello() ; }
public class RealSubject implements ISubject{ @Override public void sayHello() { System.out.println("RealSubject.sayHello ... "); } }
public class ProxySubject implements ISubject { private ISubject subject; public ProxySubject(ISubject subject) { this.subject = subject; } @Override public void sayHello() { System.out.println("before realSubject sayHello ..."); subject.sayHello(); System.out.println("after realSubject sayHello ..."); } }
public class ProxyDemo { public static void main(String[] args) { ProxySubject proxy = new ProxySubject(new RealSubject()) ; proxy.sayHello(); } }
打印:
before realSubject sayHello ...
RealSubject.sayHello ...
after realSubject sayHello ...
动态代理和静态对比基本思路是一致的,只不过动态代理功能更加强大,随着业务的扩展适应性更强。
JDK Proxy 生成对象的步骤如下:
1、拿到被代理对象的引用,并且获取到它的所有的接口,反射获取。
2、JDK Proxy 类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口。
3、动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现)。重写接口的方法,在对应方法中利用反射调用目标方法
4、编译新生成的 Java 代码.class。
5、再重新加载到 JVM 中运行。
以上这个过程就叫字节码重组。JDK 中有一个规范,在 ClassPath 下只要是$开头的 class文件一般都是自动生成的。
从以上生成对象的步骤可以知道,被代理类需要继承至少一个接口 , 生成的类继承会这些接口。被代理的方法必须在接口中定义好。
public interface ISubject { public void sayHello() ; }
public class RealSubject implements ISubject{ @Override public void sayHello() { System.out.println("RealSubject.sayHello ... "); } }
public class DynamicProxy implements InvocationHandler { private Object target; public Object getInstance(Object target) { this.target = target; Class<? extends Object> clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this) ; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before doing something ..."); Object invoke = method.invoke(target, args); System.out.println("after doing something ..."); return invoke; } }
public class ProxyDemo { public static void main(String[] args) { ISubject subject = (ISubject) new DynamicProxy().getInstance(new RealSubject()); subject.sayHello(); //通过反编译工具可以查看源代码 //byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{ISubject.class}); //FileOutputStream os = new FileOutputStream("E://$Proxy0.class"); //os.write(bytes); //os.close(); } }
运行之后,能在 E://盘下找到一个$Proxy0.class 文件。使用 Jad 反编译,得到$Proxy0.jad 文件。
发现$Proxy0 继承了Proxy类,同时还实现了我们的 ISubject接口,而且重写了sayHello()等方法。而且在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,在重写的方法用反射调用目标对象的方法。
打印:
before doing something ...
RealSubject.sayHello ...
after doing something ...
CGLib 代理的目标对象不需要实现任何接口,它是通过动态继承目标对象实现的动态代理。Cglib代理会生成一个类,来继承被代理的类,然后来完成相关操作。所以被代理的类不能被定义为final class 。
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> Cglib会依赖asm包 <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>7.1</version>
public class RealSubject { public void sayHello() { System.out.println("RealSubject.sayHello ... "); } }
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor { public Object getInstance(Class<?> clazz) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before doing something ..."); Object invoke = proxy.invokeSuper(obj, args); System.out.println("after doing something ..."); return invoke; } }
public class CglibTest { public static void main(String[] args) { //利用 cglib 的代理类可以将内存中的 class 文件写入本地磁盘 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://cglib_proxy_class/"); RealSubject subject = (RealSubject)new CglibProxy().getInstance(RealSubject.class); subject.sayHello(); } }
通过代理类的源码可以看到,代理类会获得所有在 父 类 继 承 来 的 方 法 , 并 且 会 有 MethodProxy 与 之 对 应 , 比 如 MethodCGLIB$findLove$0$Method、MethodProxy CGLIB$findLove$0$Proxy;
这些方法在代理类的 sayHello()中都有调用。
注意:proxy.invokeSuper(obj, args);
必须是调用invokeSuper , 因为生成的类继承了代理类,也会重写目标方法,如果是使用invoke(obj,args) 方法,则调用的是自己的方法,不是父类的方法,然后就进入调用循环了。
at com.meihaocloud.proxy.cglibproxy.RealSubject$$FastClassByCGLIB$$62ce64d8.invoke(<generated>) at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
CGLib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。
1.JDK动态代理是实现了被代理对象的接口,CGLib是继承了被代理对象。
2.JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
3.JDK调用代理方法,是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。
先看 ProxyFactoryBean 核心的方法就是 getObject()方法。
在getObject()方法中,主要调用getSingletonInstance()和newPrototypeInstance();在Spring的配置中,如果不做任何设置,那么Spring代理生成的Bean都是单例对象。
如果修改scope则每次创建一个新的原型对象。newPrototypeInstance()里面的逻辑比较复杂,我们后面的课程再做深入研究,这里我们先做简单的了解。
Spring利用动态代理实现AOP有两个非常重要的类,一个是JdkDynamicAopProxy类和CglibAopProxy类
Spring中的代理选择原则
1、当Bean有实现接口时,Spring就会用JDK的动态代理
2、当Bean没有实现接口时,Spring选择CGLib。
3、Spring可以通过配置强制使用CGLib,只需在Spring的配置文件中加入如下代码:
<aop:aspectj-autoproxyproxy-target-class="true"/>