在Java中,实现动态代理有两种方式:
1 . JDK动态代理 : Java.lang.reflect 包中的Proxy类和 InvocationHandler 接口提供了生成动态代理类的能力。
2 . Cglib动态代理 : Cglib (Code Generation Library) 是一个第三方代码生成类库,运行时在内存中动态生成一个了类对象从而实现对目标对象功能的扩展。
用一张图片看一下什么是动态代理(概念):
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类就可以使用CGLIB实现。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的 interception (拦截)。
Calib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
所以,使用JDK动态代理的对象必须实现一个或多个接口:而使用cgib代理的对象则无需实现接口,达到代理类无侵入。
最大的区别就是静态代理是编译期确定的,但是动态代理却是运行期确定的。
同时,使用静态代理模式需要程序员手写很多代码,这个过程是比较浪费时间和精力的。一旦需要代理的类中方法比较多,或者需要同时代理多个对象的时候,这无疑会增加很大的复杂度。
反射是动态代理的实现方式之一。
Java的动态代理,在日常开发中可能并不经常使用,但是并不代表他不重要。Java的动态代理的最主要的用途就是应用在各种框架中。因为使用动态代理可以很方便的运行期生成代理类,通过代理类可以做很多事情,比如AOP,比如过滤器、拦截器等。
在我们平时使用的框架中,像 servlet 的 filter 、包括 spring 提供的 aop 以及 struts2 的拦截器都使用了动态代理功能。我们日常看到的mybatis分页插件,以及日志拦截、事务拦截、权限拦截这些几乎全部由动态代理的身影。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接 和 Proxy 类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
CGLIB (Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
public class UserServiceImpl implements UserService {
@Override
public void add() {
// TODO Auto-generated method stub
System,out,println("--------------------add----------------------");
}
}
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
PerformanceMonior.begin(target.getClass().getlame( )+"+method.getlame());
//System.out .println("-----------------begin “+method.getName()+"---------);
Object result = method.invoke(target, args);
//System.out.println("--------------end "+method.getName( )+"-----);
PerformanceMonior.end();
return result;
}
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(),this);
}
}
public static void main(string[] args) {
UserService seryice = new UserServiceImpl();
MyInvocationHandler handler = new MyInvocationHandler(service);
UserService proxy = (UserService) handler.getProxy();
proxy .add();
}
public class UserServiceImpl implements UserService {
@Override
public void add() {
//TODO Auto-generated method stub
System.out,println("--------------------add----------------------");
}
}
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj,Method method, Object[] args,MethodProxy proxy) throws Throwable {
System.out.println("前置代理");
//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.printIn("后置代理");
return result;
}
}
public class DoCGLib {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
//通过生成子类的方式创建代理类
UsenServiceImpl proxyImp = (UsenServiceImpl)proxy.getProxy(UserServiceImpl.class);
proxyImp.add():
}
}
JDK动态代理只能用于接口,不能用于类。
- 动态代理只对方法调用有效,对字段访问和赋值无效。
- 如果目标对象抛出了异常,那么这个异常会被代理对象抛出,而不是在调用
invoke
方法时抛出。
与CGLIB等其他代理技术的比较:
- CGLIB:它是一个强大的高性能的代码生成库,可以扩展JAVA类和实现JAVA接口。它主要应用于高级OOP设计和应用,如AOP实现、缓存框架、事务管理等。
- 两者的选择:如果你的目标是基于现有类的行为进行拦截或修改,可以使用CGLIB;如果目标是基于接口进行拦截或修改,那么应该使用JDK动态代理。
💡思考:
除了JDK动态代理和CGLIB,还有其他一些常用的代理技术,如Spring AOP、AspectJ等。这些技术提供了更高级的特性,如支持方法级别的拦截、支持运行时和编译时切面等。
// 导入java.lang.reflect包中的InvocationHandler和Proxy类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
// 定义一个接口Hello
interface Hello {
// 定义接口方法sayHello,没有实现
void sayHello();
}
// 定义一个实现Hello接口的类HelloImpl
class HelloImpl implements Hello {
// 实现接口方法sayHello
public void sayHello() {
System.out.println("Hello, world!");
}
}
// 定义一个实现InvocationHandler接口的类DynamicProxyHandler
class DynamicProxyHandler implements InvocationHandler {
// 私有成员变量obj,用来保存目标对象的引用
private Object obj;
// 构造方法,传入目标对象作为参数,赋值给obj成员变量
public DynamicProxyHandler(Object obj) { this.obj = obj; }
// 实现InvocationHandler接口的方法invoke,传入代理对象、方法、参数数组,返回值类型为Object
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
// 在目标方法执行前输出"Before method call"
System.out.println("Before method call");
// 调用目标方法,传入参数args,返回值赋值给result变量
Object result = m.invoke(obj, args);
// 在目标方法执行后输出"After method call"
System.out.println("After method call");
// 返回目标方法的返回值或者异常(如果目标方法抛出了异常)
return result;
}
}
public class DynamicProxyExample {
public static void main(String[] args) {
// 创建一个HelloImpl类的实例作为目标对象,并实现Hello接口的sayHello方法
Hello hello = new HelloImpl();
// 创建一个DynamicProxyHandler类的实例作为InvocationHandler,传入目标对象实例作为参数传入构造器中
InvocationHandler handler = new DynamicProxyHandler(hello);
/** 使用Proxy类的静态方法newProxyInstance创建代理对象实例,传入目标对象的类加载器、目标对象的接口数组以及
* InvocationHandler实例作为参数传入构造器中。返回的是代理对象实例,类型为目标对象的接口类型。
* 创建的代理对象实例可以直接像目标对象实例一样使用,只不过它实现了所有接口中的方法。
* 所有这些方法的调用最终会调用到我们提供的InvocationHandler实例中对应的invoke方法中去处理。
* 这样我们就能够在不修改原有代码的基础上为某个对象提供额外行为操作,
* 也就是实现了在运行时动态扩展了某个类的行为功能操作,即实现了AOP的功能。
* 这样就达到了在不修改原有代码的基础上扩展了某个类的行为功能操作的目的。
* 比如可以在目标对象方法执行前后添加额外的逻辑操作处理。此处因为动态代理的目标是Hello接口,所以没有错误。
*/
Hello proxy = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), new Class[] { Hello.class }, handler);
}
}
最后总结一下JDK动态代理的思想: