【面试题】什么是代理以及如何实现代理
在 Spring 中,代理主要用于实现 AOP(面向切面编程),它是一种在不修改目标对象代码的情况下,为目标对象添加额外功能的机制。
在 Spring 中,静态代理是一种在编译期就确定代理关系的方式。它需要手动创建代理类,代理类实现与目标对象相同的接口,并在代理类中调用目标对象的方法,同时可以在调用前后添加额外的逻辑。
一、静态代理的实现步骤
-
定义目标接口:
- 首先定义一个接口,该接口包含了目标对象需要实现的方法。例如:
public interface TargetInterface { void method(); }
-
实现目标对象:
- 创建一个实现目标接口的类,作为被代理的目标对象。例如:
public class TargetObject implements TargetInterface { @Override public void method() { System.out.println("目标对象的方法被调用。"); } }
-
创建代理对象:
- 手动创建一个代理类,同样实现目标接口。在代理类中持有一个目标对象的引用,并在代理类的方法中调用目标对象的方法,同时可以在调用前后添加额外的逻辑。例如:
public class StaticProxy implements TargetInterface { private TargetInterface target; public StaticProxy(TargetInterface target) { this.target = target; } @Override public void method() { System.out.println("在调用目标对象方法之前执行的逻辑。"); target.method(); System.out.println("在调用目标对象方法之后执行的逻辑。"); } }
-
使用代理对象:
- 在客户端代码中,创建目标对象和代理对象,并通过代理对象调用目标对象的方法。例如:
public class Client { public static void main(String[] args) { TargetInterface target = new TargetObject(); TargetInterface proxy = new StaticProxy(target); proxy.method(); } }
二、静态代理的特点
-
明确的代理关系:
- 静态代理在编译期就确定了代理对象和目标对象的关系,代码结构相对清晰,容易理解。
- 可以清楚地看到代理对象对目标对象的方法调用以及在调用前后添加的额外逻辑。
-
灵活性有限:
- 如果需要为多个不同的目标对象创建代理,需要为每个目标对象都手动创建一个代理类,代码冗余度较高。
- 当目标接口发生变化时,代理类也需要相应地进行修改,不符合开闭原则。
-
适用于简单场景:
- 静态代理适用于一些简单的场景,例如为单个目标对象添加特定的功能,或者在不需要频繁变化的情况下进行代理。
总的来说,静态代理在 Spring 中虽然不常用,但它是理解代理机制的基础。在实际应用中,Spring 更多地使用动态代理来实现 AOP,以提高代码的灵活性和可维护性。
在 Spring 中,动态代理是在运行时动态地生成代理对象的一种机制。与静态代理不同,动态代理不需要为每个目标对象都手动创建一个代理类,而是根据目标对象的类型和需要添加的额外逻辑,在运行时自动生成代理对象。
一、JDK 动态代理
-
实现原理:
- JDK 动态代理是基于 Java 的反射机制实现的。它要求目标对象必须实现一个接口,通过实现
InvocationHandler
接口并使用Proxy
类来生成代理对象。 InvocationHandler
接口的invoke
方法是代理对象的核心方法,在这个方法中可以拦截对目标对象方法的调用,并在调用前后执行额外的逻辑。
- JDK 动态代理是基于 Java 的反射机制实现的。它要求目标对象必须实现一个接口,通过实现
-
实现步骤:
- 定义目标接口:与静态代理一样,首先定义一个接口,目标对象实现这个接口。例如:
public interface TargetInterface { void method(); }
- 实现目标对象:创建一个实现目标接口的类。例如:
public class TargetObject implements TargetInterface { @Override public void method() { System.out.println("目标对象的方法被调用。"); } }
- 创建代理对象:实现
InvocationHandler
接口,在invoke
方法中添加额外的逻辑,并调用目标对象的方法。然后使用Proxy
类的newProxyInstance
方法创建代理对象。例如:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkDynamicProxy implements InvocationHandler { private Object target; public JdkDynamicProxy(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; } public static Object createProxy(Object target) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new JdkDynamicProxy(target)); } }
- 使用代理对象:在客户端代码中,使用
createProxy
方法创建代理对象,并通过代理对象调用目标对象的方法。例如:
public class Client { public static void main(String[] args) { TargetInterface target = new TargetObject(); TargetInterface proxy = (TargetInterface) JdkDynamicProxy.createProxy(target); proxy.method(); } }
二、CGLIB 动态代理
-
实现原理:
- CGLIB 动态代理是通过生成目标类的子类来实现代理的。它使用字节码生成技术,在运行时动态地生成一个继承目标类的子类,并在子类中重写目标方法,添加额外的逻辑。
-
实现步骤:
- 定义目标类:创建一个没有实现接口的类。例如:
public class TargetClass { public void method() { System.out.println("目标对象的方法被调用。"); } }
- 创建代理对象:使用 CGLIB 的
Enhancer
类来创建代理对象。设置代理对象的父类为目标类,并实现MethodInterceptor
接口,在intercept
方法中添加额外的逻辑,并调用目标对象的方法。例如:
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibDynamicProxy implements MethodInterceptor { public Object createProxy(Object 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 = proxy.invokeSuper(obj, args); System.out.println("在调用目标对象方法之后执行的逻辑。"); return result; } }
- 使用代理对象:在客户端代码中,使用
createProxy
方法创建代理对象,并通过代理对象调用目标对象的方法。例如:
public class Client { public static void main(String[] args) { TargetClass target = new TargetClass(); TargetClass proxy = (TargetClass) new CglibDynamicProxy().createProxy(target); proxy.method(); } }
三、动态代理的特点
-
灵活性高:
- 动态代理可以在运行时根据需要为不同的目标对象生成代理对象,不需要为每个目标对象都手动创建代理类。
- 当目标对象的类型或需要添加的额外逻辑发生变化时,只需要修改代理对象的生成逻辑,而不需要修改目标对象的代码。
-
符合开闭原则:
- 可以在不修改目标对象代码的情况下,为目标对象添加新的功能,符合开闭原则。
-
适用于复杂场景:
- 动态代理适用于需要为多个不同类型的目标对象添加相同或不同的额外逻辑的复杂场景,如在企业级应用中实现日志记录、事务管理、安全检查等功能。