1.什么是代理模式
**代理模式是通过一个代理对象控制对原对象的访问,在不修改原对象的前提下,增强或者扩展其功能。**代理对象作为中间层,可以在调用目标对象前后插入额外的逻辑(如日志、权限验证、缓存等等)。
2.为什么需要代理模式
- 保护目标对象:隐藏目标对象细节,限制直接访问目标对象;
- 功能增强:无侵入式地实现对目标对象的功能扩展;
- 解耦:将核心业务逻辑非功能性需求分离(如日志);
3.代理模式的分类
代理分为静态代理、动态代理。而动态代理可以分为JDK代理以及cglib代理。其中cglib代理又经常被单独提及,因为其在实现上无需涉及到接口实现。
类型 | 特点 | 适用场景 |
---|---|---|
静态代理 | 手动编写代理类,代理类与目标类实现同一接口 | 简单场景,代理类数量少时使用,需要代理的类数量太多,代理工作就变得多量而且低效了,代码冗余 |
动态代理 | 运行时动态生成代理类,无需手动编写代理类代码 | 复杂场景,需代理多个类或方法 |
JDK代理 | 基于接口实现,要求目标类必须实现接口 | Spring默认对接口的代理 |
CGLIB代理 | 基于继承实现,可代理无接口的类 | 代理普通类,或目标类无接口时使用 |
3.1静态代理
java
// 目标接口
interface UserService {
void save();
}
// 目标类
class UserServiceImpl implements UserService {
@Override
public void save() { System.out.println("保存用户"); }
}
// 静态代理类
class UserServiceProxy implements UserService {
private UserService target = new UserServiceImpl();
@Override
public void save() {
//经典场景就是通过这种增强方式实现被调用类中方法的日志增强
System.out.println("前置增强,比如输出日志");
target.save(); // 调用目标方法
System.out.println("后置增强");
}
}
静态代理是既可以代理有接口的实现类(上述)、也可以代理没有实现接口的类的(通过即成的方式)。
3.2JDK动态代理
java
public class JdkProxy implements InvocationHandler {
private Object target;
public Object getProxy(Object target) {
this.target = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@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;
}
}
使用
java
public interface ServiceDao {
void save();
}
public class ServiceDaoImpl implements ServiceDao {
@Override
public void save() {
System.out.println("数据保存成功");
}
}
//Main.java
public class Main {
public static void main(String[] args) {
ServiceDao target = new ServiceDaoImpl();
JdkProxy proxyFactory = new JdkProxy();
//这一步强转的类型是接口,并不是具体的实现类!!!之后再调用接口的方法
ServiceDao proxy = (ServiceDao) proxyFactory.getProxy(target);
proxy.save(); // 正常执行代理逻辑
}
}
JDK动态代理生成的代理对象本质上是接口的实现类 ,而非目标类(如ServiceDaoImpl
)的子类。所以JDK代理只能够针对至少实现了一个接口的类进行代理,如果目标类没有实现任何接口,JDK代理无法为其生成代理对象,这时候需要使用CGLIB代理,后者通过继承目标类来生成子类代理。
如果遇上多个接口实现的类,不同接口中有相同方法签名的方法怎么办?
可以在InvocationHandler.invoke中通过Method对象区分
java
public class UserServiceImpl implements UserService, Loggable, Serializable {
@Override
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
@Override
public void log(String message) {
System.out.println("日志记录:" + message);
}
}
//如何区分不同接口相同的方法名,进行不同处理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == UserService.class) {
System.out.println("处理UserService方法");
} else if (method.getDeclaringClass() == Loggable.class) {
System.out.println("处理Loggable方法");
}
return method.invoke(target, args);
}
JDK动态代理只能增强接口中声明的方法。即使目标类有自己独有的非接口方法,这些方法也不会被代理,因此调用这些方法时不会触发`InvocationHandler`的`invoke`方法。用户需要明确,只有接口中定义的方法会被代理拦截和增强。
总结:DK动态代理天然支持多接口代理,一个代理对象即可覆盖所有接口。同时其局限性,即必须基于接口,只能增强接口方法,而无法处理类自身的方法,这也是为什么CGLIB代理在需要代理普通类时被推荐使用的原因。
3.3 cglib代理
那么针对没有实现接口的类,我们可以通过cglib代理(子类代理)进行代理实现。
对比点 | JDK代理 | CGLIB代理 |
---|---|---|
实现方式 | 基于接口 | 基于继承 |
依赖条件 | 目标类必须实现接口 | 目标类不能是final类,方法不能是final |
性能 | 生成代理快,但反射调用慢 | 生成代理慢,但方法调用快(FastClass机制) |
代理对象类型 | 实现目标接口的代理类 | 目标类的子类 |
通过继承的方式实现代理,可以想象final类是无法继承,final方法也是无法继承的,故此这一类的类和方法都无法通过cglib代理。
java
public class CglibProxy implements MethodInterceptor {
public Object getProxy(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz); // 设置父类
enhancer.setCallback(this); // 设置回调
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;
}
}
//Main.java
public class Main {
public static void main(String[] args) {
CGLIBProxy proxy = new CGLIBProxy();
UserService proxy1 = (UserService) proxy.getProxy(UserServiceImpl.class);
proxy1.save();
}
}
//输出
CGLIB前置增强
保存用户
CGLIB后置增强
总结:掌握代理模式的核心在于理解"控制访问"和"功能增强"的本质,区分静态代理与动态代理的适用场景,并能在实际开发中灵活选择JDK代理或CGLIB代理。通过代理模式,可以实现代码解耦、功能扩展,同时提升系统的可维护性。
参考文献:
......