JAVA代理模式 之 静态代理、jdk动态代理和cglib动态代理
- 代理 (Proxy)
首先先了解下代理(Proxy),生活中最经常遇到的就是朋友圈的微商,例如:面膜
面膜厂家把商品委托给代理商A销售,用户从代理商处购买面膜。
- 代理模式介绍
代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
2.1. 用户只关心接口功能 visit()
2.2. Subject 接口类定义了 RealSubject 和 ProxySubject 公用的接口,这样就在任何使用RealSubject的地方都可以使用Proxy
2.3. 接口Subject 真正实现着是RealSubject ,但是它不与用户直接接触,而是通过代理
2.4. ProxySubject 同样实现了Subject 接口,用户调用ProxySubject 的时候,ProxySubject 内部调用了 RealSubject ,所以,ProxySubject 是中介者,可以增强RealSubject 操作。
- 静态代理
这种代理方式需要代理对象和目标对象实现一样的接口。
优点:
可以在不修改目标对象的前提下,扩展目标对象的功能
缺点:
代理对象与目标对象实现统一的接口,会产生过多的代理类
3.1 接口类:Subject
package com.proxy;
public interface Subject {
public void visit();
}
3.2 委托类:RealSubject
package com.proxy;
public class RealSubject implements Subject{
@Override
public void visit() {
System.out.println("拜访客户");
}
}
3.3 静态代理类:ProxySubject
package com.proxy;
public class ProxySubject implements Subject{
private Subject target;
public UserDaoProxy(Subject target) {
this.target = target;
}
@Override
public void visit() {
System.out.println("开启事务");//扩展了额外功能
target.visit();
System.out.println("提交事务");
}
}
3.4 测试类:TestProxy
package com.proxy;
import org.junit.Test;
public class StaticSubjectProxy {
@Test
public void testStaticProxy(){
//目标对象
Subject target = new RealSubject();
//代理对象
ProxySubject proxy = new ProxySubject(target);
proxy.visit();
}
}
输出结果:
--- 开启事务
--- 拜访客户
--- 提交事务
- jdk 动态代理
jdk 动态代理利用 JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。
与静态代理的主要区别:
静态代理在编译时已经实现,编译完成后代理类是一个实际的class文件
动态代理是在运行时动态生成的,编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM 中。
特点:
动态代理不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态接口
jdk 动态代理中主要包含一个类和一个接口
InvocationHandler接口:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
}
参数说明:
Object proxy:指被代理的对象。
Method method:要调用的方法
Object[] args:方法调用时所需要的参数
Proxy类:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h
) throws IllegalArgumentException
参数说明:
ClassLoader loader:类加载器
Class<?>[] interfaces:得到全部的接口
InvocationHandler h:得到InvocationHandler接口的子类实例
注释:类加载器
在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器;
Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;
Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jrelibext目录中的类;
AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。
4.1 接口类:Subject
package com.proxy;
public interface Subject {
public void visit();
}
4.2 委托类:RealSubject
package com.proxy;
public class RealSubject implements Subject{
@Override
public void visit() {
System.out.println("拜访客户");
}
}
4.3 jdk 动态代理工厂:ProxySubject
package com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory{
private Object target;// 维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}
// 为目标对象生成代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
// 执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务");
return null;
}
});
}
}
3.4 测试类:TestProxy
package com.proxy;
import org.junit.Test;
public class DynamicSubjectProxy {
@Test
public void testDynamicProxy(){
//目标对象
Subject target = new RealSubject();
System.out.println(target.getClass());
//代理对象
Subject proxy = (Subject)new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass());
proxy.visit();
}
}
输出结果:
--- class com.proxy.RealSubject
--- class com.sun.proxy.$Proxy4
--- 开启事务
--- 拜访客户
--- 提交事务
- cglib 动态代理
cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
cglib特点
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。( 使用cglib代理的对象则无需实现接口,达到代理类无侵入。)
CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
cglib 需要引入 jar 包:
5.1 委托类:RealSubject
package com.proxy;
public class RealSubject {
public void visit() {
System.out.println("拜访客户");
}
}
5.2 cglib 动态代理工厂:ProxySubject
package com.proxy;
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 ProxyFactory implements MethodInterceptor{
private Object target;// 维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}
//为目标对象生成代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类对象代理
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事务");
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("关闭事务");
return null;
}
}
5.3 测试类:TestProxy
package com.proxy;
import org.junit.Test;
public class DynamicSubjectProxy {
@Test
public void testDynamicProxy(){
//目标对象
RealSubject target = new RealSubject();
System.out.println(target.getClass());
//代理对象
Subject proxy = (Subject)new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass());
proxy.visit();
}
}
输出结果:
--- class com.proxy.RealSubject
--- class com.proxy.RealSubject$$EnhancerByCGLIB$$552188b6
--- 开启事务
--- 拜访客户
--- 提交事务
- 总结
静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。
JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。
动态代理生成的类为 lass com.sun.proxy.$Proxy4,cglib代理生成的类为class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6。
静态代理在编译时产生class字节码文件,可以直接使用,效率高。
动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。