文章目录
1. 什么是代理
简单来讲就是:不直接访问目标,而是通过一个中间层来访问
2. 动态代理产生的原因
当某些代码块需要很多地方执行,但又不想与特定方法耦合,即不想使用硬编码的方法将公共代码块与特定方法耦合,这时动态代理就可以实现上面的需求
3. 动态代理的分类
3.1 JDK的动态代理
只能实现接口代理,并且是包装的被代理对象(类的实例),也就是说,在代理的过程中,有2个对象,一个代理对象,一个目标对象,目标对象被包装在代理对象里面。
3.1.1 实现
在Java中,有一个Proxy类,可以直接使用反射方式,代理拦截。 最常用的是一个静态方法Proxt.newProxyInstance()来创建动态代理对象
-
定义接口
interface Dog{ void info(); void run(String name); }
-
接口实现
public class GunDog implements Dog{ public void info(){ System.out.println("我是一只猎狗"); } public void run(){ System.out.println("我奔跑速度"); } }
-
代理对象生成
要实现InvocationHandler,并实现其中的invoke方法,在调用目标对象的时候,会先调用到invoke方法,再主动调用被调用者方法public class MyInvokationHandler implements InvocationHandler{ //需要代理的对象 private Object target; public void setTarget(Object target){ this.target = target; } // 重写invoke(),执行动态代理对象的所有方法时,都会被替换成执行如下的invoke()方法 pubic void invoke(Object proxy,Method method,Object[] args) throws Exception{ System.out.println("======模拟第一个通用方法"); Object result = method.invoke(target,args); System.out.println("======模拟第二个通用方法"); return result; } }.
-
使用
public class Test{ //创建MyInvokationHandler对象 MyInvokationHandler handler = new MyInvokationHandler (); //创建原始的GunDog对象,作为target Dog target = newGunDog(); //为MyInvokationHandler 指定target对象, handler.setTarget(target); //以指定的target来创建动态代理对象,接口类型来接收 Dog dog = (Dog) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler); dog.info(); dog.run(); }
-
运行结果
=======模拟第一个通用方法 我是一只猎狗 ======模拟第二个通用方法 =======模拟第一个通用方法 我奔跑迅速 ======模拟第二个通用方法
3.1.2 相关知识
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
//获取代理类的class对象
Class<?> cl = getProxyClass0(loader, intfs);
//根据参数获取class对象的构造函数
final Constructor<?> cons = cl.getConstructor(constructorParams);
//生成代理类实例
return cons.newInstance(new Object[]{h});
}
先查看缓存中查到是否该类加载器已经加载过该代理类,如果找到则直接返回,否则使用ProxyClassFactory来生成代理类
根据类加载器和目标接口类获取代理类Class对象:该方法里面包含了代理类的动态创建过程。会生成一个形如$Proxy0…之类的动态代理类,然后JVM会加载这些字节类,得到对应的Class对象,进行缓存
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
long num = nextUniqueNumber.getAndIncrement();
//拼接代理类名
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//生成字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
//通过类加载器生成代理类的Class对象
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
=====================================================
java.lang.ClassLoader
这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
每次生成动态代理类对象时都需要指定一个类装载器对象
3.2 CGLIB的代理
是继承目标对象,生成了一个新的类,然后来实现代理,这样,在内存中只有代理对象,没有目标对象了,使用的是直接继承的方式
3.2.1 什么是CGLIB
Cglib与一些框架和语言的关系:
-
最底层的是字节码Bytecode,字节码是Java为了保证“一次编译、到处运行”而产生的一种虚拟指令格式,例如iload_0、iconst_1、if_icmpne、dup等
-
位于字节码之上的是ASM,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉
-
位于ASM之上的是CGLIB、Groovy、BeanShell,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码----只要你能生成Java字节码,JVM并不关心字节码的来源,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码
-
位于CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP这些框架了,这一层大家都比较熟悉
-
最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序
Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。
运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。
3.2.2 优点
- JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了
- CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础
- Cglib动态代理比使用java反射的JDK动态代理要快(Cglib的FastClass机制,解析参考http://www.cnblogs.com/cruze/category/593899.html )
3.2.3 缺点
- CGLib由于是采用动态创建子类的方法,对于被代理类中的final方法,无法进行代理,因为子类中无法重写final函数
- CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些
3.2.4 实现
将上面用java动态代理实现改为使用CGLIB代理实现
-
定义被代理类
public class Dog { public void info(){ System.out.println("我是一只猎狗"); }; public void run(){ System.out.println("我奔跑速度"); }; }
-
代理对象生成
import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class MyMethodInterceptor implements MethodInterceptor { //Object表示要进行增强的对象; //Method表示拦截的方法; //Object[]数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double; //MethodProxy表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用 @Override public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable { System.out.println("======模拟第一个通用方法"); Object object = proxy.invokeSuper(obj, arg); System.out.println("======模拟第二个通用方法" ); return object; }; }
-
使用
public class Test { public static void main(String[] args){ MyMethodInterceptor interceptor = new MyMethodInterceptor(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Dog.class); enhancer.setCallback(interceptor); Dog dog = (Dog)enhancer.create(); dog.info(); dog.run(); }
-
运行结果
======模拟第一个通用方法 我是一只猎狗 ======模拟第二个通用方法 ======模拟第一个通用方法 我奔跑速度 ======模拟第二个通用方法
运行结果和java实现的动态代理运行效果一样,但是可以看出少了一个步骤,就是不用定义接口了:
注:可能使用CGLIB报错:
Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
at net.sf.cglib.core.TypeUtils.parseType(TypeUtils.java:184)
at net.sf.cglib.core.KeyFactory.<clinit>(KeyFactory.java:66)
at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:69)
如果是maven了项目,引入如下依赖即可:
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
以上完成了动态代理的讲解,下面说一下静态代理
4. 静态代理
4.1 实现
-
定义接口
interface Dog{ void info(); void run(); }
-
接口实现
public class GunDog implements Dog{ public void info(){ System.out.println("我是一只猎狗"); } public void run(){ System.out.println("我奔跑速度"); } }
-
代理类
public class DogProxy implements Dog{ // 目标对象 private Dog dog; // 通过构造方法传入目标对象 public DogProxy (Dog dog){ this.dog=dog; } @Override public void info() { System.out.println("======模拟第一个通用方法"); dog.info(); System.out.println("======模拟第一个通用方法"); } @Override public void run() { System.out.println("======模拟第一个通用方法"); dog.info(); System.out.println("======模拟第一个通用方法"); } }
-
使用
public class Test {public static void main(String[] args){ DogProxy dog = new DogProxy(new GunDog()); dog.info(); dog.run(); }
-
运行结果
======模拟第一个通用方法 我是一只猎狗 ======模拟第二个通用方法 ======模拟第一个通用方法 我奔跑速度 ======模拟第二个通用方法
4.2 优点
- 代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)
4.3 缺点
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度
- 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类
静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。