目录
1、Java静态代理
静态代理是一种设计模式,其中代理类在编译时就已经定义好了。静态代理通过实现与目标对象相同的接口,在代理类中调用目标对象的方法并在其前后执行一些操作。它可以在不修改目标对象代码的前提下,对目标对象的功能进行扩展。//不修改目标对象,对功能进行扩展
(1)静态代理的优缺点
优点:
简洁明确:代理类在编译时生成,结构清晰,易于理解和维护。
符合单一职责原则:可以通过代理类扩展目标对象的功能,而不改变目标对象自身的代码。
缺点:
代码冗余:每个目标对象都需要编写对应的代理类,如果接口增加方法,代理类需要同步修改,容易导致代码膨胀。
不灵活:只能代理特定接口的实现类,不能动态代理其他类。
(2)静态代理的实现
下面是一个静态代理的实现示例,展示如何使用静态代理来扩展目标对象的功能。
首先定义一个接口,包含目标对象和代理对象都要实现的方法。
public interface HelloService {
void sayHello();
}
目标对象类实现HelloService接口,包含具体的业务逻辑。
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("Hello, World!");
}
}
代理类同样实现HelloService接口,并包含对目标对象的引用。在代理类的方法中,调用目标对象的方法,并在其前后加入额外的逻辑。
public class ServiceProxy implements HelloService {
private final HelloService target;
public ServiceProxy(HelloService target) {
this.target = target;
}
@Override
public void sayHello() {
// 方法调用前的操作
System.out.println("Before performing task in ServiceProxy...");
// 调用目标对象的方法
target.sayHello();
// 方法调用后的操作
System.out.println("After performing task in ServiceProxy...");
}
}
使用代理类来调用目标对象的方法,可以看到代理类在方法调用前后执行的额外操作。
public class StaticProxyTest {
public static void main(String[] args) {
// 创建目标对象
HelloService service = new HelloServiceImpl();
// 创建代理对象
HelloService proxy = new ServiceProxy(service);
// 通过代理对象调用方法
proxy.sayHello();
}
}
静态代理的实现方式比较简单,适合在需要为少量对象添加代理功能的场景中使用。它缺点在于需要为每个接口都编写代理类,当接口方法变更时,代理类也需要进行同步修改,这可能导致代码膨胀和维护成本增加。在实际开发中,为了减少代码冗余和提高灵活性,会更多地使用动态代理,如JDK动态代理和CGLIB动态代理。
2、JDK动态代理
JDK动态代理是一种在运行时动态生成代理类的机制,它允许我们在不修改原始类代码的情况下增强或修改方法行为。动态代理通常用于实现跨切面(如日志记录、事务管理等),在AOP(面向切面编程)中使用广泛。
(1)使用JDK动态代理的步骤
使用JDK动态代理一般有如下步骤:
- 定义接口: 要使用动态代理,首先需要定义一个接口,代理类和被代理的实际类都要实现这个接口。
- 实现接口: 实现接口的具体类就是我们要代理的目标对象。
- 创建InvocationHandler: 实现InvocationHandler接口,该接口的invoke方法会被代理对象的每个方法调用。
- 使用Proxy类创建代理对象: 通过Proxy.newProxyInstance方法来创建代理对象。
(2)JDK动态代理的实现
对于HelloService接口和HelloServiceImpl实现类,下面是一个JDK动态代理的实现示例。
以下是一个通用JDK动态代理类的实现示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 通用动态代理工具库:
* 只能代理实现了接口的类(接口代理)。
* 使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。
* JDK动态代理是在运行时生成代理类的字节码,并创建代理对象。
*/
public class DynamicProxyHandler implements InvocationHandler {
private final Object target;
// 构造函数,接受一个被代理的目标对象
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在方法执行前的操作,例如日志、权限检查等
beforeMethod(method, args);
// 执行目标对象的方法
Object result = method.invoke(target, args);
// 在方法执行后的操作,例如日志、结果处理等
afterMethod(method, args);
return result;
}
private void beforeMethod(Method method, Object[] args) {
System.out.println("Before method: " + method.getName());
// 这里可以加入其他的通用操作
}
private void afterMethod(Method method, Object[] args) {
System.out.println("After method: " + method.getName());
// 这里可以加入其他的通用操作
}
// 静态方法用于创建代理对象
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new DynamicProxyHandler(target)
);
}
}
我们可以使用通用该动态代理类来代理HelloService对象:
public class ProxyTest {
public static void main(String[] args) {
// 创建目标对象
HelloService helloService = new HelloServiceImpl();
// 创建代理对象
HelloService proxy = DynamicProxyHandler.createProxy(helloService);
// 调用代理对象的方法
proxy.sayHello();
}
}
JDK动态代理的类为什么必须需要有接口呢?
查看JDK的动态代理源码你会发现,动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的.class文件,并加载.class文件运行的过程,通过反编译被生成的 $Proxy0.class 文件发现,.class类被定义为:
public final class $Proxy0 extends Proxy implements Interface {
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
}
由于Java的单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口。
3、CGLIB 动态代理
(1)CGLIB 动态代理的特点
CGLIB 动态代理能够代理没有实现接口的类(类代理)。该方式使用字节码增强技术生成代理类的子类,通过继承代理目标类来实现代理。因为 CGLIB 代理是通过生成目标类的子类来实现的,所以被代理类不能是 final 类。此外,CGLIB 是第三方库,需要额外引入依赖。
(2)CGLIB 动态代理的实现
对于HelloService接口和HelloServiceImpl实现类,下面是一个CGLIB动态代理的实现示例。
引入依赖:使用 Maven 时,添加以下依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
以下是一个通用CGLIB动态代理类的实现示例:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 通用CGLIB动态代理工具类:
* 能够代理没有实现接口的类(类代理)。
* 使用字节码增强技术生成代理类的子类,通过继承代理目标类来实现代理。
* CGLIB 是第三方库,需要额外引入依赖。
* 代理类不能是 final 类,因为 CGLIB 代理通过生成目标类的子类来实现。
*/
public class CglibProxyHandler implements MethodInterceptor {
private Object target;
// 构造方法,接收被代理的对象
public CglibProxyHandler(Object target) {
this.target = target;
}
// 创建代理对象
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
// 拦截方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在方法执行前的操作
beforeMethod(method, args);
// 执行目标对象的方法
Object result = proxy.invokeSuper(obj, args);
// 在方法执行后的操作
afterMethod(method, args);
return result;
}
private void beforeMethod(Method method, Object[] args) {
System.out.println("Before method: " + method.getName());
// 这里可以加入其他的通用操作
}
private void afterMethod(Method method, Object[] args) {
System.out.println("After method: " + method.getName());
// 这里可以加入其他的通用操作
}
}
我们可以使用通用该动态代理类来代理HelloService对象:
public class CglibProxyTest {
/**
* 在JDK 9及以上版本(包括JDK 17)中,Java引入了模块系统(Project Jigsaw)
* 为了确保CGLIB能够正确地访问和代理某些类,需要确保代理类所在的包被正确开放。
* 如果项目没有模块化,可以直接这样使用:
* --add-opens java.base/java.lang=ALL-UNNAMED
* @param args
*/
public static void main(String[] args) {
// 创建目标对象
HelloServiceImpl helloService = new HelloServiceImpl();
// 创建代理对象
CglibProxyHandler proxyHandler = new CglibProxyHandler(helloService);
HelloServiceImpl proxyInstance = (HelloServiceImpl) proxyHandler.getProxyInstance();
// 调用代理对象的方法
proxyInstance.sayHello();
}
}
需要注意的是,由于模块系统(introduced in Java 9)和一些类加载器的变化,CGLIB 可能需要一些额外的配置或对模块进行开放访问,以确保代理生成能够顺利进行。
当模块未进行开放访问时,使用CGLIB动态代理,可能会抛出以下错误:
Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @11028347
如果你的项目不是模块化的,解决此问题你可以通过JVM参数来开放包。启动Java应用时,使用以下参数:
--add-opens java.base/java.lang=ALL-UNNAMED
例如,当在 idea 中运行时,添加 JVM 参数的示例图如下:
如果你正在使用Java模块系统并且定义了module-info.java文件,你需要确保目标类的包对CGLIB开放。假设你有一个模块your.module.name,并且CGLIB需要代理com.example.service包中的类,可以通过以下方式开放包:
module your.module.name {
requires cglib;
opens com.example.service to cglib;
}
这行代码的意思是将com.example.service包开放给CGLIB模块,以便它能够反射性地访问该包中的类和成员。
(3)JDK 动态代理 vs CGLIB 动态代理
特点 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
代理对象 | 代理接口 | 代理类(生成子类) |
性能 | 通常性能较好,适用于代理接口的场景 | 性能稍逊于 JDK 动态代理,适用于代理类 |
实现方式 | 基于反射机制 | 基于字节码生成 |
限制 | 只能代理接口 | 不能代理 final 类和 final 方法 |
JDK 动态代理 是官方的解决方案,适用于需要代理实现了接口的类。CGLIB 动态代理适用于没有实现接口的类或需要代理类的情况。它通过生成目标类的子类来实现代理。
至此,全文结束。