静态代理
利用继承,子类重写父类的方法。
例:
public interface IUserDao {
public void save();
}
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("保存数据");
}
}
public class UserDaoProxy implements IUserDao{
private IUserDao target;
public UserDaoProxy(IUserDao target) {
this.target = target;
}
@Override
public void save() {
System.out.println("开启事务");//扩展了额外功能
target.save();
System.out.println("提交事务");
}
}
测试类:
public class StaticUserProxy {
@Test
public void testStaticProxy(){
//目标对象
IUserDao target = new UserDao();
//代理对象
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();
}
}
输出结果: 开启事务 保存数据 提交事务
总结 :
- 编译期生成class文件对象
- 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
- 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
JDK动态代理
动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。
静态代理与动态代理的区别主要在:
- 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
- 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
特点:
动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。
例:
package com.zhy.jdkProxy;
//subject接口,这个是jdk动态代理必须的前提。
public interface Subject {
void request();
void hello();
}
package com.zhy.jdkProxy;
//目标对象RealSubject
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("request原方法执行");
}
@Override
public void hello() {
System.out.println("hello原方法执行");
}
}
//代理类JdkProxySubject
public class JdkProxySubject implements InvocationHandler {
//这个就是我们要代理的真实对象
private Object subject;
//构造方法,给我们要代理的真实对象赋初值
public JdkProxySubject(Object subject) {
this.subject = subject;
}
/*
*invoke方法方法参数解析
*Object proxy:指被代理的对象。
*Method method:要调用的方法
*Object[] args:方法调用时所需要的参数
*InvocationHandler接口的子类可以看成代理的最终操作类。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("原方法执行前");
Object result = null;
try {
//利用反射动态的来反射方法,这就是动态代理和静态代理的区别
result = method.invoke(subject,args);
} catch (Exception e) {
System.out.println("ex:"+e.getMessage());
throw e;
} finally {
System.out.println("原方法执行后");
}
return result;
}
}
测试类
public class JDKProxyTest {
public static void main(String[] args) throws InterruptedException {
//我们要代理的真实对象
Subject realSubject = new RealSubject();
//我们要代理哪个真实对象,就将该对象传进去
InvocationHandler handler = new JdkProxySubject(realSubject);
//必须要传入接口对象,因此要有接口
Object o = Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class[]{Subject.class}, handler);
System.out.println(o.getClass().getName());
System.out.println(o.getClass().getSuperclass().getName());
System.out.println(o.getClass().getGenericInterfaces()[0].getTypeName());
// System.out.println(o instanceof RealSubject);
// System.out.println(o instanceof Proxy);
// System.out.println(o instanceof Subject);
Subject subject = (Subject)o;
subject.hello();
subject.request();
TimeUnit.HOURS.sleep(1);
}
}
输出结果:
com.sun.proxy.$Proxy0
java.lang.reflect.Proxy
com.zhy.jdkProxy.Subject
原方法执行前
hello原方法执行
原方法执行后
原方法执行前
request原方法执行
原方法执行后
动态生成的代理类:
cglib代理
cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
cglib特点
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。
如果想代理没有实现接口的类,就可以使用CGLIB实现。 - CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。
它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。 - CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
cglib与动态代理最大的区别就是
- 使用动态代理的对象必须实现一个或多个接口
- 使用cglib代理的对象则无需实现接口,达到代理类无侵入。
使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。
例:
public class CGlibMethodInterceptor implements MethodInterceptor {
/**
* cglib
*
* @param o cglib生成的代理对象
* @param method 被代理对象的方法
* @param objects 方法入参
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib动态代理,原始方法执行前!");
Object invoke = methodProxy.invokeSuper(o, objects);//方法执行,参数:target 目标对象 arr参数数组
System.out.println("Cglib动态代理,原始方法执行后!");
return invoke;
}
}
public class RealObject {
public void orgMethod(){
System.out.println("原始方法");
}
/**
* 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
*/
final public String sayOthers(String name) {
System.out.println("HelloService...sayOthers..." + name);
return null;
}
}
测试类:
public class TestCGlib {
public static void main(String[] args) throws InterruptedException {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealObject.class);
enhancer.setCallback(new CGlibMethodInterceptor());
RealObject proxy = (RealObject) enhancer.create();
proxy.orgMethod();
proxy.sayOthers("");
TimeUnit.HOURS.sleep(1);
}
}
输出结果:
Cglib动态代理,原始方法执行前!
原始方法
Cglib动态代理,原始方法执行后!
RealObject...sayOthers...
动态生成的代理类:
总结
- 静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。
- JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。
- 动态代理生成的类为 lass com.sun.proxy.\$Proxy4,cglib代理生成的类为class com.cglib.UserDao\$\$EnhancerByCGLIB\$\$552188b6。
- 静态代理在编译时产生class字节码文件,可以直接使用,效率高。
- 动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
- cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。
jdk动态代理和cglib动态代理的区别
1、jdk动态代理是实现了被代理对象的接口,cglib是继承了被代理对象。
2、jdk和cglib都是在运行期生成字节码,jdk是直接写class字节码,cglib使用ASM框架写class字节码,cglib代理实现更复杂,生成代理类比jdk效率低。
3、jdk调用代理方法,是通过反射机制调用,cglib是通过FastClass机制直接调用方法,cglib执行效率更高。
参考
https://segmentfault.com/a/1190000011291179
辅助工具:
查看动态生成的类
java -classpath ..\lib\sa-jdi.jar sun.jvm.hotspot.HSDB
jps
35424 RemoteMavenServer
37456 HSDB
27988 Jps
36324 TestCGlib
21224 Launcher
30364