目录:
Spring - AOP - 底层原理
1. 代理模式
代理模式是一种比较好的理解的设计模式。简单来说就是:
- 使用代理对象来增强目标对象(target obiect),这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
- 将核心业务代码和非核心的公共代码分离解耦,提高代码可维护性,让被代理类专注业务降低代码复杂度。
- 被代理类专注业务
- 代理类非核心的公共代码
通常用代理实现比如拦截器,事务控制,还有测试框架 mock、用户鉴权、日志、全局异常处理等功能
- 代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
举个例子:客户找老板, 让秘书登记来访人员和时间, 记录来访用时,并且让秘书拒绝老板不想见的客户。 秘书充当代理角色。
/**
* CEO 类代表首席执行官,用于模拟与客户会面的行为。
*/
public class CEO {
/**
* 与指定名称的客户进行会面。
*
* @param name 客户的名称。
* 说明:该参数用于指定与CEO会面的客户的名字,以便在会面过程中进行引用。
*/
public void meeting(String name) {
System.out.println("CEO 接见客户:" + name);
}
}
/**
*=========================================================================
* CEO代理类,用于控制对CEO会议的访问。
* 该类继承自CEO类,以实现对特定人员(如张三)的访问控制。
*/
public class CEOProxy extends CEO{
/**
* 代理会议访问方法。
* 在允许访问之前,此方法会记录来访者的姓名。
* 如果来访者是张三,则允许进入会议;否则,不允许进入。
*
* @param name 来访者的姓名。
*/
@Override
public void meeting(String name) {
// 记录来访人员,以便于会议管理和安全控制。
System.out.println("记录来访人员:" + name);
// 判断来访者是否为张三,张三是被允许直接参加会议的人。
if (name.equals("张三")){
System.out.println("允许进入");
// 如果是张三,則调用CEO的会议方法,允许进入会议。
super.meeting(name);
}
}
}
/**
*=========================================================================
* 测试代理类的示例。
* 该类演示了如何使用代理模式来控制对CEO对象的访问。
*/
public class TestProxy {
/**
* 程序入口点。
* 创建一个CEO代理对象,并通过代理对象来调用会议方法。
* @param args 命令行参数,本例中未使用。
*/
public static void main(String[] args) {
// 创建CEO的代理对象
CEO ceo = new CEOProxy();
// 通过代理对象安排会议
ceo.meeting("张三");
}
}
2. 静态代理
- 静态代理是一种设计模式,其中代理类是在编译时期就确定的,也就是说,代理类的源代码在编译时就已经存在,而不是在运行时动态生成的。这种代理模式通常用于需要在方法调用前后添加额外功能的场景,例如日志记录、权限检查、事务管理等。
在Java中,静态代理通常涉及以下元素:
- 接口:定义了一组方法,这些方法将由代理类和真实主题类实现。
- 真实主题类:实现了接口,并提供了具体的功能实现。
- 代理类:也实现了相同的接口,它包含对真实主题类的引用。代理类的主要职责是封装对真实主题的调用,并在调用前后添加额外的操作。
下面是一个简单的Java静态代理模式的示例:
// 定义接口
public interface Subject {
void doAction();
}
// 目标类
public class RealSubject implements Subject {
@Override
public void doAction() {
System.out.println("Real subject is doing action.");
}
}
// 代理类
public class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void doAction() {
System.out.println("Proxy: Before calling real subject.");
realSubject.doAction();
System.out.println("Proxy: After calling real subject.");
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 创建真实主题对象,该对象是代理主题所代理的实际执行者
RealSubject realSubject = new RealSubject();
// 创建代理主题对象,并将真实主题作为参数传入,代理主题将通过该真实主题执行操作
ProxySubject proxySubject = new ProxySubject(realSubject);
// 通过代理主题对象调用方法,此时代理主题将会在调用真实主题的方法前后添加额外的操作
proxySubject.doAction();
}
}
在这个例子中,
Subject
是一个接口,RealSubject
是实现了这个接口的真实主题类,而ProxySubject
是代理类,它也实现了Subject
接口,并持有RealSubject
的引用。当ProxySubject
的doAction
方法被调用时,它会先执行一些前置操作,然后调用RealSubject
的doAction
方法,最后执行一些后置操作。静态代理的优点是结构清晰,易于理解和实现。然而,它的缺点也很明显,如果需要代理的对象很多,那么需要为每一个对象都创建一个代理类,这会导致代码量的增加和维护成本的上升。此外,如果接口中的方法数量增加,那么所有的代理类都需要进行修改,这违反了开闭原则(Open/Closed Principle)。因此,在实际应用中,动态代理往往更受欢迎,因为它可以避免这些问题。
3. JDK 动态代理
- JDK 动态代理是 Java 反射 API 中的一个重要特性,它允许你在运行时动态地创建一个实现了指定接口的代理类。这个代理类可以用来包装一个目标对象,从而在调用目标对象的方法前后添加额外的行为,这在面向切面编程(AOP)和中间件设计模式中非常有用。
要使用 JDK 动态代理,你需要了解以下几个关键概念:
-
java.lang.reflect.InvocationHandler
:这是所有动态代理类必须实现的接口。当代理对象的方法被调用时,InvocationHandler
接口的invoke()
方法会被调用,该方法接收三个参数:proxy
:代理对象本身。method
:正在调用的方法的 Method 对象。args
:调用方法时传递的参数数组。
-
java.lang.reflect.Proxy
:这是一个工具类,用于创建代理实例。它有一个静态方法newProxyInstance()
,接受以下参数:-
ClassLoader loader
:用于加载代理类的类加载器。 -
Class<?>[] interfaces
:代理类需要实现的接口列表。 -
InvocationHandler h
:处理代理方法调用的InvocationHandler
实例。
-
下面是一个使用 JDK 动态代理的基本示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
public interface MyInterface {
void doSomething();
}
// 目标类
public class RealObject implements MyInterface {
public void doSomething() {
System.out.println("Real object doing something.");
}
}
// 代理类
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call");
Object result = method.invoke(target, args);
System.out.println("After method call");
return result;
}
}
// 测试类
public class Main {
public static void main(String[] args) {
/*
* 创建一个真实对象,该对象实现了MyInterface接口。
* 这个真实对象将被代理对象在需要时调用,以执行实际的业务逻辑。
*/
MyInterface realObject = new RealObject();
/*
* 创建一个代理对象,该代理对象实现了MyInterface接口。
* 代理对象在调用方法时,会将方法调用转发给真实对象。
* 使用Proxy.newProxyInstance动态地创建一个代理类,该代理类实现了MyInterface接口。
* 参数说明:
* - Main.class.getClassLoader():指定代理类的类加载器,确保代理类和真实对象使用相同的类加载器。
* - new Class[]{MyInterface.class}:指定代理类实现的接口列表,此处指明代理类将实现MyInterface接口。
* - new DynamicProxy(realObject):一个InvocationHandler实现,代理对象的方法调用将被转发到这个InvocationHandler的invoke方法。
*/
MyInterface proxyObject = (MyInterface) Proxy.newProxyInstance(
Main.class.getClassLoader(),
new Class[]{MyInterface.class},
new DynamicProxy(realObject)
);
/*
* 通过代理对象调用方法。
* 此时,方法的实际执行者是动态代理中的InvocationHandler,它会根据需要调用真实对象的方法。
*/
proxyObject.doSomething();
}
}
}
在这个例子中,我们首先定义了一个接口
MyInterface
和一个实现了该接口的RealObject
类。然后我们创建了一个DynamicProxy
类来实现InvocationHandler
接口,这个类会在方法调用前后打印一些信息。最后,我们在main
方法中使用Proxy.newProxyInstance()
方法创建了MyInterface
的代理对象,并调用了它的doSomething
方法。当这个方法被调用时,DynamicProxy
的invoke
方法就会被执行,从而实现了在方法调用前后的增强行为。
4. CGlib 动态代理
- CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行期扩展Java类与实现Java接口。CGLIB的实质是字节码技术,它可以用来为一个类创建子类,因此它要求代理的目标对象必须是可继承的(即没有声明为
final
)。CGLIB常被用于那些不希望因为引入接口而修改现有代码的情况下,或者当无法访问目标源代码时。
CGLIB的工作原理
- CGLIB通过字节码技术为一个类创建子类,并在子类中加入拦截器(interceptor),这样就可以在方法调用前后执行自定义的代码。拦截器可以理解为一种特殊的适配器,它允许你控制对目标对象方法的调用。
使用CGLIB进行动态代理
要使用CGLIB进行动态代理,你需要以下步骤:
-
添加CGLIB依赖到你的项目中(Spring 已集成)。如果你使用Maven,可以在
pom.xml
文件中添加如下依赖:<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
-
创建一个增强的目标对象,通常这个对象不是接口的实现。
-
创建一个
Enhancer
实例,并设置其回调(Callback)和目标对象。 -
调用
Enhancer
的create()
方法来创建代理对象。 -
使用代理对象调用目标方法。
下面是一个使用CGLIB动态代理的例子:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 定义接口
public interface Service {
void execute();
}
// 目标类
public class ServiceImpl implements Service {
@Override
public void execute() {
System.out.println("Executing service...");
}
}
// 代理类
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method execution");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method execution");
return result;
}
}
// 测试类
public class main {
public static void main(String[] args) {
// 创建一个Enhancer实例,用于生成CGLIB代理对象
Enhancer enhancer = new Enhancer();
// 设置代理对象的基类为ServiceImpl
// 这里的ServiceImpl是一个具体的服务实现类,代理对象将继承它的行为
enhancer.setSuperclass(ServiceImpl.class);
// 设置回调机制,这里使用CglibProxy实现
// 当代理对象的方法被调用时,实际上会触发CglibProxy中的方法拦截器
enhancer.setCallback(new CglibProxy());
// 创建ServiceImpl的代理对象
// 这里的代理对象在功能上等同于ServiceImpl,但多了动态织入的逻辑
Service proxyService = (Service) enhancer.create();
// 调用代理对象的方法,实际上会触发CglibProxy中的逻辑
// 这里执行的具体行为取决于CglibProxy的实现
proxyService.execute();
}
}
在这个例子中,我们定义了一个服务接口
Service
和它的实现类ServiceImpl
。然后我们创建了一个CglibProxy
类来实现MethodInterceptor
接口,这个类会在方法调用前后打印一些信息。接下来,我们使用Enhancer
创建了ServiceImpl
的代理对象,并调用了它的execute
方法。当这个方法被调用时,CglibProxy
的intercept
方法就会被执行,从而实现了在方法调用前后的增强行为。
5. 注意事项
- CGLIB代理的对象不能是
final
类或final
方法,因为CGLIB是通过继承来实现动态代理的。 - CGLIB在性能上可能比JDK动态代理稍慢,因为它涉及到字节码的生成和解析。但在某些场景下,如无法使用接口的情况,CGLIB是一个很好的选择。