🧑 博主简介:历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,
15年
工作经验,精通Java编程
,高并发设计
,Springboot和微服务
,熟悉Linux
,ESXI虚拟化
以及云原生Docker和K8s
,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
Java 代理:深入理解与实际应用
在 Java 开发中,代理模式是一种非常重要的设计模式。它可以在不修改目标对象代码的情况下,为目标对象添加额外的功能。本文将深入探讨 Java 代理中的静态代理与动态代理,包括常见的动态代理实现 JDK Proxy
和 CGLIB
,以及它们之间的对比和在 Spring AOP
中的实际应用。
一、静态代理
(一)概念介绍
静态代理是由程序员创建或工具生成代理类的代码,再对其编译。在程序运行前,代理类的.class 文件就已经存在了。
静态代理中,代理类与目标对象实现相同的接口,并且在代理类中持有一个目标对象的引用。当客户端调用代理对象的方法时,代理对象会将请求转发给目标对象,并在转发前后添加一些额外的功能,比如日志记录、性能统计等。
(二)代码示例及注释
以下是一个静态代理的示例代码:
// 定义接口
interface Subject {
void doSomething();
}
// 目标对象实现接口
class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("目标对象的方法被调用");
}
}
// 代理对象实现接口
class StaticProxy implements Subject {
private Subject target;
public StaticProxy(Subject target) {
this.target = target;
}
@Override
public void doSomething() {
System.out.println("在调用目标对象方法前执行一些额外操作");
target.doSomething();
System.out.println("在调用目标对象方法后执行一些额外操作");
}
}
在上述代码中,Subject
是一个接口,RealSubject
是目标对象,实现了Subject
接口。StaticProxy
是代理对象,也实现了Subject
接口,并在代理对象中持有一个目标对象的引用。当客户端调用代理对象的doSomething
方法时,代理对象会先执行一些额外的操作,然后调用目标对象的doSomething
方法,最后再执行一些额外的操作。
二、动态代理
(一)概念介绍
动态代理是在程序运行时,通过 JDK 反射机制或 CGLIB 等工具动态生成代理类的字节码,并加载到 JVM 中。与静态代理不同,动态代理不需要为每个目标对象都创建一个代理类,而是可以根据需要动态地创建代理对象。
动态代理可以实现对目标对象的方法进行拦截和增强,而不需要修改目标对象的代码。它通常用于实现 AOP(面向切面编程),可以在不修改业务代码的情况下,为业务方法添加日志记录、性能统计、事务管理等功能。
(二)常见的动态代理实现 JDK Proxy
-
JDK Proxy 的工作原理
- JDK 动态代理是基于接口实现的。它使用 Java 的反射机制在运行时创建代理对象。代理对象实现了与目标对象相同的接口,并在代理对象的方法调用中,通过反射机制调用 InvocationHandler 的 invoke 方法来实现对目标对象方法的拦截和增强。
-
代码示例及注释
- 以下是一个使用 JDK Proxy 的示例代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface Subject {
void doSomething();
}
// 目标对象实现接口
class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("目标对象的方法被调用");
}
}
// 代理对象的调用处理器
class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在调用目标对象方法前执行一些额外操作");
Object result = method.invoke(target, args);
System.out.println("在调用目标对象方法后执行一些额外操作");
return result;
}
}
在上述代码中,首先定义了一个接口Subject
和一个实现了该接口的目标对象RealSubject
。然后定义了一个代理对象的调用处理器DynamicProxyHandler
,它实现了InvocationHandler
接口。在DynamicProxyHandler
的invoke
方法中,可以实现对目标对象方法的拦截和增强。最后,使用Proxy.newProxyInstance
方法创建代理对象,并将代理对象转换为Subject
接口类型进行调用。
(三)CGLIB
-
CGLIB 的工作原理
- CGLIB(Code Generation Library)是一个强大的代码生成库。它通过继承目标对象来创建代理对象。CGLIB 使用字节码生成技术在运行时生成目标对象的子类,并在子类中重写目标对象的方法,从而实现对目标对象方法的拦截和增强。
-
代码示例及注释
- 以下是一个使用 CGLIB 的示例代码:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
class RealSubject {
public void doSomething() {
System.out.println("目标对象的方法被调用");
}
}
class CglibProxy implements MethodInterceptor {
private Object target;
public Object getProxyInstance(Object target) {
this.target = target;
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 {
System.out.println("在调用目标对象方法前执行一些额外操作");
Object result = method.invoke(target, args);
System.out.println("在调用目标对象方法后执行一些额外操作");
return result;
}
}
在上述代码中,首先定义了一个目标对象RealSubject
。然后定义了一个代理对象CglibProxy
,它实现了MethodInterceptor
接口。在CglibProxy
的intercept
方法中,可以实现对目标对象方法的拦截和增强。最后,使用Enhancer
类创建代理对象,并将代理对象转换为目标对象的类型进行调用。
三、JDK Proxy 和 CGLIB 的对比
(一)实现方式
- JDK Proxy 是基于接口实现的,它要求目标对象必须实现一个或多个接口。
- CGLIB 是通过继承目标对象来实现的,它不要求目标对象实现任何接口。
(二)性能
- 在 JDK 1.8 及以上版本中,JDK Proxy 的性能与 CGLIB 相当。在某些情况下,JDK Proxy 甚至可能比 CGLIB 更快,因为它不需要生成目标对象的子类。
- CGLIB 在生成代理对象时需要进行字节码生成,因此在性能上可能会有一些开销。但是,CGLIB 可以对没有实现接口的目标对象进行代理,这在某些情况下可能会更加灵活。
(三)使用场景
- 如果目标对象实现了接口,并且对性能要求不是非常高,可以使用 JDK Proxy。
- 如果目标对象没有实现接口,或者需要对目标对象的方法进行深度定制,可以使用 CGLIB。
四、动态代理在Spring AOP的应用
(一)Spring AOP 简介
Spring AOP(Aspect-Oriented Programming)是一种基于 Spring 框架的面向切面编程实现。它使用动态代理技术,在不修改业务代码的情况下,为业务方法添加日志记录、性能统计、事务管理等功能。
Spring AOP 主要由切面(Aspect)、通知(Advice)和切点(Pointcut)组成。切面是一个模块化的横切关注点,它包含了一个或多个通知。通知是在切点处执行的增强代码,比如前置通知、后置通知、环绕通知等。切点是一个表达式,用于指定在哪些地方应用切面。
(二)代码示例及注释
以下是一个使用 Spring AOP 实现日志记录的示例代码:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
// 前置通知,在目标方法执行前执行
@Before("execution(* com.example.demo.service.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
logger.info("Before method: " + joinPoint.getSignature().getName());
}
// 环绕通知,在目标方法执行前后执行
@Around("execution(* com.example.demo.service.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
logger.info("Before executing method: " + proceedingJoinPoint.getSignature().getName());
Object result = proceedingJoinPoint.proceed();
logger.info("After executing method: " + proceedingJoinPoint.getSignature().getName());
return result;
}
}
在上述代码中,定义了一个切面类LoggingAspect
,它使用@Aspect
注解标识为一个切面。在切面类中,定义了两个通知方法:beforeMethod
和aroundMethod
。beforeMethod
是前置通知,在目标方法执行前执行;aroundMethod
是环绕通知,在目标方法执行前后执行。通知方法使用@Before
和@Around
注解指定切点表达式,用于指定在哪些地方应用通知。
五、总结
本文深入探讨了 Java 代理中的静态代理与动态代理,包括常见的动态代理实现 JDK Proxy
和 CGLIB
,以及它们之间的对比和在 Spring AOP 中的实际应用。静态代理需要为每个目标对象都创建一个代理类,代码冗余度高,维护成本大。动态代理可以在运行时动态地创建代理对象,更加灵活和高效。JDK Proxy
是基于接口实现的,而 CGLIB 是通过继承目标对象来实现的。在实际应用中,可以根据具体情况选择合适的代理方式。Spring AOP
是一种基于动态代理的面向切面编程实现,它可以在不修改业务代码的情况下,为业务方法添加额外的功能,提高了代码的可维护性和可扩展性。
六、参考资料文献
- 《
Effective Java
》 - 《
Spring in Action
》 - 《Java 核心技术》
- 官方 Java 文档:https://docs.oracle.com/javase/8/docs/api/
- Spring 官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/index.html