Java代理模式
一、什么是代理模式?
代理模式(Proxy Pattern)是Java中非常经典的结构型设计模式。它的核心思想是: 通过引入一个“代理对象”来控制对“目标对象”的访问 。就像明星的经纪人,外界找明星工作必须通过经纪人,经纪人可以做过滤、谈判、排期,甚至代替明星处理简单事务。
二、静态代理(编译期确定)
原理 :代理类和目标类实现 同一个接口 ,代理类持有目标类的引用,在调用目标方法前后插入逻辑。
适用场景 :目标类很少,且不需要频繁更换代理逻辑。
缺点 :如果接口新增方法,代理类必须同步修改;如果有很多Service,每个都要写一个代理类,代码冗余严重。
/**
* 接口定义
*/
public interface UserService {
void saveUser(String name);
}
/**
* 目标对象(真实业务)
*/
public class UserServiceImpl implements UserService{
@Override
public void saveUser(String name) {
System.out.println("保存用户:" + name);
}
}
/**
* 静态代理类
*/
public class UserServiceProxy implements UserService{
private UserService target;//持有目标对象
public UserServiceProxy(UserService userService){
this.target = userService;
}
@Override
public void saveUser(String name) {
System.out.println("【前置增强】...");
target.saveUser(name);//调用真实业务
System.out.println("【后置增强】...");
}
}
public class Main {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = new UserServiceProxy(target);
proxy.saveUser("张三");
}
}
三、动态代理(运行时生成)
为了解决静态代理的冗余问题,Java提供了动态代理,在运行时动态生成代理字节码。主要分为两种:
一、JDK动态代理(基于接口)
- 要求 :目标对象必须实现至少一个接口 。
- 核心 :
java.lang.reflect.Proxy和InvocationHandler。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkLogProxy implements InvocationHandler {
private Object target;//目标对象
public JdkLogProxy(Object target){
this.target = target;
}
/**
* 生成代理对象
* @param target
* @return
*/
public static Object getProxy(Object target){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JdkLogProxy(target));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【JDK动态代理】前置...");
Object result = method.invoke(target,args);//使用反射调用方法
System.out.println("【JDK动态代理】后置...");
return result;
}
}
public class Main {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) JdkLogProxy.getProxy(target);
proxy.saveUser("张三");
}
}
二、CGLIB动态代理(基于继承)
- 要求 :目标对象不需要实现接口 ,底层通过生成目标类的子类来实现(
Enhancer)。 - 注意 :目标类不能被
final修饰,方法也不能是final。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibLogProxy implements MethodInterceptor {
/**
* 生成代理对象(子类)
* @param clazz
* @return
*/
public static Object getProxy(Class<?> clazz){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new CglibLogProxy());
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("【CGLIB动态代理】前置...");
Object result = proxy.invokeSuper(obj, args);//调用父类(目标类)方法
System.out.println("【CGLIB动态代理】后置...");
return result;
}
}
/**
* 目标类(没有实现接口)
*/
public class UserService {
public void saveUser(String name) {
System.out.println("保存:" + name);
}
}
public class Main {
public static void main(String[] args) {
UserService proxy = (UserService) CglibLogProxy.getProxy(UserService.class);
proxy.saveUser("王五");
}
}
需要注意的是:如果使用的是高版本的JDK,可能会报 InaccessibleObjectException,具体原因如下:
- Java 9+ 模块化 :JDK 内部将
java.lang.ClassLoader等核心类放在了java.base模块中。 - 强封装策略 :从 Java 16/17 开始,JDK 严格禁止通过
setAccessible(true)来强行调用java.base中未明确开放的内部包(java.lang)。 - CGLIB 的底层逻辑 :CGLIB 为了动态生成代理类的字节码,需要调用
ClassLoader.defineClass()这个 受保护的内部方法 。CGLIB 尝试用反射强行打开访问权限,结果被 JDK 直接拒绝,抛出InaccessibleObjectException。
解决方法:添加JVM启动参数,显式打开 java.lang 包供所有未命名模块访问。
--add-opens java.base/java.lang=ALL-UNNAMED
三、二者对比
| 维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现机制 | 反射 + 生成接口的匿名实现类 | 生成目标类的子类(ASM字节码技术) |
| 目标要求 | 必须实现接口 | 无须接口 (普通类即可) |
| 性能 | 反射调用效率相对低(JDK8+优化后差距缩小) | 生成字节码快,调用性能略高 |
| 依赖 | JDK原生,无外部依赖 | 需引入 cglib 包(Spring已内置) |
| 无法代理 | 接口方法 | final 修饰的类或方法 |

浙公网安备 33010602011771号